使用 Go 语言实现 WebSocket的核心逻辑
文章目录
- WebSocket 简介
- 时序图
- 核心逻辑
- Client 结构与功能
- 创建新客户端
- 消息读取逻辑 (ReadPump)
- 发送消息逻辑 (Send)
- 客户端管理器 (ClientManager)
- WebSocket 处理器
- 处理心跳与长连接
- 总结
本文将基于 Go 语言,通过使用 gorilla/websocket
库来实现一个简单的聊天应用。该应用具备处理 WebSocket 连接、消息传输、以及用户连接管理等功能。我们将详细展示如何实现这些功能,并剖析背后的核心逻辑与原理。
WebSocket 简介
WebSocket 是一种全双工的通信协议,允许客户端和服务器之间在一个持久连接上进行双向数据传输。与 HTTP 的短连接不同,WebSocket 可以在建立连接后保持连接状态,从而实现实时通信。因此,WebSocket 非常适合用于聊天应用等需要实时数据传输的场景。
时序图
核心逻辑
在本示例中,我们主要实现了以下几个核心模块:
- Client:表示单个 WebSocket 连接的客户端,负责处理消息的收发。
- ClientManager:用于管理多个客户端的连接,处理客户端的增加、删除以及消息的路由。
- WebSocket 处理逻辑:处理新连接的建立、消息的读取与发送。
Client 结构与功能
type Client struct {conn *websocket.ConnmessageQueue chan []bytemu sync.Mutexuser string
}
Client
结构体用于表示一个 WebSocket 客户端连接。每个客户端包含:
conn
:当前的 WebSocket 连接。messageQueue
:用于存储待发送的消息队列。mu
:用于保证并发安全的互斥锁。user
:表示客户端的用户标识。
创建新客户端
func NewClient(user string, conn *websocket.Conn) *Client {return &Client{conn: conn,user: user,messageQueue: make(chan []byte, 100),}
}
NewClient
函数用于创建新的客户端实例。每个客户端都有一个独立的消息队列,用于存储要发送给客户端的消息。
消息读取逻辑 (ReadPump)
func (c *Client) ReadPump() {defer func() {c.conn.Close()}()for {mt, message, err := c.conn.ReadMessage()if err != nil {log.Println("read:", err)manager.mu.Lock()delete(manager.clients, c.user)_ = c.conn.Close()manager.mu.Unlock()break}if mt == websocket.TextMessage || mt == websocket.PingMessage {c.mu.Lock()c.messageQueue <- messagec.mu.Unlock()}}
}
ReadPump
方法用于持续从 WebSocket 连接中读取消息,并将接收到的消息存储到 messageQueue
队列中。该方法通过一个无限循环,不断读取 WebSocket 的消息。当出现错误时,例如客户端断开连接,便会关闭当前连接并将该客户端从客户端管理器中移除。
其中,ReadMessage()
方法用于从 WebSocket 连接中读取消息,返回的 mt
表示消息类型。常见的类型包括文本消息(TextMessage
)和 ping 消息(PingMessage
)。对于这些消息类型,消息会被推送到 messageQueue
以便后续处理。
发送消息逻辑 (Send)
func Send(user string, returnMessage []byte, logger logx.Logger) {manager.mu.RLock()client, exists := manager.clients[user]manager.mu.RUnlock()if !exists {logger.Infof("client not found for user:%s message:%s", user, string(returnMessage))return}client.mu.Lock()err := client.conn.WriteMessage(websocket.TextMessage, returnMessage)client.mu.Unlock()if err != nil {logger.Errorf("client.conn.WriteMessage error %s", err.Error())manager.mu.Lock()delete(manager.clients, user)manager.mu.Unlock()_ = client.conn.Close()}
}
Send
函数负责向指定的用户发送消息。首先,它会检查用户是否存在于 ClientManager
中,如果不存在则记录日志并返回。如果用户存在,则通过 WriteMessage()
方法将消息发送给客户端。若发送消息时发生错误,会将该用户从连接管理器中移除,并关闭该 WebSocket 连接。
客户端管理器 (ClientManager)
type ClientManager struct {clients map[string]*Clientmu sync.RWMutex
}var manager = ClientManager{clients: make(map[string]*Client),
}
ClientManager
用于管理多个客户端的连接,clients
字段是一个存储所有客户端连接的映射,键是用户标识,值是客户端对象。通过读写锁 (sync.RWMutex
),确保在并发访问时的线程安全。
WebSocket 处理器
ChatWebsocketHandler
是处理 WebSocket 连接的 HTTP 处理函数。
func ChatWebsocketHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {conn, err := upgrader.Upgrade(w, r, nil)logger := logx.WithContext(r.Context())if err != nil {logger.Errorf("upgrade:%+v", err)return}user := r.URL.Query().Get("user")if user == "" {logger.Errorf("user is empty:")_ = conn.Close()return}client := NewClient(user, conn)manager.mu.Lock()oldClient, exists := manager.clients[user]if exists {_ = oldClient.conn.Close()}manager.clients[user] = clientmanager.mu.Unlock()go client.ReadPump()// 省略其他消息处理逻辑...}
}
- 连接升级:首先使用
upgrader.Upgrade()
将 HTTP 请求升级为 WebSocket 连接。 - 用户认证:通过 URL 查询参数获取用户 ID,并创建对应的
Client
。 - 旧连接处理:如果该用户已经有一个旧的 WebSocket 连接,则会关闭旧连接。
- 启动消息读取:通过启动
ReadPump()
协程,持续读取该用户的 WebSocket 消息。
处理心跳与长连接
在 WebSocket 通信中,维持长连接的一个常用做法是使用心跳机制。
if req.Heartbeat {// 处理心跳消息err = client.conn.WriteMessage(websocket.PongMessage, []byte(""))if err != nil {logger.Errorf("write pong message failed:", err)manager.mu.Lock()delete(manager.clients, user)manager.mu.Unlock()_ = client.conn.Close()return}
}
每当接收到心跳消息时,服务器会返回一个 PongMessage
,以维持连接的活跃状态。如果发送 PongMessage
失败,服务器会关闭该客户端连接。
总结
本文详细展示了如何使用 Go 语言实现一个 WebSocket 聊天应用的核心逻辑。我们讨论了客户端的创建与管理、消息的收发、以及长连接的维持等关键功能。通过这些核心组件,我们可以轻松地扩展功能,构建复杂的 WebSocket 应用。
WebSocket 实现的关键在于良好的连接管理和消息处理机制,这样可以确保在高并发情况下仍然能维持高效且稳定的实时通信。
相关文章:

使用 Go 语言实现 WebSocket的核心逻辑
文章目录 WebSocket 简介时序图核心逻辑Client 结构与功能创建新客户端消息读取逻辑 (ReadPump)发送消息逻辑 (Send)客户端管理器 (ClientManager)WebSocket 处理器处理心跳与长连接 总结 本文将基于 Go 语言,通过使用 gorilla/websocket 库来实现一个简单的聊天应用…...

Linux下的杀毒软件介绍
Linux下的杀毒软件介绍 一、Linux杀毒软件的基本概念和作用二、Linux杀毒软件的选择三、Linux杀毒软件推荐四、Linux杀毒软件对应用进程的影响五、结论在当今数字化和网络化的环境中,保护计算机系统的安全至关重要。尽管Linux操作系统因其开源、稳定且相对安全的特性而较少受到…...
JSONP详解
JSONP(JSON with Padding)是一种非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过JavaScript callback的形式实现跨域访问。以下是对JSONP的详细解释: 一、JSONP的背景与原理 背景: 由于浏…...

Leetcode—1115. 交替打印 FooBar【中等】(多线程)
2024每日刷题(180) Leetcode—1115. 交替打印 FooBar C实现代码 class FooBar { private:int n;sem_t fooSem;sem_t barSem;public:FooBar(int n) {this->n n;sem_init(&fooSem, 0, 1);sem_init(&barSem, 0, 0);}~FooBar() {sem_destroy(&…...

Visual Studio Code基础:使用debugpy调试python程序
相关阅读 VS codehttps://blog.csdn.net/weixin_45791458/category_12658212.html?spm1001.2014.3001.5482 一、安装调试器插件 在VS code中可以很轻松地调试Python程序,首先需要安装Python调试器插件,如图1所示。 图1 安装调试器插件 Python Debugge…...

超全!一文详解大型语言模型的11种微调方法
导读:大型预训练模型是一种在大规模语料库上预先训练的深度学习模型,它们可以通过在大量无标注数据上进行训练来学习通用语言表示,并在各种下游任务中进行微调和迁移。随着模型参数规模的扩大,微调和推理阶段的资源消耗也在增加。…...
C 主要函数解析
1、fseek 函数 int fseek(FILE *stream, long offset, int fromwhere); 第一个参数stream为文件指针 第二个参数offset为偏移量,正数表示正向偏移,负数表示负向偏移 第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEE…...

vue3学习:数字时钟遇到的两个问题
在前端开发学习中,用JavaScript脚本写个数字时钟是很常见的案例,也没什么难度。今天有时间,于是就用Vue的方式来实现这个功能。原本以为是件非常容易的事,没想到却卡在两个问题上,一个问题通过别人的博文已经找到答案&…...

吴恩达深度学习笔记:卷积神经网络(Foundations of Convolutional Neural Networks)3.7-3.8
目录 第四门课 卷积神经网络(Convolutional Neural Networks)第三周 目标检测(Object detection)3.7 非极大值抑制(Non-max suppression)3.8 Anchor Boxes 第四门课 卷积神经网络(Convolutional…...

【Linux】最基本的字符设备驱动
前面我们介绍到怎么编译出内核模块.ko文件,然后还加载了这个驱动模块。但是,那个驱动代码还不完善,驱动写好后怎么在应用层使用也没有介绍。 字符设备抽象 Linux内核中将字符设备抽象成一个具体的数据结构(struct cdevÿ…...

利用 Llama 3.1模型 + Dify开源LLM应用开发平台,在你的Windows环境中搭建一套AI工作流
文章目录 1. 什么是Ollama?2. 什么是Dify?3. 下载Ollama4. 安装Ollama5. Ollama Model library模型库6. 本地部署Llama 3.1模型7. 安装Docker Desktop8. 使用Docker-Compose部署Dify9. 注册Dify账号10. 集成本地部署的 Llama 3.1模型11. 集成智谱AI大模型…...
Docker常用命令分享二
docker的用户组管理过程: 1、sudo : 可以让普通用户临时获得root用户的权限,来新建docker用户组 2、普通用户并没有使用sudo的权限 3、先要让root用户把testing用户加入到sudoers的授权文件中 4、sudoers的文件居然是只读的,先解决这个问…...

【一步步开发AI运动小程序】二十、AI运动小程序如何适配相机全屏模式?
引言 受小程序camera组件预览和抽帧图像不一致的特性影响,一直未全功能支持全屏模式,详见本系列文件第四节小程序如何抽帧;随着插件在云上赛事、健身锻炼、AI体测、AR互动场景的深入应用,各开发者迫切的希望能在全屏模式下应用&am…...
[Java基础] 运算符
[Java基础] 基本数据类型 [Java基础] Java HashMap 的数据结构和底层原理 目录 算术运算符 比较运算符 逻辑运算符 位运算符 赋值运算符 其他运算符 常见面试题 Java语言支持哪些类型的运算符? 请解释逻辑运算符&&和&的区别? 请解释条件运…...

[001-02-018].第05节:数据类型及类型转换
我的后端学习大纲 我的Java学习大纲 1、数据类型介绍: 1.0.计算机存储单位: 1.1.基本数据类型介绍: a.整型:byte、short、int、long 1.整型包括:byte、short、int、long,可如下图方式类比记忆࿱…...

Netty基础
Netty基础 一级目录I/O请求基础知识Netty如何实现自己的I/O模型 网络框架的选型 Netty整体架构Netty逻辑处理架构网络通信层事件调度层服务编排层 组件关系梳理Netty源码结构 netty是目前最流行的一款高性能java网络编程框架,广泛使用于中间件、直播、社交、游戏等领…...
602,好友申请二:谁有最多的好友
好友申请二:谁有最多的好友 实现 with tmp as (selectrequester_id idfrom RequestAcceptedunion allselectaccepter_id idfrom RequestAccepted )selectid,count(*) num from tmp group by id order by num desc limit 1;...
【Matlab算法MATLAB实现的音频信号时频分析与可视化(附MATLAB完整代码)
MATLAB实现的音频信号时频分析与可视化 前言正文:时频分析实现原理代码实现代码运行结果图及说明结果图:结果说明:总结前言 音频信号的时频分析是信号处理领域中的一个重要研究方向。它允许我们同时观察信号在时间和频率域的特性,为音频处理、语音识别、音乐分析等应用提供…...

界面耻辱纪念堂--可视元素03
更多的迹象表明,关于在程序里使用新的动态界面元素,微软的态度是不确定的,其中一个是仅仅需要对比一下Office97 里的“Coolbars”和“标准工具条”。Coolbar 按钮直到用户指针通过的时候才成为按钮(否则是平的)。 工具…...

国产龙芯处理器选择迅为2K1000开发板有资料
硬件配置国产龙芯处理器,双核64位系统,板载2G DDR3内存,流畅运行Busybox、Buildroot、Loognix、QT5.12 系统!接口全板载4路USB HOST、2路千兆以太网、2路UART、2路CAN总线、Mini PCIE、SATA固态盘接口、4G接口、GPS接口WIF1、蓝牙、Mini HDMI…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

云安全与网络安全:核心区别与协同作用解析
在数字化转型的浪潮中,云安全与网络安全作为信息安全的两大支柱,常被混淆但本质不同。本文将从概念、责任分工、技术手段、威胁类型等维度深入解析两者的差异,并探讨它们的协同作用。 一、核心区别 定义与范围 网络安全:聚焦于保…...
32位寻址与64位寻址
32位寻址与64位寻址 32位寻址是什么? 32位寻址是指计算机的CPU、内存或总线系统使用32位二进制数来标识和访问内存中的存储单元(地址),其核心含义与能力如下: 1. 核心定义 地址位宽:CPU或内存控制器用32位…...