首页 > 其他分享 >go 使用websocket

go 使用websocket

时间:2024-07-08 16:08:13浏览次数:9  
标签:websocket string rs Client user 使用 go conn


package chat

import (
	"encoding/json"
	"github.com/gorilla/websocket"
	"github.com/zeromicro/go-zero/core/logx"
	"log"
	"net/http"
	"sync"
)

type Client struct {
	conn         *websocket.Conn
	messageQueue chan []byte
	mu           sync.Mutex
	user         string
}

func NewClient(user string, conn *websocket.Conn) *Client {
	return &Client{
		conn:         conn,
		user:         user,
		messageQueue: make(chan []byte, 100),
	}
}

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 <- message
			c.mu.Unlock()
		}
	}
}

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 中移除无效连接
		manager.mu.Lock()
		delete(manager.clients, user)
		manager.mu.Unlock()
		_ = client.conn.Close()
	}
}

type ClientManager struct {
	clients map[string]*Client
	mu      sync.RWMutex
}

var manager = ClientManager{
	clients: make(map[string]*Client),
}

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
		}
		// 假设前端会发送一个用户 ID 或会话 ID 用于识别连接
		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] = client
		manager.mu.Unlock()

		go client.ReadPump()

		l := chat.NewChatWebsocketLogic(r.Context(), svcCtx)

		for {
			select {
			case message := <-client.messageQueue:
				var req types.ChatWebsocketRequest
				_ = json.Unmarshal(message, &req)

				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
					}
					returnMessage, _ := json.Marshal(true)
					Send(user, returnMessage, logger)
					continue
				}

				channel := make(chan string, 50)

				go func() {
					defer func() {
						close(channel)
						close(baseInfoCh)
					}()
					res := &types.ChatWebsocketResponse{}
					res, errChat := l.ChatWebsocket(&req, channel, baseInfoCh)
					if errChat != nil {
						logger.Error("ChatWebsocketHandler error :", errChat)
						res.ErrorMessage = response.GetErrorMessage(errChat)
					}

					returnMessage, _ := json.Marshal(res)
					Send(user, returnMessage, logger)
				}()

				var rs []rune
				length := 1
				for {
					s, ok := <-channel
					if !ok {
						if len(rs) > 0 {
							SendSocketMessage(string(rs), req.MessageId)
							rs = []rune{}
						}
						break
					}
					rs = append(rs, []rune(s)...)

					if len(rs) > length {
						SendSocketMessage(string(rs), req.MessageId)
						rs = []rune{}
						if length < 4 {
							length++
						}
					}
				}
			}
		}
	}
}

func SendSocketMessage(message, messageId string) {
	baseReturn := &types.ChatWebsocketResponse{}
	returnMessage, _ := json.Marshal(types.ChatWebsocketResponse{
		Message:    message,
		MessageId:  messageId,
	})
	Send(user, returnMessage, logger)
}

标签:websocket,string,rs,Client,user,使用,go,conn
From: https://www.cnblogs.com/crazytata/p/18290107

相关文章

  • 使用F1C200S从零制作掌机之构建debian文件系统
    前情:使用buildrootfs构建的文件系统调试了很久NES模拟器,执行InfoNES模拟器的时候一直黑屏,无内容显示,调不通了,所以改用debian系统试试。一、环境配置首先下载两个工具:qemu-arm-static和debootstrap。qemu-arm-static:通过qemu-arm-static,我们在x86的UbuntuPC机上,可以模拟A......
  • MybatisPlus使用分页功能
    MybatisPlus使用分页功能分页查询是一个很常见的需求,故Mybatis-Plus提供了一个分页插件,使用它可以十分方便的完成分页查询。下面介绍Mybatis-Plus分页插件的用法,详细信息可参考[官方文档](分页插件|MyBatis-Plus(baomidou.com))首先为分页编写一个配置类:@Configurationpub......
  • 使用邮件每日发送磁盘使用率
    需要每天监控服务器的磁盘使用状况,可以使用任务计划,获取df信息后每天定时发送邮件提醒linux系统使用邮件发送提醒检查mail服务安装状态rpm-qa|grepmaillibreport-plugin-mailx-2.1.11-53.el7.centos.x86_64mailx-12.5-19.el7.x86_64编辑发件人配置文件vi/etc/mail.rc......
  • google adsense verify失败
    现象Wecouldn'tverifyyoursite.MakesurethechangesyoumadetoyoursitearepublishedandaccessiblebytheGoogleAdSensecrawler.Ifyou’restillhavingissuestryanothermethod.背景笔者网站接入googleadsense时接入方式选择Ads.txt接入方式,新增Ad......
  • 如何使用AWS云服务器?
    亚马逊云服务器(AmazonEC2)作为一款强大的云计算服务,为企业和个人提供了灵活可扩展的虚拟服务器资源。如果你正在考虑购买亚马逊云服务器,不妨跟着下面的步骤,了解如何开始你的云端之旅。步骤一:注册AWS账号首先,访问亚马逊AWS官网(aws.amazon.com),并点击“创建免费账号”。按照页面......
  • Docker部署Django+MySQL+Redis+Nginx+uWSGI+Celery(超详细)
    一、知识储备经过我们之前学习的Docker相关知识,现在我们来进行实战,以下介绍如何通过DockerCompose部署Django项目:先前知识:Docker学习笔记(一)概念理解-CSDN博客Docker学习笔记(二)镜像、容器、仓库相关命令操作-CSDN博客Docker学习笔记(三)Dockerfile-CSDN博客DockerCompose......
  • GO (无法)同时引用同一个库的不同版本(未从代码角度解决)
    GO(无法)同时引用同一个库的不同版本(未从代码角度解决)背景一个区块链项目需要用到https://github.com/ethereum/go-ethereum(geth)这个库去对不同的链(eth,bnb等)包括我们自己的链(随便取个称呼BF)进行扫块。项目中用的是gethv1.14.6后来调用智能合约查询我们自己的链(后面就......
  • Ubuntu 2204 安装使用 mariadb
    1.查看mariadb版本,实际上使用的还是mysqld命令:mysqld--version 2.安装完成之后它的生命周期依然由systemctl进行维护,服务名为:mariadb.servicectlstatusmariadb.service 3.它的服务端配置文件位于 /etc/mysql/mariadb.conf.d/50-server.cnf,修改端口也是在里面,一看......
  • python:使用matplotlib库绘制图像(一)
    作者是跟着http://t.csdnimg.cn/4fVW0学习的,matplotlib系列文章是http://t.csdnimg.cn/4fVW0的自己学习过程中整理的详细说明版本,对小白更友好哦!一、Matplotlib图像基础1.1 基本绘图实例:sin、cos函数图代码详解:1.frompylabimport*:导入pylab库中所有函数和变量。pyla......
  • C/C++ 断言 assert 的使用方法和注意事项
    C/C++中的断言(Assertion)是一种调试辅助工具,主要用于在开发过程中检测程序中的错误。断言对于确保程序的内部状态满足特定条件非常有用。如果条件为真(即,预期的条件得到了满足),程序可以继续执行。如果条件为假,则断言失败,程序会报告错误并终止执行。使用方法在C语言中,断言是通过a......