从HTTP到WebSocket:Golang中WebSocket连接的实现与应用

news/2025/3/13 18:23:51/文章来源:https://www.cnblogs.com/twh233/p/18770535

一、WebSocket协议简介

WebSocket是一种计算机通信协议,提供全双工通信渠道,通过单个TCP连接实现。WebSocket最大的特点是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话。与HTTP协议不同的是:
  • WebSocket只需要一次握手,就可以建立持久连接
  • 建立连接后,客户端和服务器可以随时互相发送数据
  • 数据格式轻量,开销小

二、WebSocket连接建立过程

WebSocket连接是通过HTTP升级机制建立的,具体步骤如下:
  1. 客户端发起连接请求:客户端发送一个普通的HTTP请求,但在请求头中包含特殊字段
  1. 服务器进行协议升级:服务器检查请求头,确认是WebSocket握手请求
  1. 连接建立成功:服务器返回101状态码(Switching Protocols),双方开始使用WebSocket协议通信

三、服务器端实现(基于Golang)

以下是一个WebSocket服务器的实现示例(基于提供的代码):

package gnetimport ("github.com/gorilla/websocket""net""net/http""sync/atomic"
)// WSAcceptor 结构体用于接受和管理WebSocket连接
type WSAcceptor struct {host     string          // 主机地址port     string          // 端口号listener net.Listener    // 网络监听器factory  HandlerFactory  // 处理器工厂,用于创建连接处理器codec    *Codec          // 编解码器,用于处理消息的编码和解码stop     chan bool       // 停止信号通道ccu      int32           // 当前连接用户数
}// Start 方法启动WebSocket接收器
func (acceptor *WSAcceptor) Start(port string, factory HandlerFactory, isGzip bool, maxIncomingPacket uint32) error {acceptor.codec = NewCodec(isGzip, maxIncomingPacket)acceptor.factory = factoryacceptor.host = ""acceptor.port = portaddress := net.JoinHostPort("", port)listener, err := net.Listen("tcp", address)if err != nil {return err}acceptor.listener = listener// 启动接受连接的循环go acceptor.startAcceptLoop()return nil
}// startAcceptLoop 方法开始接受连接的循环
func (acceptor *WSAcceptor) startAcceptLoop() {http.HandleFunc("/", acceptor.wsHandler)if err := http.Serve(acceptor.listener, nil); err != nil {panic(err)}
}// wsHandler 处理WebSocket连接请求
func (acceptor *WSAcceptor) wsHandler(w http.ResponseWriter, r *http.Request) {select {case <-acceptor.stop:returndefault:// 创建WebSocket升级器upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {return true // 允许所有来源的WebSocket连接},}// 将HTTP连接升级为WebSocket连接conn, err := upgrader.Upgrade(w, r, nil)if err != nil {return}// 处理WebSocket连接wsConn := NewWSConn(conn, acceptor.codec, acceptor.stop)wsConn.delegate = acceptor.factory(wsConn)go func() {defer atomic.AddInt32(&acceptor.ccu, -1)atomic.AddInt32(&acceptor.ccu, 1)wsConn.Start()}()}
}// Stop 方法停止WebSocket接收器
func (acceptor *WSAcceptor) Stop() {close(acceptor.stop)_ = acceptor.listener.Close()
}// GetCcu 方法返回当前连接用户数
func (acceptor *WSAcceptor) GetCcu() int32 {return atomic.LoadInt32(&acceptor.ccu)
}

代码解析

  1. WSAcceptor结构体:
  • 负责接受和管理WebSocket连接
  • 维护监听器、处理器工厂等组件
  1. Start方法:
  • 初始化监听器和其他组件
  • 启动接受连接的循环
  1. wsHandler方法:
  • 处理HTTP请求并升级为WebSocket连接
  • 使用upgrader.Upgrade()函数完成协议升级
  • 创建新的连接对象并启动处理

四、客户端实现(Golang版本)

以下是一个与上述服务器配套的Golang客户端示例:

package mainimport ("fmt""github.com/gorilla/websocket""log""net/url""os""os/signal""time"
)func main() {// 构建WebSocket URLu := url.URL{Scheme: "ws", Host: "localhost:8080", Path: "/"}// 记录尝试连接的日志log.Printf("连接到 %s", u.String())// 建立WebSocket连接conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)if err != nil {log.Fatal("连接失败:", err)}defer conn.Close()// 处理中断信号interrupt := make(chan os.Signal, 1)signal.Notify(interrupt, os.Interrupt)// 创建用于接收消息的通道done := make(chan struct{})// 启动读取消息的goroutinego func() {defer close(done)for {// 读取消息_, message, err := conn.ReadMessage()if err != nil {log.Println("读取错误:", err)return}log.Printf("收到消息: %s", message)}}()// 创建定时发送消息的tickerticker := time.NewTicker(time.Second)defer ticker.Stop()// 消息计数器msgCount := 0// 主循环for {select {case <-done:returncase <-ticker.C:msgCount++message := fmt.Sprintf("Hello %d", msgCount)// 发送消息err := conn.WriteMessage(websocket.TextMessage, []byte(message))if err != nil {log.Println("写入错误:", err)return}log.Printf("发送消息: %s", message)case <-interrupt:log.Println("收到中断信号,关闭连接...")// 关闭WebSocket连接err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))if err != nil {log.Println("写入关闭消息错误:", err)return}// 等待服务器确认关闭select {case <-done:case <-time.After(time.Second):}return}}
}

客户端代码解析

  1. 连接建立:
  • 使用websocket.DefaultDialer.Dial函数建立WebSocket连接
  • 连接成功后返回连接对象
  1. 消息处理:
  • 使用goroutine异步读取消息
  • 使用定时器定期发送消息
  1. 连接关闭:
  • 监听中断信号
  • 发送正常关闭的消息
  • 优雅地关闭连接

五、连接建立详解

1. HTTP升级为WebSocket的请求头

客户端发送的HTTP请求头包含:

ET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

2. 服务器响应头

服务器返回的响应头包含:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

3. 连接升级过程

在服务器代码中,关键的升级过程由以下代码完成:

upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {return true},
}
conn, err := upgrader.Upgrade(w, r, nil)

这个过程中:

  1. 服务器检查客户端的HTTP请求是否包含正确的WebSocket握手头部
  1. 服务器生成响应头并发送101状态码
  1. TCP连接从HTTP协议切换到WebSocket协议

六、实践建议与注意事项

1. 安全性考虑

在示例代码中,CheckOrigin函数设置为允许所有来源的连接,这在生产环境中不安全。应该根据实际需求限制允许连接的来源:

upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {// 检查Origin是否在允许的列表中origin := r.Header.Get("Origin")return isOriginAllowed(origin)},
}

2. 心跳机制

WebSocket连接可能因为网络问题或防火墙设置而断开,因此应该实现心跳机制:

// 服务器端
func (conn *WSConn) startPingPong() {ticker := time.NewTicker(30 * time.Second)defer ticker.Stop()for {select {case <-ticker.C:if err := conn.conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(time.Second)); err != nil {return}case <-conn.stop:return}}
}// 客户端
conn.SetPingHandler(func(string) error {conn.WriteControl(websocket.PongMessage, []byte{}, time.Now().Add(time.Second))return nil
})

3. 错误处理与重连

客户端应当处理连接断开的情况,并尝试重新连接:

func connectWithRetry(urlStr string, maxRetries int) (*websocket.Conn, error) {var conn *websocket.Connvar err errorfor i := 0; i < maxRetries; i++ {conn, _, err = websocket.DefaultDialer.Dial(urlStr, nil)if err == nil {return conn, nil}log.Printf("连接失败,%d秒后重试: %v", i+1, err)time.Sleep(time.Duration(i+1) * time.Second)}return nil, fmt.Errorf("达到最大重试次数,连接失败: %v", err)
}

七、总结

WebSocket连接建立是通过HTTP协议升级机制完成的,这一过程包括:

  1. 客户端发送带有特定请求头的HTTP请求
  1. 服务器验证请求头并返回101状态码
  1. TCP连接从HTTP协议切换到WebSocket协议

一旦连接建立,客户端和服务器就可以通过这个全双工连接自由地互相发送消息,而不必等待对方的响应。这使得WebSocket特别适用于需要实时通信的应用,如聊天、游戏、实时数据更新等场景。通过本文提供的Golang服务器和客户端示例,你可以快速搭建自己的WebSocket应用,实现实时双向通信功能。希望这篇博客能帮助你在将来快速回忆WebSocket的工作原理和实现方式!

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

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

相关文章

[算法学习记录] 并查集(附例题)

并查集简介 并查集是一种重要的数据结构,主要用于实现节点之间的合并查询操作(例如判断两个节点是否属于同一个连通块(共享同一个父节点的节点组成的集合叫连通块)),在解决不相交集合时有很大的用处;并查集同样常用于处理无向图,来描述接点的连通性,在初始化时,每个节…

Electron 进程间通信(IPC)方法详解

Electron 是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架,它是基于 Chromium 和 Node.js 构建的,而 Chromium 本身是采用多进程架构的,所以 Electron 也是多进程的。 Electron 是一个多进程框架,它的进程主要分为两类:主进程(Main Process) 和 渲染进程(R…

从零开始的web前端学习-JavaScript

JavaScript 是一种运行在客户端(浏览器)的编程语言,实现人机互动效果:网页特效(监听用户的某些行为并令网页进行反馈) 表单验证(针对表单数据的合法性进行判断) 数据交互(获取后台数据并渲染到前端)JavaScript 组成ECMAScript:基础语法核心 Web APIs:DOM(页面文档…

【Azure Service Bus】分享使用 Python Service Bus SDK 输出SDK内操作日志

问题描述 使用Python代码消费Service Bus中的消息,默认情况 Console 中的信息都是通过 print 打印输出。 有时候需要调查更多SDK中的日志,那么如何才能让SDK输出更多的日志呢?问题解答 方法就是引入 Logging SDK,然后再初始化 ServiceBusClient 对象时,设置logging_enabl…

nvm和nodejs安装

nvm和nodejs安装安装 nvm 全名 node.js version management,顾名思义是一个nodejs的版本管理工具。通过它可以安装和切换不同版本的nodejs。首先下载安装包,可以用GitHub上的,可以有点看,也可以用一些镜像,然后点击安装一直下一步即可。 然后打开命令行,可以用nvm -v指令…

dify文件上传到http节点

dify系统上传sys.files变量是Array[File]类型,由于 HTTP 请求节点不支持 Array[File] 上传,需要单独处理每个文件,以下是实现此功能的步骤: 添加迭代节点 迭代输入选sys.files 输出选http请求body http请求body类型选form-data 键值选迭代的item.File 本文使用dify版本为0.…

可行性分析(第五组)

目录 第1章 系统分析 1.1 可行性分析 1.1.1 技术可行性分析 1.1.2 经济可行性分析 1.1.3 社会可行性分析 1.1.4 法律可行性分析 1.2 系统流程分析 1.2.1 系统开发总流程 1.2.2 登录流程 1.2.3 系统操作流程 1.2.4 系统性能分析 第1章 可行性分析 1.1可行性分析 下面分别从技术可…

C# 子窗体中调用父窗体中的方法(或多窗体之间方法调用)

看似一个简单的功能需求,其实很多初学者处理不好的,很多朋友会这么写:C# Code://父窗体是是frmParent,子窗体是frmChildA //在父窗体中打开子窗体 frmChildA child = new frmChildA(); child.MdiParent = this; child.Show();//子窗体调父窗体方法: //错误的调用!!!!!!!! …

Qt HTTP模块——调用API对话DeepSeek

HTTP模块 Qt的网络模块(QtNetwork)支持HTTP/HTTPS协议,提供异步、非阻塞的API,实现客户端与服务器之间的 HTTP 请求与响应交互。核心类:QNetworkAccessManager:负责协调网络操作(如GET/POST请求),管理请求队列和返回的响应。 QNetworkRequest:封装HTTP请求的详细信息…

Linux下环境变量

Linux打印环境变量: echo $PATH

GitLearning

创建新仓库 创建新文件夹,打开,然后执行 git init创建新的 git 仓库 (也可以直接 git clone 远程仓库) git clone /path/to/repository git clone username@host:/path/to/repository工作流 本地仓库由 git 维护的三棵树。第一个是工作目录,它持有实际文件;第二个是暂存区(…