使用 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…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...
