go tcp客户端自动重连_使用 Go 语言创建 WebSocket 服务

今天介绍如何用 Go 语言创建 WebSocket 服务,文章的前两部分简要介绍了 WebSocket 协议以及用 Go 标准库如何创建 WebSocket 服务。第三部分实践环节我们使用了 gorilla/websocket 库帮助我们快速构建 WebSocket 服务,它帮封装了使用 Go 标准库实现 WebSocket 服务相关的基础逻辑,让我们能从繁琐的底层代码中解脱出来,根据业务需求快速构建 WebSocket 服务。

Go Web 编程系列的每篇文章的源代码都打了对应版本的软件包,供大家参考。公众号中回复 gohttp10 获取本文源代码

WebSocket介绍

WebSocket 通信协议通过单个 TCP 连接提供全双工通信通道。与 HTTP 相比, WebSocket 不需要你为了获得响应而发送请求。它允许双向数据流,因此您只需等待服务器发送的消息即可。当 Websocket 可用时,它将向您发送一条消息。对于需要连续数据交换的服务(例如即时通讯程序,在线游戏和实时交易系统), WebSocket 是一个很好的解决方案。 WebSocket 连接由浏览器请求,并由服务器响应,然后建立连接,此过程通常称为握手。 WebSocket 中的特殊标头仅需要浏览器与服务器之间的一次握手即可建立连接,该连接将在其整个生命周期内保持活动状态。 WebSocket 解决了许多实时 Web 开发的难题,并且与传统的 HTTP 相比,具有许多优点:

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

WebSocket协议实现起来相对简单。它使用 HTTP 协议进行初始握手。握手成功后即建立连接, WebSocket 实质上使用原始 TCP 读取/写入数据。

5d94b1dcf9de45d5851255a189ce1380

客户端请求如下所示:

GET /chat HTTP/1.1    Host: server.example.com    Upgrade: websocket    Connection: Upgrade    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==    Sec-WebSocket-Protocol: chat, superchat    Sec-WebSocket-Version: 13    Origin: http://example.com

这是服务器响应:

HTTP/1.1 101 Switching Protocols    Upgrade: websocket    Connection: Upgrade    Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=    Sec-WebSocket-Protocol: chat

如何在Go中创建WebSocket应用

要基于Go 语言内置的 net/http 库编写 WebSocket 服务器,你需要:

  • 发起握手
  • 从客户端接收数据帧
  • 发送数据帧给客户端
  • 关闭握手

发起握手

首先,让我们创建一个带有 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 劫持( hijack )。通过劫持,可以接管基础的 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 header    func (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"))      .....}

客户端发起 WebSocket 连接请求时用的 Sec-WebSocket-key 是随机生成的,并且是Base64编码的。接受请求后,服务器需要将此密钥附加到固定字符串。假设秘钥是 x3JJHMbDL1EzLkh9GBhXDw== 。在这个例子中,可以使用 SHA-1 计算二进制值,并使用 Base64 对其进行编码。得到 HSmrc0sMlYUkAGmm5OPpG2HaGWk= 。然后使用它作为 Sec-WebSocket-Accept 响应头的值。

传输数据帧

握手成功完成后,您的应用程序可以从客户端读取数据或向客户端写入数据。WebSocket规范定义了一个客户机和服务器之间使用的特定帧格式。这是框架的位模式:

986e7ba9805644db8d7c4bf3b9bbec7a

图:传输数据帧的位模式

使用以下代码对客户端有效负载进行解码:

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

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

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

关闭握手

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

// Close sends a close frame and closes the TCP connectionfunc (ws *Ws) Close() error {    f := Frame{}    f.Opcode = 8    f.Length = 2    f.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服务

通过上面的章节可以看到用 Go 自带的 net/http 库实现 WebSocket 服务还是太复杂了。好在有很多对 WebSocket 支持良好的第三方库,能减少我们很多底层的编码工作。这里我们使用 gorilla web toolkit 家族的另外一个库 gorilla/websocket 来实现我们的 WebSocket 服务,构建一个简单的 Echo 服务( echo 意思是回音,就是客户端发什么,服务端再把消息发回给客户端)。

我们在 http_demo 项目的 handler 目录下新建一个 ws 子目录用来存放 WebSocket 服务相关的路由对应的请求处理程序。

增加两个路由:

  • /ws/echo echo 应用的 WebSocket 服务的路由。
  • /ws/echo_display echo 应用的客户端页面的路由。

创建WebSocket服务端

// handler/ws/echo.gopackage wsimport (    "fmt"    "github.com/gorilla/websocket"    "net/http")var upgrader = websocket.Upgrader{    ReadBufferSize:  1024,    WriteBufferSize: 1024,}func EchoMessage(w http.ResponseWriter, r *http.Request) {    conn, _ := upgrader.Upgrade(w, r, nil) // 实际应用时记得做错误处理    for {        // 读取客户端的消息        msgType, msg, err := conn.ReadMessage()        if err != nil {            return        }        // 把消息打印到标准输出        fmt.Printf("%s sent: %s", conn.RemoteAddr(), string(msg))        // 把消息写回客户端,完成回音        if err = conn.WriteMessage(msgType, msg); err != nil {            return        }    }}
  • conn 变量的类型是 *websocket.Conn , websocket.Conn 类型用来表示 WebSocket 连接。服务器应用程序从 HTTP 请求处理程序调用 Upgrader.Upgrade 方法以获取 *websocket.Conn
  • 调用连接的 WriteMessage 和 ReadMessage 方法发送和接收消息。上面的 msg 接收到后在下面又回传给了客户端。 msg 的类型是 []byte 。

创建WebSocket客户端

前端页面路由对应的请求处理程序如下,直接返回 views/websockets.html 给到浏览器渲染页面即可。

// handler/ws/echo_display.gopackage wsimport "net/http"func DisplayEcho(w http.ResponseWriter, r *http.Request) {    http.ServeFile(w, r, "views/websockets.html")}

websocket.html 里我们需要用 JavaScript 连接 WebScoket 服务进行收发消息,篇幅原因我就只贴 JS 代码了,完整的代码通过本节的口令去公众号就能获取到下载链接。

    Send    
......

注册路由

服务端和客户端的程序都准备好后,我们按照之前约定好的路径为他们注册路由和对应的请求处理程序:

// router/router.gofunc RegisterRoutes(r *mux.Router) {    ...    wsRouter := r.PathPrefix("/ws").Subrouter()    wsRouter.HandleFunc("/echo", ws.EchoMessage)    wsRouter.HandleFunc("/echo_display", ws.DisplayEcho)}

测试验证

重启服务后访问 http://localhost:8000/ws/echo_display ,在输入框中输入任何消息都能再次回显到浏览器中。

eb3ffc133a7f4f34a970f04d15f58da2

图片

服务端则是把收到的消息打印到终端中然后把调用 writeMessage 把消息再回传给客户端,可以在终端中查看到记录。

ec0e87d2f0504e80b4236fa1bc974757

总结

WebSocket 在现在更新频繁的应用中使用非常广泛,进行 WebSocket 编程也是我们需要掌握的一项必备技能。文章的实践练习稍微简单了一些,也没有做错误和安全性检查。主要是为了讲清楚大概的流程。关于 gorilla/websocket 更多的细节在使用时还需要查看官方文档才行。

参考链接:

how-to-build-websockets-in-go

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/571792.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux系统终端more,一篇文章让你学透Linux系统中的more命令

Linux系统下有很多实用工具可以让你在终端界面查看文本文件。其中一个就是 more。more 跟我之前另一篇文章里写到的工具 —— less 很相似。它们之间的主要不同点在于 more 只允许你向前查看文件。尽管它能提供的功能看起来很有限,不过它依旧有很多有用的特性值得你…

python项目开发案例集锦_在线分享 | 在 VS Code 中一站式完成 Python 项目开发

往期活动回顾VS Code 中文社区自成立以来,已经举办了4场活动:Workshop | First Step to VS Code 基础篇 Workshop | First Step to VS Code 进阶篇 在线沙龙 | 程序员职业生涯如何不迷茫 干货回顾 | VS Cod…

jvm--Garbage Collection

垃圾回收(GC)一直是java语言的重中之重。 1 对象状态鉴别 1.1 标记对象是否可回收一般有两种算法: 引用计数算法:给每个对象添加一个引用计数器,当引用一次时1,当引用时效时-1,当计数器为0时即可…

linux安装mongo卸载mongo,CentOS7安装及卸载MongoDB.md

安装 MongoDB 社区版本配置 yum 包管理系统创建 /etc/yum.repos.d/mongodb-org-4.2.repo 文件,这样你就可以使用 yum 安装 MongoDB,文件内容如下:123456[mongodb-org-4.2]nameMongoDB Repositorybaseurlhttps://repo.mongodb.org/yum/redhat/…

程序固化到优盘中_将Windows 8/10 系统装进优盘

介绍Windows To Go技术Windows 有一项相当吸引人的神奇功能——“Windows To Go”,它可以让你将 Windows 完整安装到U盘、移动硬盘等便携设备上,并且能随处在不同的电脑硬件上直接运行,让系统可以随身携带!简单说来,这…

如何获得物体的主要方向?

问题来源为网友提供的资料,原文地址为:《Object Orientation, Principal Component Analysis & OpenCV》 问题描述:对于这样的图像(2副,采用了背投光),如何获得上面工件的主要方向主要思路&…

Linux的开源免费办公软件,开源免费Office办公套件(LibreOffice)

LibreOffice是一款全面的开源免费Office办公套件,软件拥有强大的数据导入和导出功能,能直接导入 PDF 文档、微软 Works、LotusWord,支持主要的 OpenXML 格式。软件本身并不局限于 Debian 和Ubuntu 平台,支持 Windows、Mac、PRM pa…

ubuntu安装ftp_如何在 Ubuntu 20.04 上安装 Webmin

本文最先发布在: 如何在 Ubuntu 20.04 上安装 Webmin​www.itcoder.techWebmin 是一个开源控制面板,它允许你通过简单易用的 Web 界面,就可以管理你的 Linux 服务器。它允许你管理用户,组,磁盘配额,创建文件…

c++ auto用法_不想写表达式的类型?试试auto吧

作者:守望,Linux应用开发者,目前在公众号【编程珠玑】 分享Linux/C/C/数据结构与算法/工具等原创技术文章和学习资源。前言你以为我说的自动变量类型auto吗?非也,我们知道C语言中其实也有auto关键字,它和早…

浅谈内存映射I/O(MMIO)与端口映射I/O(PMIO)的区别

最近在看NVMeDirect和SPDK的源码,觉得有必要梳理一下MMIO和PMIO的区别。关于MMIO和PMIO,维基百科上是这么讲滴, Memory-mapped I/O (MMIO) and port-mapped I/O (PMIO) (which is also called isolated I/O) are two complementary methods o…

linux sftp密码错误,linux个别用户sftp坏掉,验证密码后卡住, 大概是什么问题?...

问题描述linux个别用户sftp坏掉,验证密码后卡住, 大概是什么问题?所有采用sftp的软件都不能用了winsshfs 点击mount后就卡住xftp 连接验证结束后也卡住sublimeText3 的sftp插件也不好用了突然发生的情况之前一直用着都没有问题一直正常使用 只…

c语言 结构体_C语言 技能提升 系列文章 (三)结构体

今天,来跟大家聊一聊C语言中的结构体。在C语言的各种数据类型中,结构体最特别,因为它是可以被程序员定义的,它的特点是非常的灵活。定义struct defined_name{type_name field_name;};结构体内部的成员可以是任意类型的数据&#x…

基于@FeignClient注解实现两个微服务之间接口的调用(简单)

场景需求:微服务A中的接口input需要调用微服务B中接口的output数据。 实现:使用feign实现即可。 微服务B中的接口: 步骤一:微服务A中编写一个接口,该接口就是调用微服务B的接口;需要在接口上添加FeignClien…

spring boot 自动跳转登录页面_徒手撸一个扫码登录示例工程

徒手撸一个扫码登录示例工程不知道是不是微信的原因,现在出现扫码登录的场景越来越多了,作为一个有追求、有理想新四好码农,当然得紧跟时代的潮流,得徒手撸一个以儆效尤本篇示例工程,主要用到以下技术栈qrcode-plugin&…

inputstreamreader未关闭会导致oom_ThreadLocal 一定会导致内存泄露?

在面试的时候,ThreadLocal作为高并发常用工具经常会被问到。而面试官比较喜欢问的问题有以下两个:1、ThreadLocal是怎么实现来保证每个线程的变量副本的。2、ThreadLocal的内存泄露是怎么产生的,怎么避免内存泄露。首先我们来看第一个问题&am…

keras保存模型_TF2 8.模型保存与加载

举个例子:先训练出一个模型import 接下来第一种方法:只保留模型的参数:这个有2种方法:model.save_weights("adasd.h5")model.load_weights("adasd.h5") model.predict(x_test)model.save_weights(./checkpoin…

第一章 Burp Suite 安装和环境配置

Burp Suite是一个集成化的渗透测试工具,它集合了多种渗透测试组件,使我们自动化地或手工地能更好的完成对web应用的渗透测试和攻击。在渗透测试中,我们使用Burp Suite将使得测试工作变得更加容易和方便,即使在不需要娴熟的技巧的情…

mysql57服务无法启动_将mysqld.service服务加入到systemctl

在开始安装二进制MySQL的时候感觉都还挺好,就是在启动服务的时候比较麻烦,一开始是在Centos6下的感觉也没有什么费劲的;但是在Centos7下面还是有点不太适应,不过还好用用就熟悉了;说明一下,我的安装目录在/usr/local/m…

linux raid autodetect,软raid的建立

1 增加磁盘并分区(修改id)fdisk /dev/sdbCommand (m for help): pDisk /dev/sdb: 8589 MB, 8589934592 bytes255 heads, 63 sectors/track, 1044 cylindersUnits cylinders of 16065 * 512 8225280 bytesDevice Boot Start End Blocks Id System/dev/sd…

c语言for循环的省略写法,C语言两种for循环写法分析

每个C程序员都知道同一个for循环语句可以有两种写法:A: for (i 0; i B: for (i cnt; i > 0; i--){ }前几天,DEBUG的时候, 发现采用A写法的代码反汇编出来有BUG.当时没有时间记录,环境也没有保存下来.今天尝试重现,又没来出现上次的问题...很奇怪.很久很久以前也听说过这两…