Go 即时通讯系统:客户端与服务端 WebSocket 通信交互

article/2025/8/6 5:40:41

客户端和服务端的交互

客户端与服务端建立连接

  • 客户端:客户端通过浏览器或者其他应用程序发起一个 HTTP 请求到服务端的 /socket.io 路径。在请求中会携带用户的 UUID 作为参数(通过 c.Query("user") 获取)。
// router/socket.go
func RunSocket(c *gin.Context) {user := c.Query("user") // 获取用户标识if user == "" {return // 无用户标识则拒绝连接}log.Info("newUser", log.String("newUser", user))// 升级 HTTP 连接为 WebSocket 连接ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)if err != nil {return}// 创建客户端对象client := &server.Client{Name: user,Conn: ws,Send: make(chan []byte),}// 注册客户端到服务器server.MyServer.Register <- client// 启动读写协程go client.Read()go client.Write()
}
  • 服务端:服务端接收到请求后,使用 websocket.Upgrader 将 HTTP 连接升级为 WebSocket 连接。然后创建一个 Client 实例,并将其发送到 ServerRegister 通道。

客户端注册到服务端

// internal/server/server.go
func (s *Server) Start() {for {select {// 处理客户端注册case conn := <-s.Register:log.Info("login", log.String("login", conn.Name))s.Clients[conn.Name] = connmsg := &protocol.Message{From:    "System",To:      conn.Name,Content: "welcome!",}protoMsg, _ := proto.Marshal(msg) // 序列化为字节切片conn.Send <- protoMsg}}
}
  • 客户端:无特定操作,等待服务端响应。
  • 服务端:服务端的 Start 方法会监听 Register 通道,当有新的客户端注册时,将客户端信息保存到 Clients 映射中,并向客户端发送欢迎消息。

客户端发送消息到服务端

  • 客户端:客户端通过 Client 结构体的 Read 方法从 WebSocket 连接读取消息。如果是心跳消息,客户端会发送 Pong 响应;否则,根据配置将消息发送到 Kafka 或者服务端的 Broadcast 通道。
// internal/server/client.go
func (c *Client) Read() {// 消息读取for {c.Conn.PongHandler()_, message, err := c.Conn.ReadMessage()if err != nil {log.Error("client read message error", log.Err(err))MyServer.Ungister <- cc.Conn.Close()break}msg := &protocol.Message{}proto.Unmarshal(message, msg)// 处理心跳消息if msg.Type == constant.HEAT_BEAT {pong := &protocol.Message{Content: constant.PONG,Type:    constant.HEAT_BEAT,}pongBytes, err2 := proto.Marshal(pong)if err2 != nil {log.Error("client marshal message error", log.Err(err))}c.Conn.WriteMessage(websocket.BinaryMessage, pongBytes)} else {MyServer.Broadcast <- message}}
}
  • 服务端:服务端的 Start 方法会监听 Broadcast 通道,当接收到消息时,根据消息的类型(单聊、群聊)将消息转发给相应的客户端。
// internal/server/server.go
func (s *Server) Start() {for {select {// 处理消息广播case message := <-s.Broadcast:msg := &protocol.Message{}proto.Unmarshal(message, msg)if msg.To != "" {// 有指定接收者的消息处理if msg.ContentType >= constant.TEXT && msg.ContentType <= constant.VIDEO {// 一般消息,比如文本消息,视频文件消息等_, exists := s.Clients[msg.From]if exists { // 检查发送者是否在连接列表中saveMessage(msg)}if msg.ContentType == constant.MESSAGE_TYPE_USER {// 单人消息处理s.sendUserMessage(msg)} else if msg.ContentType == constant.MESSAGE_TYPE_GROUP {// 多人消息处理s.sendGroupMessage(msg)} else {// 语音电话,视频电话等,仅支持单人聊天,不支持群聊// 不保存文件,直接进行转发client, ok := s.Clients[msg.To]if ok {client.Send <- message}}} else {// 无指定接收者的广播消息处理for id, conn := range s.Clients {log.Info("allUser", log.String("allUser", id))select {case conn.Send <- message: // 发送消息给客户端,成功继续处理default: // 失败关闭客户端close(conn.Send)delete(s.Clients, conn.Name)}}}}}}
}

服务端发送消息到客户端

  • 服务端:服务端将消息发送到客户端的 Send 通道。
// internal/server/server.go
client.Send <- msgByteclient.Send <- message
  • 客户端:客户端的 Write 方法会监听 Send 通道,当接收到消息时,将消息通过 WebSocket 连接发送给客户端。
// internal/server/client.go
func (c *Client) Write() {defer func() {c.Conn.Close()}()for message := range c.Send {c.Conn.WriteMessage(websocket.BinaryMessage, message)}
}

客户端断开连接

  • 客户端:无特定操作,等待服务端响应。
  • 服务端:服务端的 Start 方法会监听 Ungister 通道,当有客户端断开连接时,将客户端信息从 Clients 映射中删除,并关闭客户端的 Send 通道。
// internal/server/server.go
func (s *Server) Start() {for {// 处理客户端注销case conn := <-s.Ungister:log.Info("loginout", log.String("loginout", conn.Name))if _, ok := s.Clients[conn.Name]; ok {close(conn.Send)delete(s.Clients, conn.Name)}}
}

客户端和服务端通过 WebSocket 连接进行实时通信,通过 RegisterUngisterBroadcast 通道进行客户端的注册、注销和消息广播,通过客户端的 Send 通道进行消息的发送。整个交互过程基于 Go 语言的协程和通道机制,实现了高效、并发的通信。

代码地址:server.go,client.go

言的协程和通道机制,实现了高效、并发的通信。

代码地址:server.go,client.go


http://www.hkcw.cn/article/RoguolxXrH.shtml

相关文章

Python 训练营打卡 Day 41

简单CNN 一、数据预处理 在图像数据预处理环节&#xff0c;为提升数据多样性&#xff0c;可采用数据增强&#xff08;数据增广&#xff09;策略。该策略通常不改变单次训练的样本总数&#xff0c;而是通过对现有图像进行多样化变换&#xff0c;使每次训练输入的样本呈现更丰富…

什么是模块化设计?模块和微服务是一样?

软件的模块化设计和微服务是两种不同层次的概念&#xff0c;它们有相似之处但并非等同。以下是详细解释&#xff1a; 一、软件的模块化设计&#xff08;Modular Design&#xff09; 定义 模块化设计是指将一个复杂的软件系统拆分为多个相对独立的模块&#xff08;Module&…

基于千帆大模型的AI体检报告解读系统实战:使用OSS与PDFBox实现PDF内容识别

目录 说明 前言 需求 流程说明 表结构说明 整体流程 百度智能云 注册和实名认证 创建应用 费用说明 大模型API说明 集成大模型 设计Prompt 上传体检报告 读取PDF内容 功能实现 智能评测 抽取大模型工具 功能实现 总结 说明 AI体检报告解读、病例小结或者…

PySide6 GUI 学习笔记——常用类及控件使用方法(标签控件QLabel)

文章目录 标签控件QLabel及其应用举例标签控件QLabel的常用方法及信号应用举例Python 代码示例1Python 代码示例2 小结 标签控件QLabel及其应用举例 QLabel 是 PySide6.QtWidgets 模块中的一个控件&#xff0c;用于在界面上显示文本或图像。它常用于作为标签、提示信息或图片展…

TCP三次握手四次挥手

TCP基本认识 TCP的头格式 序列号:在建立连接时由计算机生成随机数作为初始值&#xff0c;通过SYN包传给接收端。每发送一次数据就累加一次该数据字节数的大小。用来解决网络号乱序。(乱序问题:未按发送顺序到达接收端称为乱序) 确认应答号:指下一次期望收到的数据的序列号&…

黑马Java面试笔记之MySQL篇(事务)

一. 事务的特性 事务的特性是什么&#xff1f;可以详细说一下吗&#xff1f; 事务是一组操作的集合&#xff0c;他是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失…

AI炼丹日志-27 - Anubis 通过 PoW工作量证明的反爬虫组件 上手指南 原理解析

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇&#xff1a; MyBatis 更新完毕目前开始更新 Spring&#xff0c;一起深入浅出&#xff01; 大数据篇 300&#xff1a; Hadoop&…

WEBSTORM前端 —— 第3章:移动 Web —— 第4节:移动适配-VM

目录 一、适配方案 二、VM布局 ​编辑 三、vh布局 四、案例—酷我音乐 一、适配方案 二、VM布局 三、vh布局 四、案例—酷我音乐

AI:使用 Keras 实现线性回归模型

🌟从零开始:使用 Keras 实现线性回归模型(附完整代码 + 可视化教程)🔢📈 ✨线性回归是机器学习中的“Hello World”,适合新手入门。本文将通过一个完整的实战案例,带你使用 TensorFlow Keras 搭建一个线性回归模型,并对训练与预测结果进行可视化分析。 📎 本文亮…

TDengine 基于 TDgpt 的 AI 应用实战

基于 TDgpt 时序数据智能体的风力发电预测 作者&#xff1a; derekchen Demo 数据集准备 我们使用公开的UTSD数据集里面的某风场发电数据&#xff0c;作为预测算法的数据来源&#xff0c;基于历史数据预测未来一天内的每15分钟的发电量。原始数据集的采集频次为4秒&#xff…

模拟实现线程池(线程数目为定值)和定时器

前言 昨天学习关于定时器的相关知识。今天花时间去模拟实现了一个定时器&#xff0c;同时也去模拟实现了一个线程池(线程数目为定值)。我感觉我收获了很多&#xff0c;对于线程的理解加深了。跟大家分享一下~ 线程池和定时器(这个是主要)的实现 代码 线程池 import java.ut…

JMeter 性能测试

1.定时器 1.1 同步定时器 作用&#xff1a;阻塞线程使同时达到n个线程之后再发出请求&#xff0c;模拟高并发的场景。 路径&#xff1a;右键请求--添加--定时器--Synchronizing Timer 2.2 常数吞吐量定时器 作用&#xff1a;模拟服务器负载&#xff0c;即需要服务器以一个固定…

通俗易懂的 JS DOM 操作指南:从创建到挂载

目录 &#x1f9e9; 1. 创建元素&#xff1a;document.createElement / createElementNS &#x1f4dd; 2. 创建文本&#xff1a;document.createTextNode ✏️ 3. 修改文本&#xff1a;node.nodeValue &#x1f5d1;️ 4. 移除元素&#xff1a;el.removeChild() &#x1…

串口通信技术及USART应用研究

串口通信技术及USART应用研究 # 串口通信技术及USART应用研究 摘要&#xff1a;本文深入探讨了串口通信技术的基本原理、硬件电路设计以及USART&#xff08;通用同步/异步收发器&#xff09;在STM32微控制器中的应用。首先对通信接口进行了概述&#xff0c;分析了不同通信协议…

OneRef论文精读(补充)

接上篇&#xff1a;OneRef论文精读 The five referring datasets 这些数据集应用于指代表达式理解&#xff08;REC&#xff09;、短语定位&#xff08;PG&#xff09;及指代表达式分割&#xff08;RES&#xff09;任务。表8列出了详细的统计数据。 RefCOCO/RefCOCO/RefCOCOg&…

vscode 代理模式(agent mode),简单尝试一下。

1. 起因&#xff0c; 目的: agent mode&#xff0c; 很流行&#xff0c;名气很大。简单试试效果&#xff0c;确实很强。agent mode&#xff0c; 取代人工&#xff0c;确实是前进了一大步。 2. 先看效果 效果对比&#xff0c;左边是 普通的AI 生成的&#xff0c; 右边是 代理…

Scratch节日 | 六一儿童节抓糖果

六一儿童节怎么能没有糖果&#xff1f;这款 六一儿童节抓糖果 小游戏&#xff0c;让你变身小猫&#xff0c;开启一场甜蜜大作战&#xff01; &#x1f3ae; 游戏玩法 帮助小猫收集所有丢失的糖果&#xff0c;收集越多分数越高&#xff01; 小心虫子一样的“坏糖果”&#xff…

【Linux系统】第八节—进程概念(上)—冯诺依曼体系结构+操作系统+进程及进程状态+僵尸进程—详解!

hi&#xff0c;我是云边有个稻草人 偶尔中二的博主^(*&#xffe3;(oo)&#xffe3;)^&#xff0c;与你分享专业知识&#xff0c;祝博主们端午节快乐&#xff01; Linux—本节博客所属专栏—持续更新中—欢迎订阅&#xff01; 目录 一、冯诺依曼体系结构 二、操作系统(Opera…

告别手动绘图!基于AI的Smart Mermaid自动可视化图表工具搭建与使用指南

以下是对Smart Mermaid的简单介绍&#xff1a; 一款基于 AI 技术的 Web 应用程序&#xff0c;可将文本内容智能转换为 Mermaid 格式的代码&#xff0c;并将其渲染成可视化图表可以智能制作流程图、序列图、甘特图、状态图等等&#xff0c;并且支持在线调整、图片导出可以Docke…

PCB设计教程【强化篇】——USB拓展坞PCB布局

前言 本教程基于B站Expert电子实验室的PCB设计教学的整理&#xff0c;为个人学习记录&#xff0c;旨在帮助PCB设计新手入门。所有内容仅作学习交流使用&#xff0c;无任何商业目的。若涉及侵权&#xff0c;请随时联系&#xff0c;将会立即处理 目录 前言 一、前期准备与板框…