翻译自:How to Use Websockets in Golang
微信公众号:运维开发故事,作者:wanger

在不刷新页面的情况下发送消息并获得即时响应是我们认为理所当然的事情。但在过去,启用实时功能对开发人员来说是一个真正的挑战。开发者社区已经从 HTTP 长轮询和 AJAX 走了很长一段路,终于找到了构建真正实时应用程序的解决方案。
该解决方案以 WebSockets 的形式出现,它可以在用户的浏览器和服务器之间打开交互式会话。WebSockets 允许浏览器向服务器发送消息并接收事件驱动的响应,而无需轮询服务器以获取回复。
目前,WebSockets 是构建实时应用程序的首选解决方案:在线游戏、即时通讯工具、跟踪应用程序等。本指南解释了 WebSockets 的运行方式,并展示了我们如何使用 Go 编程语言构建 WebSocket 应用程序。

网络套接字与 WebSockets

网络套接字

网络套接字,或简称为套接字,用作内部端点,用于在运行在同一台计算机或同一网络上的不同计算机上的应用程序之间交换数据。
套接字是基于 Unix 和 Windows 的操作系统的关键部分,它们使开发人员可以更轻松地创建支持网络的软件。应用程序开发人员可以在他们的程序中包含套接字,而不是从头开始构建网络连接。由于网络套接字用于多种网络协议(HTTP、FTP 等),因此可以同时使用多个套接字。
套接字是由套接字的应用程序编程接口 ( API )定义的一组函数调用创建和使用的。
有几种类型的网络套接字:
数据报套接字(SOCK_DGRAM),也称为无连接套接字,使用用户数据报协议 (UDP)。数据报套接字支持双向消息流并保留记录边界。
流套接字(SOCK_STREAM),也称为面向连接的套接字,使用传输控制协议 (TCP)、流控制传输协议 (SCTP) 或数据报拥塞控制协议 (DCCP)。这些套接字提供双向、可靠、有序和不重复的数据流,没有记录边界。
原始套接字(或原始 IP 套接字)通常在路由器和其他网络设备中可用。这些套接字通常是面向数据报的,尽管它们的确切特性取决于协议提供的接口。大多数应用程序不使用原始套接字。提供它们是为了支持新通信协议的开发,并提供对现有协议更深奥的设施的访问。

套接字通信

每个网络套接字都由地址标识,地址是传输协议、IP 地址和端口号的三元组。
主机之间的通信主要有两种协议:TCP 和 UDP。

  • 连接到 TCP 套接字

Go 客户端使用 net 包中的 DialTCP 函数来建立 TCP 连接。DialTCP 返回一个 TCPConn 对象。建立连接后,客户端和服务器开始交换数据:客户端通过 TCPConn 对象向服务器发送请求,服务器解析请求并发送响应,TCPConn 对象接收来自服务器的响应。
![](https://img-blog.csdnimg.cn/img_convert/3feac07f16b12e7233aa9609795d72fe.png#align=left&display=inline&height=895&id=u3f2cee8a&margin=[object Object]&originHeight=895&originWidth=800&status=done&style=none&width=800)此连接一直有效,直到客户端或服务器关闭它。创建连接的函数如下:
客户端:


// inittcpAddr, err := net.ResolveTCPAddr(resolver, serverAddr)if err != nil {// handle error}conn, err := net.DialTCP(network, nil, tcpAddr)if err != nil {// handle error}// send message_, err = conn.Write({message})if err != nil {// handle error}// receive messagevar buf [{buffSize}]byte_, err := conn.Read(buf[0:])if err != nil {// handle error}

服务器端:


// inittcpAddr, err := net.ResolveTCPAddr(resolver, serverAddr)if err != nil {// handle error}listener, err := net.ListenTCP("tcp", tcpAddr)if err != nil {// handle error}// listen for an incoming connectionconn, err := listener.Accept()if err != nil {// handle error}// send messageif _, err := conn.Write({message}); err != nil {// handle error}    // receive messagebuf := make([]byte, 512)n, err := conn.Read(buf[0:])if err != nil {// handle error}
  • 连接到 UDP 套接字

与 TCP 套接字相反,使用 UDP 套接字,客户端只向服务器发送数据报。没有 Accept 函数,因为服务器不需要接受连接,只需等待数据报到达。
![](https://img-blog.csdnimg.cn/img_convert/9d9452f3e84b1e681c4b0dea58b4e189.png#align=left&display=inline&height=800&id=ue680a09f&margin=[object Object]&originHeight=800&originWidth=800&status=done&style=none&width=800)其他 TCP 功能与 UDP 对应;只需在上面的函数中用 UDP 替换 TCP。
客户端:


// initraddr, err := net.ResolveUDPAddr("udp", address)if err != nil {// handle error}conn, err := net.DialUDP("udp", nil, raddr)if err != nil {// handle error}....... // send messagebuffer := make([]byte, maxBufferSize)n, addr, err := conn.ReadFrom(buffer)if err != nil {// handle error}.......            // receive messagebuffer := make([]byte, maxBufferSize)n, err = conn.WriteTo(buffer[:n], addr)if err != nil {// handle error}

服务器端:

 // initudpAddr, err := net.ResolveUDPAddr(resolver, serverAddr)if err != nil {// handle error}conn, err := net.ListenUDP("udp", udpAddr)if err != nil {// handle error}.......// send messagebuffer := make([]byte, maxBufferSize)n, addr, err := conn.ReadFromUDP(buffer)if err != nil {// handle error}.......// receive messagebuffer := make([]byte, maxBufferSize)n, err = conn.WriteToUDP(buffer[:n], addr)if err != nil {// handle error}

什么是 WebSocket

WebSocket 通信包通过单个 TCP 连接提供全双工通信通道。这意味着客户端和服务器都可以在需要时同时发送数据而无需任何请求。
WebSockets 是需要持续数据交换的服务的一个很好的解决方案——例如,即时通讯、在线游戏和实时交易系统。可以在 Internet 工程任务组 (IETF) RFC 6455 规范 中找到有关 WebSocket 协议的完整信息。

WebSocket 连接由浏览器请求并由服务器响应,然后建立连接。这个过程通常称为握手。WebSockets 中的特殊类型的标头只需要浏览器和服务器之间的一次握手即可建立连接,该连接将在其整个生命周期内保持活动状态。
WebSocket 协议使用端口 80 进行不安全连接,使用端口 443 进行安全连接。WebSocket规范 确定 ws (WebSocket) 和 wss (WebSocket Secure) 协议需要哪些统一的资源标识符方案。
WebSockets 解决了开发实时 Web 应用程序的许多令人头疼的问题,并且与传统 HTTP 相比有几个好处:

  • 轻量级报头减少了数据传输开销。
  • 单个 Web 客户端只需要一个 TCP 连接。
  • WebSocket 服务器可以将数据推送到 Web 客户端。

![](https://img-blog.csdnimg.cn/img_convert/dde2e8d53a3906d30ac9c6088cbc9830.png#align=left&display=inline&height=405&id=u842f845f&margin=[object Object]&originHeight=405&originWidth=800&status=done&style=none&width=800)
WebSocket 协议实现起来比较简单。它使用 HTTP 协议进行初始握手。成功握手后,连接建立,WebSocket 本质上使用原始 TCP 来读/写数据。
这是客户端请求的样子:

 GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==Sec-WebSocket-Protocol: chat, superchatSec-WebSocket-Version: 13Origin: http://example.com

这是服务器响应:

    HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=Sec-WebSocket-Protocol: chat

如何在 Go 中创建 WebSocket 应用程序

要基于 net/http 库编写一个简单的 WebSocket 回显服务器,需要:

  1. 发起握手
  2. 从客户端接收数据帧
  3. 向客户端发送数据帧
  4. 关闭握手

首先,创建一个带有 WebSocket 端点的 HTTP 处理程序:


// HTTP server with WebSocket endpointfunc Server() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {ws, err := NewHandler(w, r)if err != nil {// handle error}if err = ws.Handshake(); err != nil {// handle error}

然后初始化 WebSocket 结构。
初始握手请求始终来自客户端。一旦服务器定义了一个 WebSocket 请求,它需要用一个握手响应来回复。
不能使用 http.ResponseWriter 编写响应,因为一旦开始发送响应,它将关闭底层 TCP 连接。
可以使用HTTP劫持。http劫持接管底层 TCP 连接处理程序和 bufio.Writer。这可以在不关闭 TCP 连接的情况下读取和写入数据。


// NewHandler initializes a new handlerfunc NewHandler(w http.ResponseWriter, req *http.Request) (*WS, error) {hj, ok := w.(http.Hijacker)if !ok {// handle error}
}

要完成握手,服务器必须使用适当的标头进行响应。


// Handshake creates a handshake headerfunc (ws *WS) Handshake() error {hash := func(key string) string {h := sha1.New()h.Write([]byte(key))h.Write([]byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))return base64.StdEncoding.EncodeToString(h.Sum(nil))}(ws.header.Get("Sec-WebSocket-Key")).....
}

“Sec-WebSocket-key”随机生成并且是Base64编码的。服务器接受请求后需要将此键附加到固定字符串。假设你有x3JJHMbDL1EzLkh9GBhXDw== key. 可以使用 SHA-1 来计算二进制值并使用 Base64 对其进行编码。可以得到HSmrc0sMlYUkAGmm5OPpG2HaGWk=。将此用作Sec-WebSocket-Accept响应标头的值。

传输数据帧

握手成功完成后,应用程序可以从客户端读取数据和向客户端写入数据。所述WebSocket规范定义了的一个客户机和一个服务器之间使用的特定帧格式。这是帧的位模式:
![](https://img-blog.csdnimg.cn/img_convert/40fc4446c40e29ba82349460810ad971.png#align=left&display=inline&height=480&id=u3d4e8755&margin=[object Object]&originHeight=480&originWidth=800&status=done&style=none&width=800)
使用以下代码解码客户端负载:


// Recv receives data and returns a Framefunc (ws *WS) Recv() (frame Frame, _ error) {frame = Frame{}head, err := ws.read(2)if err != nil {// handle error}

反过来,这些代码行允许对数据进行编码:


// Send sends a Framefunc (ws *WS) Send(fr Frame) error {// make a slice of bytes of length 2data := make([]byte, 2)// Save fragmentation & opcode information in the first bytedata[0] = 0x80 | fr.Opcodeif fr.IsFragment {data[0] &= 0x7F}.....

结束握手

当一方发送具有关闭状态的关闭帧作为有效载荷时,握手关闭。发送关闭帧的一方可以在有效载荷中发送关闭原因。如果关闭是由客户端发起的,服务器应该发送一个相应的关闭帧作为响应。

// Close sends a close frame and closes the TCP connection
func (ws *Ws) Close() error {f := Frame{}f.Opcode = 8f.Length = 2f.Payload = make([]byte, 2)binary.BigEndian.PutUint16(f.Payload, ws.status)if err := ws.Send(f); err != nil {return err}return ws.conn.Close()

WebSocket 库列表

有几个第三方库可以简化开发人员的工作并极大地促进 WebSocket 的使用。

STDLIB ( x/net/websocket )

这个 WebSocket 库是标准 Go 库的一部分。它为 WebSocket 协议实现了客户端和服务器,如 RFC 6455 规范中所述。它不需要安装并且有很好的官方文档。另一方面,它仍然缺少一些可以在其他 WebSocket 库中找到的功能。/x/net/websocket 包中的 Golang WebSocket 实现不允许用户以明确的方式重用连接之间的 I/O 缓冲区。
首先,要安装和使用这个库,需要添加这行代码:

import "golang.org/x/net/websocket"

客户端:

// create connection// schema can be ws:// or wss://// host, port – WebSocket serverconn, err := websocket.Dial("{schema}://{host}:{port}", "", op.Origin)if err != nil {// handle error} defer conn.Close().......// send messageif err = websocket.JSON.Send(conn, {message}); err != nil {// handle error}.......// receive message// messageType initializes some type of messagemessage := messageType{}if err := websocket.JSON.Receive(conn, &message); err != nil {// handle error}  .......

服务器端:

// Initialize WebSocket handler + servermux := http.NewServeMux()mux.Handle("/", websocket.Handler(func(conn *websocket.Conn) {func() {for {// do something, receive, send, etc.}}.......    // receive message// messageType initializes some type of messagemessage := messageType{}if err := websocket.JSON.Receive(conn, &message); err != nil {// handle error}.......     // send messageif err := websocket.JSON.Send(conn, message); err != nil {// handle error}    ........

Gorilla

Gorilla Web 工具包中的 WebSocket 包拥有完整且经过测试的 WebSocket 协议实现以及稳定的包 API。WebSocket 包的文档齐全且易于使用。可以在 Gorilla 官方网站上查看文档。
安装:


go get github.com/gorilla/websocket
Examples of code
Client side:// init// schema – can be ws:// or wss://// host, port – WebSocket server  u := url.URL{Scheme: {schema},Host:   {host}:{port},Path:   "/",}c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)if err != nil {// handle error}.......    // send messageerr := c.WriteMessage(websocket.TextMessage, {message})if err != nil {// handle error}  .......      // receive message_, message, err := c.ReadMessage()if err != nil {// handle error}.......

服务器端:

// initu := websocket.Upgrader{}c, err := u.Upgrade(w, r, nil)if err != nil {// handle error}.......    // receive messagemessageType, message, err := c.ReadMessage()if err != nil {// handle error}.......       // send messageerr = c.WriteMessage(messageType, {message})if err != nil {// handle error}.......

Gobwas

这个微小的 WebSocket 包具有强大的功能列表,例如零拷贝升级和允许构建自定义数据包处理逻辑的低级 API。Gobwas 在 I/O 期间不需要中间分配。它还拥有 wsutil 包中 API 的高级包装器和帮助器,允许开发人员快速启动,而无需深入研究协议的内部。
查看 GoDoc 网站以获取文档。
通过包含以下代码行来安装 Gobwas:

go get github.com/gobwas/ws

客户端:

// init// schema – can be ws or wss// host, port – ws serverconn, _, _, err := ws.DefaultDialer.Dial(ctx, {schema}://{host}:{port})if err != nil {// handle error}.......// send messageerr = wsutil.WriteClientMessage(conn, ws.OpText, {message})if err != nil {// handle error}.......// receive message    msg, _, err := wsutil.ReadServerData(conn)if err != nil {// handle error}.......

服务器端:

// initlistener, err := net.Listen("tcp", op.Port)if err != nil {// handle error}conn, err := listener.Accept()if err != nil {// handle error}upgrader := ws.Upgrader{}if _, err = upgrader.Upgrade(conn); err != nil {// handle error}.......// receive messagefor { reader := wsutil.NewReader(conn, ws.StateServerSide)_, err := reader.NextFrame()if err != nil {// handle error}data, err := ioutil.ReadAll(reader)if err != nil {// handle error}.......}   .......// send messagemsg := "new server message"if err := wsutil.WriteServerText(conn, {message}); err != nil {// handle error}.......

GOWebsockets

该工具提供了广泛的易于使用的功能。它允许并发控制、数据压缩和设置请求头。GOWebsockets 支持用于发送和接收文本和二进制数据的代理和子协议。开发人员还可以启用或禁用 SSL 验证。
在GoDoc 网站和项目的GitHub 页面上可以找到有关如何使用 GOWebsockets 的文档和示例。通过添加以下代码行来安装包:

go get github.com/sacOO7/gowebsocket

客户端:

// init// schema – can be ws or wss// host, port – ws serversocket := gowebsocket.New({schema}://{host}:{port})socket.Connect().......  // send messagesocket.SendText({message})orsocket.SendBinary({message}).......// receive messagesocket.OnTextMessage = func(message string, socket gowebsocket.Socket) {// hande received message};orsocket.OnBinaryMessage = func(data [] byte, socket gowebsocket.Socket) {// hande received message};  .......

服务器端:

// init// schema – can be ws or wss// host, port – ws serverconn, _, _, err := ws.DefaultDialer.Dial(ctx, {schema}://{host}:{port})if err != nil {// handle error}.......        // send messageerr = wsutil.WriteClientMessage(conn, ws.OpText, {message})if err != nil {// handle error}.......       // receive message    msg, _, err := wsutil.ReadServerData(conn)if err != nil {// handle error}

nhooyr.io/websocket

还有一个常用的websocket库是nhooyr.io/websocket,关于这个库,煎鱼大佬在自己的书(Go语言编程之旅)中介绍很多,包括与其他库的一些比较,写的是很全面的,这里可以看一下书的电子版地址https://golang2.eddycjy.com/posts/ch4/02-protocol/

比较现有的解决方案

我们已经描述了用于 Golang 的四个最广泛使用的 WebSocket 库。下表包含这些工具的详细比较。
![](https://img-blog.csdnimg.cn/img_convert/a357807c5c934acc38fbea27c3752480.png#align=left&display=inline&height=600&id=u84d41f5c&margin=[object Object]&originHeight=600&originWidth=800&status=done&style=none&width=800)为了更好地分析它们的性能,还进行了几个基准测试。**
结果如下:
![](https://img-blog.csdnimg.cn/img_convert/b7b15b721c20e9d960acc63a0ec7c636.png#align=left&display=inline&height=624&id=udc045b07&margin=[object Object]&originHeight=624&originWidth=800&status=done&style=none&width=800)

  • Gobwas 与其他库相比具有显着优势。它每个操作的分配更少,每次分配使用的内存和时间更少。此外,它的 I/O 分配为零。此外,Gobwas 拥有创建 WebSocket 客户端-服务器交互和接收消息片段所需的所有方法。还可以使用它轻松处理 TCP 套接字。
  • 如果感觉Gobwas不合适,你可以使用 Gorilla。它非常简单,并且具有几乎所有相同的功能。也可以使用 STDLIB,但它在生产环境中没有那么好,因为它缺少许多必要的功能,并且正如在基准测试中看到的那样,提供的性能较弱。GOWebsocket 与 STDLIB 大致相同。但是如果你需要快速构建一个原型或者MVP,它可以是一个合理的选择。

Golang中用到的的Websocket库相关推荐

  1. 哪个websocket库与Node.js一起使用? [关闭]

    本文翻译自:Which websocket library to use with Node.js? [closed] Currently there is a plethora of websock ...

  2. iOS项目中用到的一些第三方库

    今天来总结一下项目中用到的一些第三方库. 1. AFNetworking,在github上有3万多颗的星星,用作处理网络请求. 2. MZGuidePages, 这是一个小工具,用于创建首次使用app ...

  3. c语言标准库 SOCKET,[转载] 基于C/C++的WebSocket库

    libwebsockets 简介 libwebsockets 是一个纯 C 语言的轻量级 WebSocket库,它的 CPU.内存占用很小,同时支持作为服务器端/客户端.其特性包括:支持 ws:// ...

  4. go-dongle 0.2.0 版本发布了,一个轻量级、语义化的 golang 编码解码、加密解密库

    dongle 是一个轻量级.语义化.对开发者友好的 Golang 编码解码和加密解密库 Dongle 已被 awesome-go 收录, 如果您觉得不错,请给个 star 吧 github.com/g ...

  5. 推荐两个go语言的websocket库

    最近在写一个需要前后端保持通信的服务.前端要能及时感知后端数据的变化,后端要及时处理前端发过来的指令.这种服务就需要用到websocket了. 以前在写websocket相关的程序时,一直在用gori ...

  6. 当在 终端 中用 npm 安装 Vant 组件库时,发生“npm ERR code ERESOLVE ;npm ERRERESOLVE could not resolve;”报错时,该怎么办?

    出现的问题: 当在 终端 中用 npm 安装 Vant 组件库时,发生 npm ERR! code ERESOLVE: npm ERR! ERESOLVE could not resolve:报错时, ...

  7. [Go实战]golang使用mysql实例和第三方库Gendry

    导入对应的包 // 安装 $ go get github.com/go-sql-driver/mysql// 导入 import ("database/sql"_ "th ...

  8. golang中文文档_Golang 标准库 限流器 time/rate 设计与实现

    限流器是后台服务中十分重要的组件,在实际的业务场景中使用居多,其设计在微服务.网关.和一些后台服务中会经常遇到.限流器的作用是用来限制其请求的速率,保护后台响应服务,以免服务过载导致服务不可用现象出现 ...

  9. iOS开发中用到的一些第三方库

    下面是我在开发中用到的一些优秀的iOS第三方开源库: 1.AFNetworking(网络请求,类似的还有ASIHTTPRequest)    https://github.com/AFNetworki ...

最新文章

  1. Windows API一日一练(56)SetEndOfFile和GetFileSizeEx函数
  2. HelloSilverlight
  3. rust tpa_Rust(腐蚀)怎么tp求大神指教。请写在下面
  4. 8-[函数]-嵌套函数,匿名函数,高阶函数
  5. 動態修改SiteMapPath路徑
  6. 基于JAVA+SpringMVC+MYSQL的球队管理系统
  7. jQuery1.3以上版本@的问题
  8. saltstack学习笔记
  9. 常用PAM模块--完全笔记
  10. ZOJ 1076 Gene Assembly
  11. linux配置环境变量宏,在linux中配置环境变量(示例代码)
  12. 手机万能摄像头ip搜索工具_一款 APP,130 多种功能,让你的手机秒变万能工具箱...
  13. strcmp函数用法
  14. javascript 闭包理解总结
  15. 矩阵卷积运算的具体过程,很简单
  16. django 数据库配置
  17. 简历javaweb项目描述怎么写_JavaWeb开发简历项目经验怎么写
  18. 三维重建笔记_基于图像的大规模场景三维建模overview
  19. 医学图像论文要点记录
  20. Percona(MySQL)安装

热门文章

  1. 2022开源社区app源码多端圈子社区论坛系统
  2. 超炫的html5擦除效果,超炫html5效果代码(需浏览器支持)
  3. 如何把一张pdf分成多个?一个pdf怎么分成若干个pdf?
  4. 基于STM32+0.96寸OLED - - 7脚SPI接线显示+代码解析
  5. Boost电路SX1308单电源转双电源输出低成本Sepic+Cuk方案
  6. excel里的一个单元格怎样拆分成几个单元格?
  7. 【高并发】- 指标介绍
  8. 计算机地理绘图软件叫什么,地理教师如何选择理想的绘图软件 ──基于对常用绘图软件的比较与分析...
  9. 第五章:正则表达式的使用-常用的正则符号(二)
  10. 安徽工贸职业技术学院计算机比赛,放飞青春,不负韶华!《追梦》——安徽工贸职业技术学院2019年宣传片...