golang开发 gorilla websocket的使用

  • golang开发 gorilla websocket的使用已关闭评论
  • 50 次浏览
  • A+
所属分类:linux技术
摘要

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