• go-webSocket——gorilla


    gorilla-websocket


    参考文章:

    0 HTTP

    http 是典型的 C/S 架构,客户端向服务端发送请求(request),服务端做出应答(response)。

    golang 的标准库 net/http 提供了 http 编程有关的接口,封装了内部TCP连接和报文解析的复杂琐碎的细节,使用者只需要和 http.requesthttp.ResponseWriter 两个对象交互。

    0.1 http.HandleFunc

    源码,相当于一个适配器

    type HandlerFunc func(ResponseWriter, *Request)
    
    // ServeHTTP calls f(w, r).
    func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
        f(w, r)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    0.1.1 简单实现

    package main
    
    import (
       "io"
       "net/http"
    )
    
    func helloHandler(w http.ResponseWriter, r *http.Request) {
       io.WriteString(w, "hello world!\n")
    }
    
    func main() {
        //  回调函数
       http.HandleFunc("/", helloHandler)
       http.ListenAndServe(":8080", nil)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    0.1.2 net/http 提供的handler

    大部分的服务器逻辑都需要使用者编写对应的 Handler,不过有些 Handler 使用频繁,因此 net/http 提供了它们的实现。

    • 比如负责文件 hosting 的 FileServer
    • 负责 404 的NotFoundHandler
    • 负责重定向的RedirectHandler

    0.1.3 ListenAndServer()

    // addr:监听的地址
    // handler:回调函数
    func ListenAndServe(addr string, handler Handler) error {
        server := &Server{Addr: addr, Handler: handler}
        return server.ListenAndServe()
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
        http.ListenAndServe("127.0.0.1:8000", nil)
    
    • 1

    0.1.4 Request

    Request 就是封装好的客户端请求,包括 URL,method,header 等等所有信息,以及一些方便使用的方法:

    Handler 需要知道关于请求的任何信息,都要从这个对象中获取,一般不会直接修改这个对象

    type Request struct {
        // Method specifies the HTTP method (GET, POST, PUT, etc.).For client requests an empty string means GET.
        Method string
    
        // URL specifies either the URI being requested (for server requests) or the URL to access (for client requests).
        URL *url.URL
    
        // The protocol version for incoming requests.
        // Client requests always use HTTP/1.1.
        Proto      string // "HTTP/1.0"
        ProtoMajor int    // 1
        ProtoMinor int    // 0
    
        // A header maps request lines to their values.
        // If the header says
        //
        //    accept-encoding: gzip, deflate
        //    Accept-Language: en-us
        //    Connection: keep-alive
        //
        // then
        //
        //    Header = map[string][]string{
        //        "Accept-Encoding": {"gzip, deflate"},
        //        "Accept-Language": {"en-us"},
        //        "Connection": {"keep-alive"},
        //    }
        Header Header
    
        // Body is the request's body.
        Body io.ReadCloser
        ContentLength int64
        TransferEncoding []string
        Close bool
        Host string
        Form url.Values
        PostForm url.Values
        MultipartForm *multipart.Form
        ...
        RemoteAddr string
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    0.1.5 ResponseWriter

    ResponseWriter 是一个接口,定义了三个方法:

    • Header():返回一个 Header 对象,可以通过它的 Set() 方法设置头部,注意最终返回的头部信息可能和你写进去的不完全相同,因为后续处理还可能修改头部的值(比如设置 Content-LengthContent-type 等操作)
    • Write(): 写 response 的主体部分,比如 html 或者 json 的内容就是放到这里的
    • WriteHeader():设置 status code,如果没有调用这个函数,默认设置为 http.StatusOK, 就是 200 状态码
    // A ResponseWriter interface is used by an HTTP handler to
    // construct an HTTP response.
    type ResponseWriter interface {
        Header() Header
        Write([]byte) (int, error)
        WriteHeader(int)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    0.2 test

    0.2.1 HTTP服务端

    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    func main() {
        http.HandleFunc("/go", myHandler)
        http.ListenAndServe("127.0.0.1:8000", nil)
    }
    
    func myHandler(w http.ResponseWriter, r *http.Request) {
        fmt.Println(r.RemoteAddr, "连接成功")
        // 请求方式:GET POST DELETE PUT UPDATE
        fmt.Println("method:", r.Method)
        fmt.Println("url:", r.URL.Path)
        fmt.Println("header:", r.Header)
        fmt.Println("body:", r.Body)
        // 回复
        w.Write([]byte("test成功"))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    0.2.2 HTTP客户端

    package main
    
    import (
        "fmt"
        "io"
        "net/http"
    )
    
    func main() {
        //resp, _ := http.Get("http://www.baidu.com")
        //fmt.Println(resp)
        resp, _ := http.Get("http://127.0.0.1:8000/go")
        defer resp.Body.Close()
        // 200 OK
        fmt.Println(resp.Status)
        fmt.Println(resp.Header)
    
        buf := make([]byte, 1024)
        for {
            // 接收服务端信息
            n, err := resp.Body.Read(buf)
            if err != nil && err != io.EOF {
                fmt.Println(err)
                return
            } else {
                fmt.Println("读取完毕")
                res := string(buf[:n])
                fmt.Println(res)
                break
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    1 webSocket

    1.1 是什么

    • WebSocket是一种在单个TCP连接上进行全双工通信的协议,长连接,双向传输
    • 需要安装第三方包:go get -u -v github.com/gorilla/websocket
    • WebSocket 协议实现起来相对简单。 HTTP 协议初始握手建立连接,WebSocket 实质上使用原始 TCP 读取 / 写入数据
    • http有良好的兼容性,ws和http的默认端口都是80,wss和https的默认端口都是443

    1.2 webSocket握手协议

    1.2.1 客户端请求 Request Header

    GET /chat HTTP/1.1
        Host: server.example.com
        Upgrade: websocket   	// 指明使用WebSocket协议
        Connection: Upgrade		// 指明使用WebSocket协议
        Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==   // Bse64 encode的值,是浏览器随机生成的
        Sec-WebSocket-Protocol: chat, superchat
        Sec-WebSocket-Version: 13  //指定Websocket协议版本
        Origin: http://example.com
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    服务端收到Sec-WebSocket-Key后拼接上一个固定的GUID,进行一次SHA-1摘要,再转成Base64编码,得到Sec-WebSocket-Accept返回给客户端。客户端对本地的Sec-WebSocket-Key执行同样的操作跟服务端返回的结果进行对比,如果不一致会返回错误关闭连接。如此操作是为了把websocket header 跟http header区分开

    1.2.2 服务器响应 Response Header

    HTTP/1.1 101 Switching Protocols
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
        Sec-WebSocket-Protocol: chat
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.2.3 websocket发送的消息类型

    5种TextMessag、BinaryMessage、CloseMessag、PingMessage、PongMessage

    • TextMessagBinaryMessage分别表示发送文本消息和二进制消息

    • CloseMessage关闭帧,接收方收到这个消息就关闭连接

    • PingMessagePongMessage是保持心跳的帧,服务器发ping给浏览器,浏览器返回pong消息

    2 gorilla/websocket

    websocket由http升级而来,首先发送附带Upgrade请求头的Http请求,所以我们需要在处理Http请求时拦截请求并判断其是否为websocket升级请求,如果是则调用gorilla/websocket库相应函数处理升级请求

    2.1 Upgrader

    Upgrader发送附带Upgrade请求头的Http请求,把 http 请求升级为长连接的 WebSocket,结构如下:

    type Upgrader struct {
        // 升级 websocket 握手完成的超时时间
        HandshakeTimeout time.Duration
    
        // io 操作的缓存大小,如果不指定就会自动分配。
        ReadBufferSize, WriteBufferSize int
    
        // 写数据操作的缓存池,如果没有设置值,write buffers 将会分配到链接生命周期里。
        WriteBufferPool BufferPool
    
        //按顺序指定服务支持的协议,如值存在,则服务会从第一个开始匹配客户端的协议。
        Subprotocols []string
    
        // http 的错误响应函数,如果没有设置 Error 则,会生成 http.Error 的错误响应。
        Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
    
        // 如果请求Origin标头可以接受,CheckOrigin将返回true。 如果CheckOrigin为nil,则使用安全默认值:如果Origin请求头存在且原始主机不等于请求主机头,则返回false。
        // 请求检查函数,用于统一的链接检查,以防止跨站点请求伪造。如果不检查,就设置一个返回值为true的函数
        CheckOrigin func(r *http.Request) bool
    
        // EnableCompression 指定服务器是否应尝试协商每个邮件压缩(RFC 7692)。 将此值设置为true并不能保证将支持压缩。 目前仅支持“无上下文接管”模式
        EnableCompression bool
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.1.1 创建Upgrader实例

    该实例用于升级请求

    var upgrader = websocket.Upgrader{
        ReadBufferSize:  1024, //指定读缓存大小
        WriteBufferSize: 1024, //指定写缓存大小
        CheckOrigin:     checkOrigin,
    }
    // 检测请求来源
    func checkOrigin(r *http.Request) bool {
        if r.Method != "GET" {
            fmt.Println("method is not GET")
            return false
        }
        if r.URL.Path != "/ws" {
            fmt.Println("path error")
            return false
        }
        return true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    其中CheckOringin是一个函数,该函数用于拦截或放行跨域请求。函数返回值为bool类型,即true放行,false拦截。如果请求不是跨域请求可以不赋值

    2.1.2 升级协议

    func (*Upgrader) Upgrade 函数将 http 升级到 WebSocket 协议。

    // responseHeader包含在对客户端升级请求的响应中。 
    // 使用responseHeader指定cookie(Set-Cookie)和应用程序协商的子协议(Sec-WebSocket-Protocol)。
    // 如果升级失败,则升级将使用HTTP错误响应回复客户端
    // 返回一个 Conn 指针,使用 Conn 读写数据与客户端通信。
    func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    升级为websocket连接并获得一个conn实例,之后的发送接收操作皆有conn,其类型为websocket.Conn。

    //Http入口
    func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        //判断请求是否为websocket升级请求。
        if websocket.IsWebSocketUpgrade(r) {
            // 收到 http 请求后升级协议
            conn, err := upgrader.Upgrade(w, r, w.Header())
            // 向客户端发送消息使用 WriteMessage(messageType int, data []byte),参数1为消息类型,参数2消息内容
            conn.WriteMessage(websocket.TextMessage, []byte("升级成功"))
            // 接受客户端消息使用 ReadMessage(),该操作阻塞线程所以建议运行在其他协程上。
            //返回值(接收消息类型、接收消息内容、发生的错误)当然正常执行时错误为 nil。一旦连接关闭返回值类型为-1可用来终止读操作。
            go func() {
                for {
                    t, c, _ := conn.ReadMessage()
                    fmt.Println(t, string(c))
                    if t == -1 {
                        return
                    }
                }
            }()
        } else {
            //处理普通请求
            c := newContext(w, r)
            e.router.handle(c)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2.1.3 设置关闭连接监听

    函数为SetCloseHandler(h func(code int, text string) error)函数接收一个函数为参数,参数为nil时有一个默认实现,其源码为:

    func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
        if h == nil {
            h = func(code int, text string) error {
                message := FormatCloseMessage(code, "")
                c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
                return nil
            }
        }
        c.handleClose = h
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以看到作为参数的函数的参数为int和string类型正好和前端的close(long string)对应即前端调用close(long string)关闭连接后两个参数会被发送给后端并最终被func(code int, text string) error所使用。

    // 设置关闭连接监听
    conn.SetCloseHandler(func(code int, text string) error {
        fmt.Println(code, text) // 断开连接时将打印code和text
        return nil
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.1.4 总览

    type WsServer struct {
        ......
        // 定义一个 upgrade 类型用于升级 http 为 websocket
        upgrade  *websocket.Upgrader
    }
    
    func NewWsServer() *WsServer {
        ws.upgrade = &websocket.Upgrader{
            ReadBufferSize:  4096,//指定读缓存区大小
            WriteBufferSize: 1024,// 指定写缓存区大小
            // 检测请求来源
            CheckOrigin: func(r *http.Request) bool {
                if r.Method != "GET" {
                    fmt.Println("method is not GET")
                    return false
                }
                if r.URL.Path != "/ws" {
                    fmt.Println("path error")
                    return false
                }
                return true
            },upgrade
        }
        return ws
    }
    
    func (self *WsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        ......
        // 收到 http 请求后 升级 协议
        conn, err := self.upgrade.Upgrade(w, r, nil)
        if err != nil {
            fmt.Println("websocket error:", err)
            return
        }
        fmt.Println("client connect :", conn.RemoteAddr())
        go self.connHandle(conn)
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    3 Demo

    3.1

    3.1.1 服务端

    package main
    
    import (
    	"fmt"
    	"github.com/gorilla/websocket"
    	"log"
    	"net/http"
    )
    
    var upgrader = websocket.Upgrader{
    	ReadBufferSize:  4096,
    	WriteBufferSize: 1024,
    	CheckOrigin: func(r *http.Request) bool {
    
    		//if r.Method != "GET" {
    		//	fmt.Println("method is not GET")
    		//	return false
    		//}
    		//if r.URL.Path != "/ws" {
    		//	fmt.Println("path error")
    		//	return false
    		//}
    		return true
    	},
    }
    
    // ServerHTTP 用于升级协议
    func ServerHTTP(w http.ResponseWriter, r *http.Request) {
    	// 收到http请求之后升级协议
    	conn, err := upgrader.Upgrade(w, r, nil)
    	if err != nil {
    		log.Println("Error during connection upgrade:", err)
    		return
    	}
    	defer conn.Close()
    
    	for {
    		// 服务端读取客户端请求
    		messageType, message, err := conn.ReadMessage()
    		if err != nil {
    			log.Println("Error during message reading:", err)
    			break
    		}
    		log.Printf("Received:%s", message)
    
    		// 开启关闭连接监听
    		conn.SetCloseHandler(func(code int, text string) error {
    			fmt.Println(code, text) // 断开连接时将打印code和text
    			return nil
    		})
    
    		//服务端给客户端返回请求
    		err = conn.WriteMessage(messageType, message)
    		if err != nil {
    			log.Println("Error during message writing:", err)
    			return
    		}
    
    	}
    }
    
    func home(w http.ResponseWriter, r *http.Request) {
    	fmt.Fprintf(w, "Index Page")
    }
    
    func main() {
    	http.HandleFunc("/socket", ServerHTTP)
    	http.HandleFunc("/", home)
    	log.Fatal(http.ListenAndServe("localhost:8080", nil))
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    3.1.2 客户端

    // client.go
    package main
    
    import (
    	"github.com/gorilla/websocket"
    	"log"
    	"os"
    	"os/signal"
    	"time"
    )
    
    var done chan interface{}
    var interrupt chan os.Signal
    
    func receiveHandler(connection *websocket.Conn) {
    	defer close(done)
    	for {
    		_, msg, err := connection.ReadMessage()
    		if err != nil {
    			log.Println("Error in receive:", err)
    			return
    		}
    		log.Printf("Received: %s\n", msg)
    	}
    }
    
    func main() {
    	done = make(chan interface{})    // Channel to indicate that the receiverHandler is done
    	interrupt = make(chan os.Signal) // Channel to listen for interrupt signal to terminate gracefully
    
    	signal.Notify(interrupt, os.Interrupt) // Notify the interrupt channel for SIGINT
    
    	socketUrl := "ws://localhost:8080" + "/socket"
    	conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil)
    	if err != nil {
    		log.Fatal("Error connecting to Websocket Server:", err)
    	}
    	defer conn.Close()
    	go receiveHandler(conn)
    
    	// 无限循环使用select来通过通道监听事件
    	for {
    		select {
    		case <-time.After(time.Duration(1) * time.Millisecond * 1000):
    			//conn.WriteMessage()每秒钟写一条消息
    			err := conn.WriteMessage(websocket.TextMessage, []byte("Hello from GolangDocs!"))
    			if err != nil {
    				log.Println("Error during writing to websocket:", err)
    				return
    			}
    		//如果激活了中断信号,则所有未决的连接都将关闭
    		case <-interrupt:
    			// We received a SIGINT (Ctrl + C). Terminate gracefully...
    			log.Println("Received SIGINT interrupt signal. Closing all pending connections")
    
    			// Close our websocket connection
    			err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
    			if err != nil {
    				log.Println("Error during closing websocket:", err)
    				return
    			}
    
    			select {
    			// 如果receiveHandler通道退出,则通道'done'将关闭
    			case <-done:
    				log.Println("Receiver Channel Closed! Exiting....")
    			//如果'done'通道未关闭,则在1秒钟后会有超时,因此程序将在1秒钟超时后退出
    			case <-time.After(time.Duration(1) * time.Second):
    				log.Println("Timeout in closing receiving channel. Exiting....")
    			}
    			return
    		}
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    3.1.3 简单版

    服务端

    package main
    
    import (
    	"github.com/gorilla/websocket"
    	"log"
    	"net/http"
    )
    
    var upgrade = websocket.Upgrader{
    	ReadBufferSize:  1024,
    	WriteBufferSize: 1024,
    	CheckOrigin: func(r *http.Request) bool {
    		return true
    	},
    }
    
    func HelloHTTP(w http.ResponseWriter, r *http.Request) {
    	//1.升级协议,并返回升级后的长连接
    	conn, err := upgrade.Upgrade(w, r, nil)
    
    	if err != nil {
    		log.Println("Error during connection upgrade:", err)
    		return
    	}
    	defer conn.Close()
    
    	for {
    		// 2.读取客户端的请求信息
    		messageType, message, err := conn.ReadMessage()
    		if err != nil {
    			log.Println("Error during message writing:", err)
    			return
    		}
    		log.Printf("Recive message:%s", message)
    		//	3.返回给客户端信息
    		err = conn.WriteMessage(messageType, message)
    		if err != nil {
    			log.Println("Error during message writing:", err)
    			return
    		}
    	}
    
    }
    
    func main() {
    
    	http.HandleFunc("/socket", HelloHTTP)
    	http.ListenAndServe(":8080", nil)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    客户端

    package main
    
    import (
       "github.com/gorilla/websocket"
       "log"
       "time"
    )
    
    func ReceiveHandler(con *websocket.Conn) {
    
       for {
    
          _, message, err := con.ReadMessage()
          if err != nil {
             log.Println("Error during Receive:", err)
             return
          }
          log.Printf("Receive:%s\n", message)
       }
    }
    
    func main() {
       socketUrl := "ws://localhost:8080" + "/socket"
       // 使用 net.Dialer Dialer.Dial 函数建立 TCP 连接,建立成功后,取得了 net.Conn 对象,
       conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil)
       if err != nil {
          log.Fatal("Error connecting to websocket Server:", err)
       }
       defer conn.Close()
    
       ticker := time.Tick(time.Second)
       for range ticker {
          err = conn.WriteMessage(websocket.TextMessage, []byte("Hello World!"))
          if err != nil {
             log.Println("Error during writing to websocket:", err)
             return
          }
          // 接受客户端消息使用ReadMessage()该操作会阻塞线程所以建议运行在其他协程上
          go ReceiveHandler(conn)
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    打印信息
    服务端
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kG3Wj6w1-1659690158274)(C:\Users\sommer.liu\AppData\Roaming\Typora\typora-user-images\image-20220805164829515.png)]
    客户端
    在这里插入图片描述

  • 相关阅读:
    C++从入门到出门
    uniapp不同平台获取文件内容以及base64编码特征
    安全渗透测试网络基础知识之路由技术
    码神之路项目部署(五)
    如何入门 AI----如何确定学习目标
    Java版分布式微服务云开发架构 Spring Cloud+Spring Boot+Mybatis 电子招标采购系统功能清单
    【OpenGL】(1) 专栏介绍:OpenGL 库 | 3D 计算机图形应用 | GPGPU 计算 | 3D 建模和 3D动画 | 渲染技术介绍
    【JVM】——JVM中内存划分
    基于Android校园交流uniAPP+vue 微信小程序v7e1
    光会面向对象基础做不了项目,还得掌握这些进阶知识
  • 原文地址:https://blog.csdn.net/qq_42647903/article/details/126181499