用websocket实现一个简单的im聊天功能

WebSocket前后端建立以及使用-CSDN博客

经过我上一篇的基本理解websocket的建立以及使用后,这篇就写一个简单的demo

实现im聊天

首先就是后端代码,详细解释我都放到了每一句的代码解析了,文章最后我会说怎么运行流程

放置后端代码

package mainimport ("encoding/json""fmt""github.com/gorilla/websocket""gopkg.in/fatih/set.v0""log""net/http""strconv""sync"
)type Message struct {FormId   string `json:"userID1"`TargetId string `json:"userID2"`Content  string `json:"data"`
}type Node struct {Conn      *websocket.ConnDataQueue chan []byte //DataQueue 用于存储待处理的数据,从 WebSocket 连接接收到的消息数据。//set.Interface 是一个集合类型的接口,可以用于实现不同类型的集合,例如包含不重复元素的集合,比如后续可以将多个用户分到同一个组//然后可以对这个一个组内的消息进行遍历然后群发消息。可以理解为并发安全的集合。GroupSets set.Interface
}// 映射关系  通过一个map集合来存储多个用户和节点的映射关系,因为每个用户和服务端建立的websocket连接地址都不同
// 比如ws://localhost:8088/ws?userID1和ws://localhost:8088/ws?userID2等等。。。
var clientMap map[int64]*Node = make(map[int64]*Node, 0)// 读写锁  多个用户在建立连接的时候都要存储到map中,所以这里用一个锁保证读写的安全
var rwLocker sync.RWMutexfunc main() {http.HandleFunc("/ws", Chat)log.Fatal(http.ListenAndServe(":8088", nil))
}// Chat 用来接收用户发起ws连接,以及后续用协程来持续读取消息,并推送给其他用户
func Chat(writer http.ResponseWriter, request *http.Request) {query := request.URL.Query()Id := query.Get("userID")//log.Println("接收到的 userID:", Id)userId, _ := strconv.ParseInt(Id, 10, 64)isvalida := trueconn, err := (&websocket.Upgrader{ //将 HTTP 连接升级为 WebSocket 连接//CheckOrigin 函数用于检查请求的来源是否合法,即允许跨域访问CheckOrigin: func(r *http.Request) bool {return isvalida},}).Upgrade(writer, request, nil) //request 参数包含了客户端发起连接时提供的 URL 信息ws://localhost:8088/useridif err != nil {log.Println("升级为WebSocket连接失败:", err)return}//2.获取connnode := &Node{Conn:      conn,DataQueue: make(chan []byte, 50),GroupSets: set.New(set.ThreadSafe),}defer conn.Close()//4. userid 跟 node绑定 并加锁  用程序的读写锁抢占线程资源rwLocker.Lock()clientMap[userId] = noderwLocker.Unlock()//5.用协程完成发送消息推送逻辑go sendProc(node)//6.用协程完成发送消息逻辑go recvProc(node)//这里有点小问题其实,然后用一个select 来阻塞主handle,然后等待协程中的发送消息以及消息推送select {}
}// 5.完成发送逻辑   发送的是消息推送
func sendProc(node *Node) {for {select {//当有人给自己发送了数据后,那么自己的node.DataQueue中就会有数据//然后就可以通过webScoket把自己接收到数据的事情传递给前端。也就是消息推送case data := <-node.DataQueue: //这个同样是阻塞式操作//告诉前端有人私聊自己了,这个消息推送通过websocket发送出去err := node.Conn.WriteMessage(websocket.TextMessage, data)if err != nil {fmt.Println(err)return}}}
}// 6.完成发送消息逻辑
func recvProc(node *Node) {for {//发送操作是前端操作的,即前端将用户A发送的消息内容放置到的websocket中//后端接收数据的data中就包含内容是谁给谁发送的信息。后续可以将这部分数据拆分出来后然后保存到数据库,//使用的是Conn接收的数据,发送的请求,而不是http了_, data, err := node.Conn.ReadMessage()if err != nil {fmt.Println("推送消息失败:", err)break}//用户A发送的消息给解析展示一下var myData Messageerr = json.Unmarshal(data, &myData)if err != nil {log.Println("解析JSON失败:", err)break}fmt.Println("[ws]发送消息:", myData)//用户A发送给别人的消息,要将别人的node.DataQueue中放入消息,然后别人通过sendProc来发送给前端broadMsg(data)}
}func broadMsg(data []byte) {msg := Message{}err := json.Unmarshal(data, &msg)if err != nil {fmt.Println(err)return}str, err := strconv.ParseInt(msg.TargetId, 10, 64)if err != nil {fmt.Println(err)return}//将用户A发送给别人(msg.TargetId)的消息放入到TargetId对应的node.DataQueue中sendMsg(str, data)//后续的扩展暂时不管群发,广播,广播//switch msg.Type {//case strconv.Itoa(1): //私信//	sendMsg(int64(msg.TargetId), data)// case 2:  //群发// 	sendGroupMsg()// case 3://广播// 	sendAllMsg()//case 4:////}
}func sendMsg(TargetId int64, msg []byte) {rwLocker.RLock()//通过接收消息的用户id来找到目标用户的nodenode, ok := clientMap[TargetId] //因为这个方法是接收数据方法下沉过来的,所以接收对象的目的对象就是UserId   所以多个人建立多个对象,也就是都相符了。rwLocker.RUnlock()if ok {//在这个目标用户的node的DataQueue中放入数据,这样如果对方是在线状态的话,它的sendProc函数中就会收到消息,然后推送给前端node.DataQueue <- msg}
}

放置前端代码

用户1 id是123

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><input type="text" id="userID2"><input type="text" id="data"><button onclick="onclicksend()">发送</button ><script>id = 123//与本地计算机通信const ws = new WebSocket('ws://localhost:8088/ws?userID='+id)//与其他计算机通信//const ws = new WebSocket('ws://服务器地址:端口号')function onclicksend(){var userID2= document.getElementById("userID2").value;var data= document.getElementById("data").value;var sendData = {userID1: ""+id,userID2: userID2,data: data};console.log(sendData);var jsonStr = JSON.stringify(sendData);ws.send(jsonStr);}ws.onopen = function () {console.log('我们连接成功啦...')// ws.send(11);}ws.onerror = function () {console.log('连接失败了...')}ws.onmessage = function (e) {console.log('服务端传来数据啦...' + e.data)}ws.onclose = function () {console.log('连接关闭了...')}</script>
</body>
</html>

用户2 id是456

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><input type="text" id="userID2"><input type="text" id="data"><button onclick="onclicksend()">发送</button ><script>id = 456//与本地计算机通信const ws = new WebSocket('ws://localhost:8088/ws?userID='+id)//与其他计算机通信//const ws = new WebSocket('ws://服务器地址:端口号')function onclicksend(){var userID2= document.getElementById("userID2").value;var data= document.getElementById("data").value;var sendData = {userID1: ""+id,userID2: userID2,data: data};console.log(sendData);var jsonStr = JSON.stringify(sendData);ws.send(jsonStr);}ws.onopen = function () {console.log('我们连接成功啦...')// ws.send(11);}ws.onerror = function () {console.log('连接失败了...')}ws.onmessage = function (e) {console.log('服务端推送数据: ' + e.data)}ws.onclose = function () {console.log('连接关闭了...')}</script>
</body>
</html>

运行的时候老样子,先运行后端服务器,然后再运行前端两个页面然后打开控制台看聊天

前端页面很简单就是两个text框,第一个是给目标用户id发信息,第二个是发送的信息内容

经过测试就会知道比如左边的页面:用户456给用户123发信息的时候左边的控制台也会输出内容

这样之后进行包装,将控制台打印的内容放到一个聊天页面就可以实现im聊天了。

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

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

相关文章

xilinx xdma drive 传输8MB以上数据受限的问题

当传输超过8 MB数据时报错error code1359&#xff0c; #define XDMA_MAX_TRANSFER_SIZE (8UL * 1024UL * 1024UL) 可以修改成&#xff1a; #define XDMA_MAX_TRANSFER_SIZE (80UL * 1024UL * 1024UL) VS2019 WDK环境的搭建 先准备好VS WDK的驱动开发环境。需要下载VS、SD…

大数据交通行政执法监测系统

交通行政执法监测系统应用系统按照监测主体可分为&#xff1a;出租车交通违法监测&#xff0c;客车交通违法监测&#xff0c;货车、危化品车辆交通违法监测&#xff0c;非法营运车辆监测。功能模块涵盖&#xff1a;特征识别、档案查询、预警分析等。 &#xff08;1&#xff09;…

[算法][单调栈] [leetcode]316. 去除重复字母

去除重复字母 给你一个字符串 s &#xff0c;请你去除字符串中重复的字母&#xff0c;使得每个字母只出现一次。需保证 返回结果的 字典序最小&#xff08;要求不能打乱其他字符的相对位置&#xff09;。 字典序最小&#xff1a; 考虑字符串 a 与 字符串 b&#xff0c;如果字…

掌握未来搜索的钥匙:深入解析 Milvus 向量搜索引擎的终极指南!

在大数据时代&#xff0c;向量搜索技术愈发重要。作为一个开源的向量相似性搜索引擎&#xff0c;Milvus 提供了基于向量的相似性搜索功能&#xff0c;广泛应用于机器学习、人工智能等领域。本文将深入介绍 Milvus 的基本概念&#xff0c;包括其介绍、主要作用、使用方法及注意事…

SpringSecurity集成第三方登录

SpringSecurity 集成第三方登录 认证及自定义流程 首先我们提供一个实现了AbstractAuthenticationProcessingFilter抽象类的过滤器&#xff0c;用来代替UsernamePasswordAuthenticationFilter逻辑&#xff0c;然后提供一个AuthenticationProvider实现类代替AbstractUserDetail…

合专家模型 (MoE) 详解

本文转载自&#xff1a;混合专家模型 (MoE) 详解 https://huggingface.co/blog/zh/moe 英文版&#xff1a;https://huggingface.co/blog/moe 文章目录 一、简短总结二、什么是混合专家模型&#xff1f;三、混合专家模型简史四、什么是稀疏性?五、混合专家模型中令牌的负载均衡…

solidworks的进阶操作

目录 1 可以找别人的图 2 渲染 2.1 基本流程 2.2 相机和光源 3 装配图缩放 3.1 将装配图转换为零件 3.2 删除一些细节(可选) 3.3 缩放 4 3dmax文件转换为STL并对STL上色 5 文件是未来版本 1 可以找别人的图 有时需要出一些示意图&#xff0c;像是电脑桌子…

Wix打包后安装包直接签名安装失败原因

生成的游戏启动器wix安装包直接打包后进行签名安装会失败&#xff0c;看安装日志显示的错误为 Failed to extract all files from container, erf: 1:2:0 网上搜到的解决方案 需要用insignia工具解包&#xff0c;解包后的文件签一次名&#xff0c;再打一次包&#xff0c;再…

校园管理系统,基于 SpringBoot+Vue+MySQL 开发的前后端分离的校园管理系统设计实现

目录 一. 前言 二. 功能模块 2.1. 管理员功能模块 2.2. 用户功能模块 2.3. 院校管理员功能模块 三. 部分代码实现 四. 源码下载 一. 前言 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身…

AR人像滤镜SDK解决方案,专业调色,打造个性化风格

视觉内容已成为企业传达品牌价值和吸引用户眼球的重要载体&#xff0c;为满足企业对于高质量、多样化视觉内容的迫切需求&#xff0c;美摄科技凭借先进的AR技术和深厚的图像处理经验&#xff0c;推出了业界领先的AR人像滤镜SDK解决方案。 一、一站式解决方案&#xff0c;覆盖多…

Leetcode3138. 同位字符串连接的最小长度

Every day a Leetcode 题目来源&#xff1a;3138. 同位字符串连接的最小长度 解法1&#xff1a;枚举同位子串的长度 从小到大枚举字符串 t 的长度 len。 因为字符串 s 由字符串 t 和它的同位字符串连接而成&#xff0c;所以 n % len 0。 然后比较所有首字母下标为 0、len…

阿里云域名备案流程

阿里云域名备案流程大致可以分为以下几个步骤&#xff0c;这些信息综合了不同来源的最新流程说明&#xff0c;确保了流程的时效性和准确性&#xff1a; UP贴心的附带了链接&#xff1a; 首次备案流程&#xff1a;ICP首次备案_备案(ICP Filing)-阿里云帮助中心 (aliyun.com) …

政安晨:【Keras机器学习示例演绎】(四十三)—— 使用 KerasNLP 实现英语到西班牙语的翻译

目录 简介 设置 下载数据 解析数据 数据标记化 格式化数据集 建立模型 训练我们的模型 解码测试句子&#xff08;定性分析&#xff09; 解码测试句子&#xff08;定性分析&#xff09; 评估我们的模型&#xff08;定量分析&#xff09; 10 个轮次后&#xff0c;得分…

事务-MYSQL

目录 1.事务操作演示 2.事务四大特性ACID 3.并发事务问题 4. 并发事务演示及隔离级别​编辑​编辑​编辑​编辑​编辑​编辑​编辑 1.事务操作演示 默认MySQL的事务是自动提交的,也就是说,当执行一条DML语句,MySQL会立即隐式的提交事务。 方式二 2.事务四大特性ACID 原子…

多线程-线程安全

目录 线程安全问题 加锁(synchronized) synchronized 使用方法 synchronized的其他使用方法 synchronized 重要特性(可重入的) 死锁的问题 对 2> 提出问题 对 3> 提出问题 解决死锁 对 2> 进行解答 对4> 进行解答 volatile 关键字 wait 和 notify (重要…

LeetCode例题讲解:844.比较含退格的字符串

给定 s 和 t 两个字符串&#xff0c;当它们分别被输入到空白的文本编辑器后&#xff0c;如果两者相等&#xff0c;返回 true 。# 代表退格字符。 注意&#xff1a;如果对空文本输入退格字符&#xff0c;文本继续为空。 示例 1&#xff1a; 输入&#xff1a;s "ab#c&qu…

llm.c的Makefile

源码 CC ? clang CFLAGS -Ofast -Wno-unused-result -Wno-ignored-pragmas -Wno-unknown-attributes LDFLAGS LDLIBS -lm INCLUDES CFLAGS_COND -marchnative# Find nvcc SHELL_UNAME $(shell uname) REMOVE_FILES rm -f OUTPUT_FILE -o $ CUDA_OUTPUT_FILE -o $# N…

springboot项目打包部署

springboot打包的前提条件jdk必须17以后不然本地运行不来&#xff08;我用的jdk是22&#xff09; 查看自己电脑jdk版本可以参考&#xff08;完美解决Windows10下-更换JDK环境变量后&#xff0c;在cmd下执行仍java -version然出现原来版本的JDK的问题-CSDN博客&#xff09; 1、…

六级翻译笔记

理解加表达 除了专有名词不能自己理解翻译&#xff0c;其它都可以 时态一般唯一 题目里出现有翻译为 客观存在&#xff1a; there be 单词结尾加er和ee的区别&#xff1a;er是主动&#xff0c;ee是被动 中文句子没有被动&#xff0c;也可以英文翻译为被动 中文的状语可以不是…

【无标获取S4与ECC的具体差异的方法题】

首先我们需要对ECC vs S4的差异这个课题要有一个深刻的理解&#xff0c;这不是一个简单并能准确说清楚的课题。 我们需要结合实际项目的具体情况去回答这个问题&#xff0c;因为这个问题本身是没有标准答案的。 首先要了解SAP本身ERP产品线的发展概况&#xff0c;其次我们要…