golang开发 gorilla websocket的使用

golang,gorilla,websocket · 浏览次数 : 7

小编点评

```go package main import ( "bytes" "context" "fmt" "io" "github.com/gin-gonic/gin" ) // websocket 服务端 func server(c context.Context) { // 定义行为逻辑 var ( msgType int message []byte ) // 读取消息 for { message, err := c.ReadMessage() if err != nil { if err == io.EOF { return } fmt.Printf("read: %v\n", err) return } // 解析消息类型 switch msgType { case 1: // 处理文本消息 msgType, message = message[4:] fmt.Printf("recv: %s\n", string(message)) case 2: // 处理二进制消息 // 获取数据长度 length := int(message[0:4]) // 获取数据 msgType = 2 message = message[4:] fmt.Printf("recv: %s\n", string(message)) } } } // client 客户端 func client(c context.Context) { // 定义行为逻辑 var ( buf bytes.Buffer message []byte ) // 构造无符号整形数据 buf.Write([]byte{110}) // 表示加好友 // 发送消息 for { message, err := c.WriteMessage(1, buf.Bytes()) if err != nil { if err == io.EOF { return } fmt.Printf("write: %v\n", err) return } // 获取回复消息 message, err := c.ReadMessage() if err != nil { if err == io.EOF { return } fmt.Printf("read: %v\n", err) return } // 打印接收的消息类型和长度 fmt.Printf("messagetype:%d 2messagelen:%d 16messagetype:%d 110recv: %s\n", msgType, len(message), msgType, 110) // 发送回复消息 buf.Write(message) } } ```

正文

很多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/;
    }
}

与golang开发 gorilla websocket的使用相似的内容:

golang开发 gorilla websocket的使用

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

[转帖]运维面试高频知识点

https://www.cnblogs.com/apink/p/16304036.html Docker 什么是docker? docker是基于Golang语言进行开发的一个开源应用容器引擎,换言之就是运行在宿主机上的一个sandboxed process,但是它与宿主机的其它进程是相互隔离的,是

golang 实现四层负载均衡

> 大家好,我是蓝胖子,做开发的同学应该经常听到过负载均衡的概念,今天我们就来实现一个乞丐版的四层负载均衡,并用它对mysql进行负载均衡测试,通过本篇你可以了解到零拷贝的应用,四层负载均衡的本质以及实践。 本文代码已经上传到github ```shell https://github.com/Ho

20个Golang片段让我不再健忘

本文使用代码片段的形式来解释在 go 语言开发中经常遇到的小功能点,由于本人主要使用 java 开发,因此会与其作比较,希望对大家有所帮助。

摸鱼快报:golang net/http中的雕虫小技

以后会开一个板块,摸鱼快报,快速记录这几周开发中雕虫小技。 1. 向开发环境localhost:3000种植cookie 前端使用Create React App脚手架,默认以localhost:3000端口启动; 后端使用golang-gin框架,使用8034端口启动。 登录模块走的是sso,前后

Golang 切片作为函数参数传递的陷阱与解答

作者:林冠宏 / 指尖下的幽灵。转载者,请: 务必标明出处。 GitHub : https://github.com/af913337456/ 出版的书籍: 《1.0-区块链DApp开发实战》 《2.0-区块链DApp开发:基于公链》 例子 切片作为函数参数传递的是值 用来误导切片作为函数参数传递的

[转帖]goproxy的设置

goproxy.io 是全球最早的 Go modules 镜像代理服务之一 【大陆地区建议使用 proxy.golang.com.cn】,采用 CDN 加速服务为开发者提供依赖下载, 该服务由一批热爱开源, 热爱 Go 语言的年轻人开发维护。从 Go 1.11 开始 Go 语言开始支持 Go mod

[golang]在Gin框架中使用JWT鉴权

什么是JWT JWT,全称 JSON Web Token,是一种开放标准(RFC 7519),用于安全地在双方之间传递信息。尤其适用于身份验证和授权场景。JWT 的设计允许信息在各方之间安全地、 compactly(紧凑地)传输,因为其自身包含了所有需要的认证信息,从而减少了需要查询数据库或会话存储

Go pprof 认知到实践

快速开始 测试环境:go version go1.22.2 windows/amd64,源代码开源在 https://github.com/oldme-git/teach-study/tree/master/golang/base/pprof 在正式开始之前,请确保安装 graphviz,这一步不可

golang pprof监控系列(2) —— memory,block,mutex 使用

golang pprof监控系列(2) —— memory,block,mutex 使用 大家好,我是蓝胖子。 profile的中文被翻译轮廓,对于计算机程序而言,抛开业务逻辑不谈,它的轮廓是是啥呢?不就是cpu,内存,各种阻塞开销,线程,协程概况 这些运行指标或环境。golang语言自带了工具库来