WebSocket 实现指南

WebSocket 实现指南

目录

  1. [TOC]

1. 依赖安装

1.1 安装必要的包

# 安装 gorilla/websocket
go get github.com/gorilla/websocket# 安装 gin 框架
go get github.com/gin-gonic/gin

1.2 更新 go.mod

require (github.com/gin-gonic/gin v1.9.1github.com/gorilla/websocket v1.5.3
)

1.3 配置文件

1.3.1Config.go
package configimport ("os""gopkg.in/yaml.v3"
)type Config struct {Server    ServerConfig    `yaml:"server"`MySQL     MySQLConfig     `yaml:"mysql"`Redis     RedisConfig     `yaml:"redis"`JWT       JWTConfig       `yaml:"jwt"`WebSocket WebSocketConfig `yaml:"websocket"`
}type ServerConfig struct {Port int    `yaml:"port"`Mode string `yaml:"mode"`
}type MySQLConfig struct {Host     string `yaml:"host"`Port     int    `yaml:"port"`User     string `yaml:"user"`Password string `yaml:"password"`DBName   string `yaml:"dbname"`
}type RedisConfig struct {Host     string `yaml:"host"`Port     int    `yaml:"port"`Password string `yaml:"password"`DB       int    `yaml:"db"`
}type JWTConfig struct {Secret string `yaml:"secret"`Expire int    `yaml:"expire"` // token过期时间(小时)
}type WebSocketConfig struct {ReadBufferSize  int   `yaml:"read_buffer_size"`WriteBufferSize int   `yaml:"write_buffer_size"`MaxMessageSize  int64 `yaml:"max_message_size"`WriteWait       int   `yaml:"write_wait"`PongWait        int   `yaml:"pong_wait"`PingPeriod      int   `yaml:"ping_period"`MaxConnections  int   `yaml:"max_connections"`
}var GlobalConfig Configfunc Init() error {// 确保日志目录存在os.MkdirAll("logs", 0755)file, err := os.ReadFile("config/config.yaml")if err != nil {return err}return yaml.Unmarshal(file, &GlobalConfig)
}
1.3.2 config.yaml
server:port: 8080mode: debug  # debug or releasewebsocket:read_buffer_size: 1024write_buffer_size: 1024max_message_size: 512write_wait: 10     # secondspong_wait: 60      # secondsping_period: 54    # secondsmax_connections: 5000mysql:host: localhostport: 3306user: rootpassword: "123456"dbname: my_kingdomredis:host: localhostport: 6379password: "123456"db: 0jwt:secret: "XueZhimin"expire: 24  # hours

2. 代码实现

2.1 WebSocket管理器 (manager.go)

package websocketimport ("mykingdom/config""net/http""sync""github.com/gin-gonic/gin""github.com/gorilla/websocket"
)// Manager WebSocket管理器
type Manager struct {clients   sync.Map    // 所有客户端连接broadcast chan []byte // 广播消息通道config    *config.WebSocketConfig
}// 配置websocket upgrader
var upgrader = websocket.Upgrader{ReadBufferSize:  1024,WriteBufferSize: 1024,CheckOrigin: func(r *http.Request) bool {return true // 允许所有跨域请求},
}// NewManager 创建WebSocket管理器
func NewManager(config *config.WebSocketConfig) *Manager {return &Manager{broadcast: make(chan []byte),config:    config,}
}// HandleWebSocket 处理WebSocket连接
func (m *Manager) HandleWebSocket() gin.HandlerFunc {return func(c *gin.Context) {// 检查连接数限制count := 0m.clients.Range(func(_, _ interface{}) bool {count++return true})if count >= m.config.MaxConnections {c.JSON(http.StatusServiceUnavailable, gin.H{"message": "达到最大连接数限制",})return}// 升级HTTP连接为WebSocket连接conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)if err != nil {return}// 创建新的客户端连接client := &Client{conn:    conn,manager: m,send:    make(chan []byte, 256),}// 存储客户端连接m.clients.Store(client.conn.RemoteAddr().String(), client)// 启动读写协程go client.readPump()go client.writePump()}
}// Broadcast 广播消息给所有客户端
func (m *Manager) Broadcast(message []byte) {m.clients.Range(func(_, value interface{}) bool {if client, ok := value.(*Client); ok {select {case client.send <- message:default:m.clients.Delete(client.conn.RemoteAddr().String())close(client.send)}}return true})
}// SendToClient 发送消息给指定客户端
func (m *Manager) SendToClient(clientAddr string, message []byte) bool {if value, ok := m.clients.Load(clientAddr); ok {if client, ok := value.(*Client); ok {client.send <- messagereturn true}}return false
}

2.2 客户端实现 (client.go)

package websocketimport ("log""time""github.com/gorilla/websocket"
)const (writeWait  = 10 * time.SecondpongWait   = 60 * time.SecondpingPeriod = (pongWait * 9) / 10
)// Client WebSocket客户端
type Client struct {conn    *websocket.Connmanager *Managersend    chan []byte
}// readPump 从WebSocket连接读取消息
func (c *Client) readPump() {defer func() {c.manager.clients.Delete(c.conn.RemoteAddr().String())c.conn.Close()}()c.conn.SetReadDeadline(time.Now().Add(pongWait))c.conn.SetPongHandler(func(string) error {c.conn.SetReadDeadline(time.Now().Add(pongWait))return nil})for {_, message, err := c.conn.ReadMessage()if err != nil {if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {log.Printf("error: %v", err)}break}// 广播消息给所有客户端c.manager.Broadcast(message)}
}// writePump 向WebSocket连接写入消息
func (c *Client) writePump() {ticker := time.NewTicker(pingPeriod)defer func() {ticker.Stop()c.conn.Close()}()for {select {case message, ok := <-c.send:c.conn.SetWriteDeadline(time.Now().Add(writeWait))if !ok {c.conn.WriteMessage(websocket.CloseMessage, []byte{})return}w, err := c.conn.NextWriter(websocket.TextMessage)if err != nil {return}w.Write(message)if err := w.Close(); err != nil {return}case <-ticker.C:c.conn.SetWriteDeadline(time.Now().Add(writeWait))if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {return}}}
}

3. 配置说明

3.1 WebSocket配置 (config.yaml)

websocket:read_buffer_size: 1024    # 读缓冲区大小write_buffer_size: 1024   # 写缓冲区大小max_connections: 5000     # 最大连接数

3.2 配置结构体 (config.go)

type WebSocketConfig struct {ReadBufferSize  int   `yaml:"read_buffer_size"`WriteBufferSize int   `yaml:"write_buffer_size"`MaxConnections  int   `yaml:"max_connections"`
}

4. 使用示例

4.1 服务端示例

func main() {r := gin.Default()// 创建WebSocket管理器wsManager := websocket.NewManager(&config.GlobalConfig.WebSocket)// WebSocket连接r.GET("/ws", wsManager.HandleWebSocket())// 广播消息r.POST("/broadcast", func(c *gin.Context) {message := c.PostForm("message")wsManager.Broadcast([]byte(message))c.JSON(200, gin.H{"message": "broadcast sent"})})r.Run(":8080")
}

4.2 前端示例

// 连接WebSocket
const ws = new WebSocket('ws://localhost:8080/ws')// 连接成功
ws.onopen = () => {console.log('连接成功')
}// 接收消息
ws.onmessage = (event) => {console.log('收到消息:', event.data)
}// 发送消息
ws.send('Hello, World!')

4.3 Vue组件示例

<template><div><div>连接状态: {{ isConnected ? '已连接' : '未连接' }}</div><input v-model="message" @keyup.enter="sendMessage"><button @click="sendMessage">发送</button></div>
</template><script setup>
import { ref, onMounted, onUnmounted } from 'vue'const ws = ref(null)
const isConnected = ref(false)
const message = ref('')const connect = () => {ws.value = new WebSocket('ws://localhost:8080/ws')ws.value.onopen = () => isConnected.value = truews.value.onclose = () => isConnected.value = falsews.value.onmessage = (event) => {console.log('收到消息:', event.data)}
}const sendMessage = () => {if (isConnected.value && message.value) {ws.value.send(message.value)message.value = ''}
}onMounted(() => connect())
onUnmounted(() => ws.value?.close())
</script>

4.4 消息收发示例

4.4.1 消息结构
// Message 定义消息结构
type Message struct {Type     string      `json:"type"`    // 消息类型Data     interface{} `json:"data"`    // 消息数据ClientID string      `json:"-"`       // 发送者的连接ID
}
4.4.2 发送消息
// 1. 广播消息给所有客户端
err := wsManager.SendMessage("chat", map[string]interface{}{"user": "系统","content": "欢迎新用户加入",
})// 2. 发送消息给指定客户端
err := wsManager.SendMessageToClient(clientAddr, "private", map[string]interface{}{"content": "这是一条私信","time": time.Now(),
})// 3. 发送游戏动作
err := wsManager.SendMessage("game_action", map[string]interface{}{"action": "move","position": map[string]int{"x": 100,"y": 200,},
})
4.4.3 接收和处理消息
// 启动消息处理协程
go func() {// 获取消息通道msgChan := wsManager.GetMessages()// 循环处理消息for msg := range msgChan {// 根据消息类型处理switch msg.Type {case "chat":handleChatMessage(msg)case "game_action":handleGameAction(msg)case "private":handlePrivateMessage(msg)default:log.Printf("未知消息类型: %s", msg.Type)}}
}()// 处理聊天消息
func handleChatMessage(msg websocket.Message) {data, ok := msg.Data.(map[string]interface{})if !ok {return}log.Printf("来自 %s 的聊天消息: %v", msg.ClientID, data["content"])
}// 处理游戏动作
func handleGameAction(msg websocket.Message) {data, ok := msg.Data.(map[string]interface{})if !ok {return}log.Printf("来自 %s 的游戏动作: %v", msg.ClientID, data["action"])
}
4.4.4 前端发送消息示例
// 1. 发送聊天消息
ws.send(JSON.stringify({type: 'chat',data: {content: '大家好!'}
}))// 2. 发送游戏动作
ws.send(JSON.stringify({type: 'game_action',data: {action: 'move',position: {x: 100,y: 200}}
}))
4.4.5 前端接收消息示例
ws.onmessage = (event) => {try {const message = JSON.parse(event.data)// 根据消息类型处理switch (message.type) {case 'chat':handleChat(message.data)breakcase 'game_action':handleGameAction(message.data)breakcase 'private':handlePrivateMessage(message.data)breakdefault:console.log('未知消息类型:', message.type)}} catch (error) {console.error('解析消息失败:', error)}
}// 处理聊天消息
function handleChat(data) {console.log(`${data.user}: ${data.content}`)
}// 处理游戏动作
function handleGameAction(data) {console.log('游戏动作:', data.action)updateGameState(data.position)
}// 处理私信
function handlePrivateMessage(data) {console.log('收到私信:', data.content)
}

4.5 错误处理示例

// 发送消息时的错误处理
if err := wsManager.SendMessage("chat", data); err != nil {log.Printf("发送消息失败: %v", err)
}// 发送私信时的错误处理
if err := wsManager.SendMessageToClient(clientAddr, "private", data); err != nil {if strings.Contains(err.Error(), "client not found") {log.Printf("用户已离线: %s", clientAddr)} else {log.Printf("发送私信失败: %v", err)}
}

4.6 消息类型建议

const (// 系统消息MessageTypeSystem     = "system"      // 系统通知MessageTypeError      = "error"       // 错误消息// 聊天消息MessageTypeChat       = "chat"        // 公共聊天MessageTypePrivate    = "private"     // 私聊消息// 游戏消息MessageTypeGameAction = "game_action" // 游戏动作MessageTypeGameState  = "game_state"  // 游戏状态MessageTypeMatch      = "match"       // 匹配相关
)

5. 框架整合

5.1 与Gin框架整合

// 中间件:验证WebSocket连接
func AuthWebSocket() gin.HandlerFunc {return func(c *gin.Context) {// 验证tokentoken := c.Query("token")if !validateToken(token) {c.AbortWithStatus(http.StatusUnauthorized)return}c.Next()}
}// 使用中间件
r.GET("/ws", AuthWebSocket(), wsManager.HandleWebSocket())

5.2 与Nginx整合

# nginx.conf
http {map $http_upgrade $connection_upgrade {default upgrade;'' close;}upstream websocket {server 127.0.0.1:8080;}server {listen 80;server_name example.com;# WebSocket代理location /ws {proxy_pass http://websocket;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection $connection_upgrade;proxy_set_header Host $host;}# 其他HTTP请求location / {proxy_pass http://127.0.0.1:8080;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;}}
}

6. 部署说明

6.1 服务器要求

  • 支持WebSocket的现代浏览器
  • Go 1.16+
  • Nginx 1.16+(如果使用)

6.2 部署步骤

  1. 编译Go程序
go build -o server cmd/main.go
  1. 配置Nginx(如果使用)
# 复制nginx配置
sudo cp nginx.conf /etc/nginx/conf.d/websocket.conf# 重启Nginx
sudo systemctl restart nginx
  1. 运行服务
./server

6.3 注意事项

  1. 连接管理

    • 定期清理断开的连接
    • 实现重连机制
    • 控制连接数量
  2. 安全性

    • 添加连接认证
    • 限制消息大小
    • 添加消息验证
  3. 性能优化

    • 使用消息队列
    • 控制广播频率
    • 添加负载均衡
  4. 监控

    • 记录连接数
    • 监控消息流量
    • 错误日志记录

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

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

相关文章

VR+智慧消防一体化决策平台

随着科技的飞速发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术与智慧城市建设的结合越来越紧密。在消防安全领域&#xff0c;VR技术的应用不仅能够提升消防训练的效率和安全性&#xff0c;还能在智慧消防一体化决策平台中发挥重要作用。本文将探讨“VR智慧消防一体化…

c++领域展开第十幕——类和对象(内存管理——c/c++内存分布、c++内存管理方式、new/delete与malloc/free区别)超详细!!!!

文章目录 前言一、c/c内存分布二、c的内存管理方式2.1new/delete操作内置类型2.2new和delete操作自定义类型 三、operator new与operator delete函数3.1operator new与operator delete函数 四、new和delete的实现原理4.1 内置类型4.2 自定义类型 五、malloc/free和new/delete的…

Android Audio基础(53)——PCM逻辑设备Write数据

1. 前言 本文,我们将以回放(Playback,播放音频)为例,讲解PCM Data是如何从用户空间到内核空间,最后传递到Codec。 在 ASoC音频框架简介中,我们给出了回放(Playback)PCM数据流示意图。: 对于Linux来说,由于分为 user space 和kernel space,而且两者之间数据不能随便…

gesp(C++四级)(6)洛谷:B3870:[GESP202309 四级] 变长编码

gesp(C四级)&#xff08;6&#xff09;洛谷&#xff1a;B3870&#xff1a;[GESP202309 四级] 变长编码 题目描述 小明刚刚学习了三种整数编码方式&#xff1a;原码、反码、补码&#xff0c;并了解到计算机存储整数通常使用补码。但他总是觉得&#xff0c;生活中很少用到 2 31…

【C++数据结构——查找】二叉排序树(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 1. 二叉排序树的基本概念 2. 二叉排序树节点结构体定义 3. 创建二叉排序树 4. 判断是否为二叉排序树 5. 递归查找关键字为 6 的结点并输出查找路径 6. 删除二叉排序树中的节点 测试说明 通关代码 测试结果 任务描述 本关任务&a…

Ubuntu下的小bug

问题1&#xff1a; terminal 终端CtrlShfitE键与搜狗输入法冲突Linux 参考链接&#xff1a;https://blog.csdn.net/u011895157/article/details/131583702?fromshareblogdetail&sharetypeblogdetail&sharerId131583702&sharereferPC&sharesourceAndroid_WPF…

Qt 下位机串口模拟器

使用 vspd 创建虚拟配对串口&#xff0c;Qt 实现下位机串口模拟器&#xff0c;便于上位机开发及实时调试&#xff0c;适用字符串格式上下位机串口通信&#xff0c;数据包格式需增加自定义解析处理。 通过以下链接下载 vspd 安装包&#xff0c;进行 dll 破解。 链接: https://…

面试高频:一致性hash算法

这两天看到技术群里&#xff0c;有小伙伴在讨论一致性hash算法的问题&#xff0c;正愁没啥写的题目就来了&#xff0c;那就简单介绍下它的原理。下边我们以分布式缓存中经典场景举例&#xff0c;面试中也是经常提及的一些话题&#xff0c;看看什么是一致性hash算法以及它有那些…

数据库1-4讲

各种名词区分 内模式也叫物理模式、存储模式。 概念模式也叫全局模式、逻辑模式。 外模式也叫用户模式。 笛卡尔积&#xff1a;D1、D2、D3集合中任取一个的所有可能情况。 因此上述笛卡尔积的基数22312 关系模型的三个完整性&#xff1a; 实体完整性&#x…

JMeter + Grafana +InfluxDB性能监控 (二)

您可以通过JMeter、Grafana 和 InfluxDB来搭建一个炫酷的基于JMeter测试数据的性能测试监控平台。 下面&#xff0c;笔者详细介绍具体的搭建过程。 安装并配置InfluxDB 您可以从清华大学开源软件镜像站等获得InfluxDB的RPM包&#xff0c;这里笔者下载的是influxdb-1.8.0.x86_…

C语言 数组编程练习

1.将数组A的内容和数组B中的内容进行交换。&#xff08;数组一样大&#xff09; 2.创建一个整形数组&#xff0c;完成对数组的操作 实现函数Init()初始化数组全为0 实现print()打印数组的每个元素 实现reverse()函数完成数组元素的逆置 //2.创建一个整形数组&#xff0c;完…

深度评测uni-app x:开启跨平台开发新篇章

文章目录 一、引言1.1 跨平台开发的崛起1.2 uni-app x 初印象 二、uni-app x 核心特性评测2.1 uts 语言&#xff1a;跨平台编程新利器2.2 uvue 渲染引擎&#xff1a;原生渲染新体验2.3 强大的组件和 API 支持2.4 插件生态&#xff1a;拓展无限可能 三、与 uni-app 对比&#xf…

wordpress开发之实现使用第三方库qrcode-generator生成二维码并上传和展示

文章目录 一、需求二、技术实现 - 利用qrcode-generator库三、代码实现 一、需求 客户的需求是能将特定的url生成二维码&#xff0c;以便将二维码分享或贴到合同纸上给他的客户扫描查看信息。 这个url包含的内容类似于如下格式&#xff1a; https://www.example.com/contrac…

vue3 数字滚动效果

效果图 代码 <template><div class"number-scroller"><divclass"viewport":style"{ width: width px, height: height px }"><div class"number-scroller-box" ref"num"><div v-for"num…

谷粒商城-高级篇-Sentinel-分布式系统的流量防卫兵

1、基本概念 1.1、熔断降级限流 1、什么是熔断 A 服务调用 B 服务的某个功能&#xff0c;由于网络不稳定问题&#xff0c;或者 B 服务卡机&#xff0c;导致功能时间超长。如果这样子的次数太多。我们就可以直接将 B 断路了&#xff08; A 不再请求 B 接口&#xff09;&#…

手机租赁平台开发实用指南与市场趋势分析

内容概要 在当今快速变化的科技时代&#xff0c;手机租赁平台的发展如火如荼。随着越来越多的人希望使用最新款的智能手机&#xff0c;但又不愿意承担昂贵的购机成本&#xff0c;手机租赁平台应运而生。这种模式不仅为用户提供了灵活的选择&#xff0c;还为企业创造了新的商机…

计算机网络 (22)网际协议IP

一、IP协议的基本定义 IP协议是Internet Protocol的缩写&#xff0c;即因特网协议。它是TCP/IP协议簇中最核心的协议&#xff0c;负责在网络中传送数据包&#xff0c;并提供寻址和路由功能。IP协议为每个连接在因特网上的主机&#xff08;或路由器&#xff09;分配一个唯一的IP…

NUTTX移植到STM32

STM32移植NUTTX 1. Ubuntu下搭建开发环境1.1 先决条件1.2 下载 NuttX1.3 使用Make 进行编译1.4 烧录运行 2.通过NUTTX点亮LED2.1 部署操作系统2.2 修改配置文件2.3 编译运行程序 开发板&#xff1a;DshanMCUF407 官方开发文档&#xff1a;安装 — NuttX latest 文档 参考文档&…

MITRE ATTCK 简介:初学者指南

网络安全已成为当今数字世界的一个关键问题。随着网络威胁日益复杂&#xff0c;组织需要一种结构化的方法来理解和应对这些风险。这就是 MITRE ATT&CK 框架发挥作用的地方。如果您是网络安全新手或刚刚开始探索威胁分析和缓解&#xff0c;本指南将为 MITRE ATT&CK 提供…

JAVA创建绘图板JAVA构建主窗口鼠标拖动来绘制线条

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c; 忍不住分享一下给大家。点击跳转到网站 学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把…