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

news2024/11/18 10:49:36

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

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

实现im聊天

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

放置后端代码

package main

import (
	"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.Conn
	DataQueue 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.RWMutex

func 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 := true
	conn, err := (&websocket.Upgrader{ //将 HTTP 连接升级为 WebSocket 连接
		//CheckOrigin 函数用于检查请求的来源是否合法,即允许跨域访问
		CheckOrigin: func(r *http.Request) bool {
			return isvalida
		},
	}).Upgrade(writer, request, nil) //request 参数包含了客户端发起连接时提供的 URL 信息ws://localhost:8088/userid

	if err != nil {
		log.Println("升级为WebSocket连接失败:", err)
		return
	}
	//2.获取conn
	node := &Node{
		Conn:      conn,
		DataQueue: make(chan []byte, 50),
		GroupSets: set.New(set.ThreadSafe),
	}
	defer conn.Close()

	//4. userid 跟 node绑定 并加锁  用程序的读写锁抢占线程资源
	rwLocker.Lock()
	clientMap[userId] = node
	rwLocker.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 Message
		err = 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来找到目标用户的node
	node, 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.coloradmin.cn/o/1664942.html

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

相关文章

半小时搞懂STM32面经知识点——系统架构与启动流程

1.Cortex-M系统 1.1系统结构 1.处理器核心&#xff1a; Cortex-M3 2.存储器系统&#xff1a; Flash&#xff0c;SRAM&#xff0c;FSMC等 3.总线接口&#xff1a; 核心通过总线接口与外设设备和存储器进行通信。 总线矩阵&#xff1a;总线矩阵是一种硬件结构&#xff0c;用于连…

libcity笔记:

1 __init__ 2 encode 得到的内容如下&#xff1a; data_feature的内容&#xff1a; 一共有多少个location1【包括pad的一个】最长的时间间隔&#xff08;秒&#xff09;最长的距离间隔&#xff08;千米&#xff09;多少个useer idpadding 的locationidpad_item的内容 location…

社交媒体数据恢复:飞书

飞书数据恢复过程包括以下几个步骤&#xff1a; 确认数据丢失&#xff1a;首先要确认数据是否真的丢失&#xff0c;有时候可能只是被隐藏或者误操作删除了。 检查回收站&#xff1a;飞书中删除的文件会默认保存在回收站中&#xff0c;用户可以通过进入回收站找到被删除的文件&…

【北京迅为】《iTOP-3588从零搭建ubuntu环境手册》-第5章 安装SSH

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

C++中调用python函数(VS2017+WIN10+Anaconda虚拟环境)

1.利用VS创建C空项目 step1 文件——新建——项目 step2 Visual C—— Windows桌面——Windows桌面向导 step3 选择空项目 step4 源文件——新建项——添加 step5 Visual C——C文件&#xff08;.cpp&#xff09; 2.配置环境 Step1. 更换成Release与X64 Step2. 打开项目属性&…

2 GPIO控制

ESP32的GPIO的模式&#xff0c;一共有输入和输出模式两类。其中输入模式&#xff1a;上拉输入、下拉输入、浮空输入、模拟输入&#xff1b;输出模式&#xff1a;输出模式、开漏输出&#xff08;跟stm32八种输入输出模式有所不同&#xff09;。库函数中控制引脚的函数如下&#…

20240511,谓词,内建函数对象

拜托铠甲勇士真的帅好不好&#xff01;&#xff01;&#xff01; STL案例2-员工分组 10个员工&#xff0c;指派部门&#xff0c;员工信息&#xff08;姓名&#xff0c;工资组成&#xff0c;部门&#xff1a;策划&#xff0c;美术&#xff0c;研发&#xff09;&#xff0c;随机…

量子波函数白话解释

关键词&#xff1a;Quantum Wave Function 文章目录 一、说明二、什么是波函数&#xff1f;三 量子波的可视化四、量子波的概率解释 一、说明 在量子力学中&#xff0c;粒子是我们只有在测量它们时才能看到的东西。其中运动模式由满足薛定谔方程的波函数描述。波函数并非量子…

基于Huffman编码的字符串统计及WPL计算

一、问题描述 问题概括&#xff1a; 给定一个字符串或文件&#xff0c;基于Huffman编码方法&#xff0c;实现以下功能&#xff1a; 1.统计每个字符的频率。 2.输出每个字符的Huffman编码。 3.计算并输出WPL&#xff08;加权路径长度&#xff09;。 这个问题要求对Huffman编码算…

AppBuilder低代码体验:构建雅思大作文组件

AppBuilder低代码体验&#xff1a;构建雅思大作文组件 ​ 在4月14日&#xff0c;AppBuilder赢来了一次大更新&#xff0c;具体更新内容见&#xff1a;AppBuilder 2024.04.14发版上线公告 。本次更新最大的亮点就是**新增了工作流&#xff0c;低代码制作组件。**具体包括&#x…

[Cmake Qt]找不到文件ui_xx.h的问题?有关Qt工程的问题,看这篇文章就行了。

前言 最近在开发一个组件&#xff0c;但是这个东西是以dll的形式发布的界面库&#xff0c;所以在开发的时候就需要上层调用。 如果你是很懂CMake的话&#xff0c;ui_xx.h的文件目录在 ${CMAKE_CURRENT_BINARY_DIR} 下 然后除了有关这个ui_xx.h&#xff0c;还有一些别的可以简…

付费文章合集第二期

☞☞付费文章合集第一期 感谢大家一年来的陪伴与支持&#xff01; 对于感兴趣的文章点标题能跳转原文阅读啦~~ 21、Matlab信号处理——基于LSB和DCB音频水印嵌入提取算法 22、CV小目标识别——AITOD数据集&#xff08;已处理&#xff09; 23、Matlab信号发生器——三角波、…

OSError: [WinError 1455] 页面文件太小,无法完成操作 的问题

实质问题是报错&#xff1a;caffe2_detectron_ops.dll“ or one of its dependencies 还需要安装一个包&#xff1a; pip install intel-openmp 安装之后顺利测试通过。

设计模式之数据访问对象模式

在Java编程的浩瀚星海中&#xff0c;有一个模式低调却强大&#xff0c;它像是一位默默无闻的超级英雄&#xff0c;支撑起无数应用的数据脊梁——那就是数据访问对象&#xff08;DAO, Data Access Object&#xff09;模式&#xff01;想象一下&#xff0c;如果你能像操纵魔法一样…

三大消息传递机制区别与联系

目录 总结放开头 1、定义区别&#xff1a; EventBus Broadcast Receiver Notification 2、使用区别: EventBus Broadcast Receiver Notification 3、补充通知渠道&#xff1a; 通知渠道重要程度 总结放开头 BroadCast Receiver:属于安卓全局监听机制&#xff0c;接收…

Linux下安装mysql8.0(以rpm包安装)

前言&#xff1a;原文在我的博客网站中&#xff0c;持续更新数通、系统方面的知识&#xff0c;欢迎来访&#xff01; Linux下安装mysql8.0&#xff08;以rpm包安装&#xff09;https://myweb.myskillstree.cn/125.html 目录 1、查操作系统信息 2、下载mysql 8.0.34的rpm包 …

JWT深入浅出

文章目录 JWT深入浅出1.JWT是什么2.为什么选JWT2.1 传统Session认证2.2 JWT认证 3.JWT怎么用4. jwt绝对安全吗&#xff1f; JWT深入浅出 1.JWT是什么 JWT&#xff08;JSON Web Token&#xff09;是一种用于在网络应用间传递信息的开放标准&#xff0c;通常用于身份认证和非敏…

LagentAgentLego智能体工具使用

1. lagent 参考文档 https://github.com/InternLM/Tutorial/blob/camp2/agent/lagent.md 使用 LMDeploy 部署 conda activate agent lmdeploy serve api_server /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-7b \--server-name 127.0.0.1 \--model-name in…

WebSocket前后端建立以及使用

1、什么是WebSocket WebSocket 是一种在 Web 应用程序中实现双向通信的协议。它提供了一种持久化的连接&#xff0c;允许服务器主动向客户端推送数据&#xff0c;同时也允许客户端向服务器发送数据&#xff0c;实现了实时的双向通信。 这部分直接说你可能听不懂&#xff1b;我…