用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;如果字…

MAVEN打包JAR启动执行manifest

当您使用Maven进行项目打包&#xff0c;特别是需要创建一个可执行的JAR文件时&#xff0c;确保JAR文件的MANIFEST.MF中包含正确的Main-Class属性是非常重要的。这个属性告诉Java运行时环境哪个类包含main方法&#xff0c;作为应用程序的入口点。 如果您发现生成的JAR文件不包含…

避免死锁陷阱:加锁策略的优化与最佳实践解析

1. 加锁方式优化的动机和背景 在并发编程中&#xff0c;锁是维护共享资源状态一致性的重要机制。随着业务流量的日益增长&#xff0c;原有的加锁方式可能会成为性能瓶颈。因此&#xff0c;了解和优化加锁方式是提升系统并发性能的关键步骤。 1.1. 对加锁方式进行优化的重要性…

自媒体探索2

说说大家做自媒体的一些难点。 1、需要时间来沉淀&#xff0c;你可以上班之外做自媒体。 2、一些技能的缺失。如果你不会拍摄不会剪辑&#xff0c;你就去学习&#xff0c;现在B站或者各大平台&#xff0c;都有很多的课程&#xff0c;免费的先学起。谨防被割韭菜。 3、以前觉…

服务器白名单

服务器白名单是一种安全措施&#xff0c;用于限制谁可以访问服务器资源。它定义了一组被允许访问或执行特定操作的IP地址、用户或设备。通过仅允许这些已验证和信任的实体进行连接&#xff0c;白名单可以有效地提高服务器的安全性和控制。 白名单的应用场景包括&#xff1a; 网…

光栅化渲染的光照参数

在基于光栅化的渲染中&#xff0c;光照通常是通过一些简化的参数来模拟的&#xff0c;这些参数包括&#xff1a; 环境光&#xff08;Ambient Light&#xff09;&#xff1a;环境光是来自场景中所有方向的均匀光&#xff0c;它模拟了间接光照的效果&#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;覆盖多…

处理VS2022中(C/C++)scanf报错问题(3种)

#pragma warning(disable:4996)//第一种&#xff1a;处理scanf在VS2022中报错 #define _CRT_SECURE_NO_WARNINGS//第二种:处理scanf在VS2022中报错 #include<bits/stdc.h> using namespace std; int main() { int a, b; scanf(“%d %d”, &a, &b);//第三种&…

Vue.js:构建现代Web应用的框架

Vue.js是一个开源的JavaScript框架&#xff0c;用于构建用户界面和单页面应用程序&#xff08;SPA&#xff09;。它由尤雨溪创建&#xff0c;以其轻量级、易用性和灵活性而闻名。本文将详细介绍Vue.js的核心概念、组件系统、响应式数据绑定以及在现代Web开发中的应用。 1. 引言…

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

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

【Linux之升华篇】Linux内核锁、用户模式与内核模式、用户进程通讯方式

文章目录 Linux 中主要几种内核锁Linux 中的用户模式和内核模式申请大块内核内存用户进程间通信的几种方式通过伙伴系统申请内核内存的函数Linux 虚拟文件系统的关键数据结构对文件或设备的操作函数的数据结构Linux 中的文件创建进程的系统调用调用 schedule()进行进程切换的方…

阿里云域名备案流程

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