• golang开发 gorilla websocket的使用


    很多APP都需要主动向用户推送消息,这就需要用到长连接的服务,即我们通常提到的websocket,同样也是使用socket服务,通信协议是基本类似的,在go中用的最多的、也是最简单的socket服务就是gorilla/websocket,它有21.1K的star,足以说明它的受欢迎程度, 它的github地址是 https://github.com/gorilla/websocket,我们的长连接服务也是通过gorilla/websocket改造出来的。

    websocket的简单使用

    我们使用的版本是1.3.0,首先下载websocket
    go get github.com/gorilla/websocket@v1.3.0

    把websocket/examples/echo下面的 client.go server.go 现在下来拷贝到项目里面。
    https://github.com/gorilla/websocket/blob/v1.3.0/examples/echo/server.go
    https://github.com/gorilla/websocket/blob/v1.3.0/examples/echo/client.go

    在一个终端执行 websocket 服务
    go run server.go

    recv: 2024-04-16 15:09:45.805438 +0800 CST m=+1.007536889
    recv: 2024-04-16 15:09:46.805425 +0800 CST m=+2.007517605
    recv: 2024-04-16 15:09:47.806274 +0800 CST m=+3.008359325
    recv: 2024-04-16 15:09:48.80495 +0800 CST m=+4.007028866
    recv: 2024-04-16 15:09:49.805743 +0800 CST m=+5.007816108
    recv: 2024-04-16 15:09:50.806087 +0800 CST m=+6.008153310
    recv: 2024-04-16 15:09:51.805348 +0800 CST m=+7.007407266
    

    再打开一个终端执行 go run client.go,充当客户端

    connecting to ws://localhost:8080/echo
    recv: 2024-04-16 15:09:45.805438 +0800 CST m=+1.007536889
    recv: 2024-04-16 15:09:46.805425 +0800 CST m=+2.007517605
    recv: 2024-04-16 15:09:47.806274 +0800 CST m=+3.008359325
    recv: 2024-04-16 15:09:48.80495 +0800 CST m=+4.007028866
    recv: 2024-04-16 15:09:49.805743 +0800 CST m=+5.007816108
    recv: 2024-04-16 15:09:50.806087 +0800 CST m=+6.008153310
    recv: 2024-04-16 15:09:51.805348 +0800 CST m=+7.007407266
    

    我们看看这个简单的例子。
    client.go

    go func() {
    		defer close(done)
    		for {
    			_, message, err := c.ReadMessage()
    			if err != nil {
    				log.Println("read:", err)
    				return
    			}
    			log.Printf("recv: %s", message)
    		}
    	}()
    
    	ticker := time.NewTicker(time.Second)
    	defer ticker.Stop()
    
    	for {
    		select {
    		case <-done:
    			return
    		case t := <-ticker.C:
    			err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
    			if err != nil {
    				log.Println("write:", err)
    				return
    			}
    		case <-interrupt:
    			log.Println("interrupt")
    
    			// Cleanly close the connection by sending a close message and then
    			// waiting (with timeout) for the server to close the connection.
    			err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
    			if err != nil {
    				log.Println("write close:", err)
    				return
    			}
    			select {
    			case <-done:
    			case <-time.After(time.Second):
    			}
    			return
    		}
    	}
    

    go func() 的 c.ReadMessage 不停的从socket里面读取数据并且打印。
    for 循环不停的从1秒的定时器里面读取时间,写到socket

    server.go

    	for {
    		mt, message, err := c.ReadMessage()
    		if err != nil {
    			log.Println("read:", err)
    			break
    		}
    		log.Printf("recv: %s", message)
    		err = c.WriteMessage(mt, message)
    		if err != nil {
    			log.Println("write:", err)
    			break
    		}
    	}
    

    for c.ReadMessage 循环不停的从 socket读取数据,把数据打印之后,又写给客户端。
    这是大致的websocket 客户端与服务端通信的简单例子。

    改成自定义的协议

    我们使用二进制数据流,自定义的协议是这样的,
    先是无符号的 uint 占4个字节,表示行为逻辑,比如111-获取信息, 110- 加好友等等
    后面是具体的数据,跟HTTP请求的GET或者POST参数类似,具体数据格式定义好就行,比如可以使用JSON数据、可以使用RPC定义好的数据格式

    修改完之后 client.go 代码是这样的

    	for {
    		select {
    		case <-done:
    			return
    		case <-ticker.C:
    			buf := bytes.NewBuffer([]byte{})
    			binary.Write(buf, binary.BigEndian, uint32(110))
    			binary.Write(buf, binary.BigEndian, []byte("我们都好"))
    			err := c.WriteMessage(websocket.BinaryMessage, buf.Bytes())
    			if err != nil {
    				log.Println("write:", err)
    				return
    			}
    		case <-interrupt:
    			log.Println("interrupt")
    
    			// Cleanly close the connection by sending a close message and then
    			// waiting (with timeout) for the server to close the connection.
    			err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
    			if err != nil {
    				log.Println("write close:", err)
    				return
    			}
    			select {
    			case <-done:
    			case <-time.After(time.Second):
    			}
    			return
    		}
    	}
    

    主要是这个

    buf := bytes.NewBuffer([]byte{})
    binary.Write(buf, binary.BigEndian, uint32(110))
    binary.Write(buf, binary.BigEndian, []byte("我们都好"))
    

    创建一个bytes Buffer
    写入一个uint32,占4个字节,表示行为逻辑
    写入行为逻辑具体的数据
    以上采用大端通信

    修改之后的 server.go

    	for {
    		mt, message, err := c.ReadMessage()
    		if err != nil {
    			log.Println("read:", err)
    			break
    		}
    		log.Println("messagetype:%d", mt)
    
    		log.Println("messagelen:%d", len(message))
    
    		log.Println("messagetype:%d", binary.BigEndian.Uint32(message[0:4]))
    		
    		log.Println("recv: %s", string(message[4:]))
    		err = c.WriteMessage(mt, message)
    		if err != nil {
    			log.Println("write:", err)
    			break
    		}
    	}
    

    mt websocket 类型
    TextMessage = 1 文本类型传传输
    BinaryMessage = 2 字节类型传输

    从socket里面获取到message之后
    先取前4个字节,表示逻辑行为
    再取剩余的字节,表示行为逻辑需要的参数

    我们重新执行一下,server.go 和 client.go

    go run client.go
    connecting to ws://localhost:8080/echo
    recv: n我们都好
    recv: n我们都好
    
    
    go run server.go
    messagetype:%d 2
    messagelen:%d 16
    messagetype:%d 110
    recv: %s 我们都好
    

    messagetype=2表示二进制通信
    message长度是16,前四个字节是无符号整形,四个汉字占12个字节,一共16个字节,无符号uint是110表示加好友。

    这就是一个简单自定义协议的socket服务。

    如果有需要登录鉴权的话,在server.go中可以从r *http.Request 获取header或者cookie,进行登录鉴权验证。

    生产的部署

    因为是 websocket 协议,如果有代理服务器的话,需要在各代理服务器上的Nginx做协议升级,将HTTP 升级为 websocket ,比如下面的代理服务器。

    map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
    }
    
    
    server {
        listen 80;
        location /serv/ {
    
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
    
            proxy_connect_timeout 3;
            proxy_redirect     off;
            proxy_read_timeout 3600;
            proxy_send_timeout 3600;
    
            proxy_pass http://push_web/serv/;
        }
    }
    
  • 相关阅读:
    JVM进阶(2)
    Java---刷题01
    找不到x3daudio1_7.dll的解决方法,快速修复x3daudio1_7.dll缺失问题
    SQL面试常见问题总结指南
    2022锦江行——非繁城品:疫情之下,存量酒店的突围之道
    免费配音软件哪个好?这里有你想知道的答案
    为什么要写单测
    DNS、HTTP与HTTPS
    【ARM】(1)架构简介
    【触想智能】安卓工业平板电脑选购注意事项以及安装方式分析
  • 原文地址:https://www.cnblogs.com/feixiangmanon/p/18222685