WebSocket与Socket.IO实现简易客服聊天系统全解析

news2025/5/16 1:54:53

WebSocket结合Socket.IO实现简易客服聊天系统全解析

一、技术选型对比

技术优点缺点适用场景
原生WebSocket浏览器原生支持,性能好API较底层,需手动处理断线重连等逻辑简单实时应用
Socket.IO自动重连,房间管理,兼容性好体积较大,有一定学习成本复杂实时应用,生产环境

为什么选择Socket.IO?

  • 内置断线自动重连
  • 支持房间/命名空间管理
  • 兼容老旧浏览器(自动降级)
  • 丰富的API和社区支持

二、核心实现流程

2.1 系统架构图

前端浏览器
  ↑↓ HTTP/WebSocket
Node.js服务器(Socket.IO)
  ↑↓ 数据库(可选)
消息持久化存储

2.2 前后端交互时序图

前端 后端 所有前端 指定前端 建立WebSocket连接 发送login事件(用户名) 返回login_success 广播user_list 发送private_message 定向转发消息 断开连接 更新user_list 前端 后端 所有前端 指定前端

三、前端代码详解

3.1 核心功能实现

功能代码片段说明
建立连接socket = io('http://localhost:3000')连接到指定服务器的Socket.IO服务
用户登录socket.emit('login', username)发送登录事件,携带用户名
接收消息socket.on('message', (data) => {...})监听服务器推送的消息
发送私聊socket.emit('private_message', {to, message})发送私聊消息到指定用户
用户列表更新socket.on('user_list', (users) => {...})实时更新在线用户列表

3.2 页面结构分析

<div class="container">
    <!-- 登录界面 -->
    <div id="login">
        <input type="text" id="username">
        <button onclick="login()">登录</button>
    </div>

    <!-- 聊天界面 -->
    <div id="chat" style="display: none;">
        <h3>当前用户:<span id="currentUser"></span></h3>
        <div>
            <h4>在线用户</h4>
            <ul id="userList"></ul>
        </div>
        <div id="messages"></div>
        <div>
            <select id="receiver">
                <option value="">选择接收人</option>
            </select>
            <input type="text" id="messageInput">
            <button onclick="sendMessage()">发送</button>
        </div>
    </div>
</div>

3.3 js功能实现

先声明全局变量:
// 2.1 全局变量声明
 let socket;       // 用于存储Socket.IO连接实例
 let currentUser;  // 存储当前登录的用户名
用户登录,即进入房间:
// 2.2 登录功能函数
        function login() {
            // 获取用户名输入框的值并去除前后空格
            const username = document.getElementById('username').value.trim();
            // 如果用户名为空,直接返回
            if (!username) return;
    
            // 2.3 建立Socket.IO连接(连接到本地3000端口)
            socket = io('http://localhost:3000');
            // 存储当前用户名
            currentUser = username;
    
            // 2.4 Socket.IO事件监听
            // 连接成功事件
            socket.on('connect', () => {
                // 向服务器发送登录事件,传递用户名
                socket.emit('login', username);
            });
    
            // 登录成功事件
            socket.on('login_success', () => {
                // 隐藏登录界面,显示聊天界面
                document.getElementById('login').style.display = 'none';
                document.getElementById('chat').style.display = 'block';
                // 显示当前用户名
                document.getElementById('currentUser').textContent = username;
            });
    
            // 登录错误事件
            socket.on('login_error', (err) => {
                // 显示错误信息
                alert(err);
            });
    
            // 用户列表更新事件
            socket.on('user_list', (users) => {
                // 获取用户列表和接收者选择框的DOM元素
                const list = document.getElementById('userList');
                const receiver = document.getElementById('receiver');
                
                // 清空用户列表和接收者选择框
                list.innerHTML = '';
                receiver.innerHTML = '<option value="">选择接收人</option>';
                
                // 2.5 遍历用户列表,更新界面
                users.forEach(user => {
                    // 不显示当前用户自己
                    if (user !== currentUser) {
                        // 添加到在线用户列表
                        const li = document.createElement('li');
                        li.textContent = user;
                        list.appendChild(li);
    
                        // 添加到接收者选择框
                        const option = document.createElement('option');
                        option.value = user;
                        option.textContent = user;
                        receiver.appendChild(option);
                    }
                });
            });
    
            // 收到消息事件
            socket.on('message', (data) => {
                // 获取消息显示区域的DOM元素
                const messagesDiv = document.getElementById('messages');
                // 创建消息元素
                const msgEl = document.createElement('div');
                // 设置消息样式类,如果是自己发送的消息添加self类
                msgEl.className = `message ${data.self ? 'self' : ''}`;
                // 设置消息内容格式:[发送者 → 接收者]: 消息内容
                msgEl.textContent = `[${data.from}${data.to}]: ${data.message}`;
                // 将消息添加到消息显示区域
                messagesDiv.appendChild(msgEl);
                // 自动滚动到底部
                messagesDiv.scrollTop = messagesDiv.scrollHeight;
            });
    
            // 错误事件
            socket.on('error', (err) => {
                // 显示错误信息
                alert(err);
            });
        }
用户发送消息:
		// 2.6 发送消息函数
        function sendMessage() {
            // 获取接收者和消息内容
            const to = document.getElementById('receiver').value;
            const message = document.getElementById('messageInput').value.trim();
            
            // 如果接收者或消息为空,直接返回
            if (!to || !message) return;
    
            // 向服务器发送私聊消息
            socket.emit('private_message', {
                to,        // 接收者
                message    // 消息内容
            });
    
            // 清空消息输入框
            document.getElementById('messageInput').value = '';
        }

四、后端代码详解

为了实现1对1的聊天功能 to().emait('',data) ,我们需要知道你要发送的那个人的 socket.id ,这里就需要对每个登录的人与他的socket.id进行关联,因为 socket.id 前端不知道,所以后端需要创建一个new Map对象对用户的name进行映射,用户的name对应它的socket.id。在发消息的时候只需要知道要发的人的name即可进行get从map对象中获取对应的socket.id,从而实现一对一聊天

4.1 主要事件处理

事件类型处理逻辑业务作用
connection初始化socket连接,打印日志新客户端连接处理入口
login检查用户名冲突,更新users映射,广播user_list用户身份认证和在线状态管理
private_message查找接收者socket.id,分别向发送方和接收方发送消息实现私聊消息转发
disconnect从users映射中移除用户,广播更新后的user_list处理用户离线状态

4.2 环境配置

// 1.1 引入必要的Node.js模块
const http = require('http');       // HTTP服务器模块
const socketio = require('socket.io'); // Socket.IO模块

// 1.2 创建基本的HTTP服务器
const server = http.createServer((req, res) => {
    // 对所有HTTP请求返回简单的HTML响应
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h1>Chat Server</h1>');
}).listen(3000, () => {
    // 服务器启动后打印日志
    console.log("Server running at http://localhost:3000");
});

// 1.3 创建Socket.IO服务器并配置CORS
const io = socketio(server, {
    cors: {
        origin: "*", // 允许所有来源的跨域请求
        methods: ["GET", "POST"] // 允许的HTTP方法
    }
});

4.3 用户信息管理

// 2.1 创建用户映射表
// 使用Map存储用户名到socket.id的映射关系
// 结构: { 用户名 => socket.id }
const users = new Map();

4.4 具体功能实现

// 3.1 监听客户端连接事件
io.on('connection', (socket) => {
    // 当新客户端连接时打印日志
    console.log(`现在登录的用户socketid: ${socket.id}`);

    // 3.2 处理用户登录
    socket.on('login', (username) => {
        // 检查用户名是否已被占用
        if (users.has(username)) {
            // 发送登录错误消息
            socket.emit('login_error', '当前用户名已经被注册');
            return;
        }

        // 3.3 存储用户信息
        users.set(username, socket.id); // 将用户名和socket.id存入Map
        socket.username = username;     // 将用户名附加到socket对象上

        // 3.4 通知所有客户端更新用户列表
        // Array.from(users.keys()) 获取所有用户名数组
        io.emit('user_list', Array.from(users.keys()));

        // 通知当前客户端登录成功
        socket.emit('login_success');
    });

    // 3.5 处理私聊消息
    socket.on('private_message', ({ to, message }) => {
        // 参数解构: to是接收者用户名, message是消息内容

        // 3.6 查找接收者的socket.id
        const targetSocketId = users.get(to);

        // 检查接收者是否存在
        if (!targetSocketId) {
            socket.emit('error', '用户未找到');
            console.log("用户未找到");
            return;
        }

        // 3.7 发送消息给发送者(自己)
        socket.emit('message', {
            from: socket.username, // 发送者
            to: to,               // 接收者
            message: message,     // 消息内容
            self: true            // 标记为自己发送的消息
        });

        // 3.8 发送消息给接收者
        // socket.to()方法指定接收者的socket.id
        socket.to(targetSocketId).emit('message', {
            from: socket.username,
            to: to,
            message: message,
            self: false           // 标记为他人发送的消息
        });
    });

    // 3.9 处理断开连接
    socket.on('disconnect', () => {
        // 检查是否是已登录用户断开连接
        if (socket.username) {
            // 3.10 从用户映射中移除该用户
            users.delete(socket.username);

            // 3.11 通知所有客户端更新用户列表
            io.emit('user_list', Array.from(users.keys()));

            // 打印断开连接日志
            console.log(`用户断开连接: ${socket.username}`);
        }
    });
});

五、.on和.emit方法解惑

5.1 Socket.io 前端视角的 onemit 方法对比

从前端(客户端)的角度来看,socket.on()socket.emit() 的使用方式与后端类似,但角色和典型场景有所不同。以下是前端视角的对比表格:

特性socket.on('event', callback) (前端)socket.emit('event', data) (前端)
用途监听服务器发送的事件向服务器发送事件
方向接收服务器数据向服务器发送数据
回调函数有,处理服务器推送的数据无,只发送数据
作用范围监听特定服务器事件向服务器发送特定事件
是否等待响应被动接收服务器消息主动向服务器发起请求
典型使用场景接收通知、更新、广播消息等发送用户操作、请求数据等
**socket.on('event', callback) **
  • 用于监听服务器发送的事件
  • 当服务器触发对应事件时,回调函数会执行
  • 典型前端使用场景:
    • 接收实时通知
    • 获取数据更新
    • 监听广播消息
  • 示例:
// 监听服务器发送的消息
socket.on('newMessage', (message) => {
  console.log('收到新消息:', message);
  // 更新UI显示新消息
});

// 监听服务器广播
socket.on('userJoined', (username) => {
  console.log(`${username} 加入了聊天室`);
});
socket.emit('event', data)
  • 用于向服务器发送事件和数据
  • 可以带参数发送给服务器
  • 典型前端使用场景:
    • 发送用户操作
    • 请求特定数据
    • 提交表单等
  • 示例:
// 发送聊天消息
socket.emit('sendMessage', {
  text: '你好!',
  room: 'general'
});

// 请求加入房间
socket.emit('joinRoom', {
  roomId: '123',
  userId: 'user456'
});

5.2 Socket.io 的 onemit 方法对比

是的,socket.on()socket.emit() 在 Socket.io 中有不同的用途。下面是一个对比表格:

特性socket.on('event', callback)socket.emit('event', data)
用途注册/监听事件触发/发送事件
方向接收数据发送数据
回调函数有,处理接收的数据无,只发送数据
作用范围监听特定事件向特定目标发送事件
是否等待响应被动等待主动触发
典型使用场景服务器监听客户端请求客户端或服务器发送消息
socket.on('event', callback)
  • 用于注册事件监听器,等待接收特定事件
  • 当对应的事件被触发时,回调函数会被执行
  • 示例:
// 服务器端监听登录事件
socket.on('login', (credentials) => {
  console.log('收到登录请求:', credentials);
  // 验证逻辑...
});
socket.emit('event', data)
  • 用于触发事件并发送数据
  • 可以向特定客户端或所有客户端广播消息
  • 示例:
// 客户端发送登录请求
socket.emit('login', { username: 'user', password: 'pass' });

// 服务器端广播消息给所有客户端
io.emit('message', '大家好!');

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

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

相关文章

第26节:卷积神经网络(CNN)-数据增强技术(PyTorch)

1. 引言 在深度学习领域,数据增强(Data Augmentation)是提升卷积神经网络(CNN)性能的关键技术之一。通过人为地扩展训练数据集,数据增强能够有效提高模型的泛化能力,防止过拟合,特别是在训练数据有限的情况下。本文将全面介绍PyTorch框架下的数据增强技术,包括基本原理、…

求助求助,重金酬谢

如图&#xff0c;我先在服务器上运行一个 dock 容器&#xff0c;然后用 nohup 命令把 auto_run.py 程序挂起&#xff0c;然后我查了一下是在 12 端口运行的&#xff0c;这时候我关闭命令窗口&#xff0c;我再重新打开运行 docker 容器就找不到挂起的进程了&#xff01;这是为什…

Axure :基于中继器的列表删除 、 列表编辑

文章目录 I 列表删除思路操作说明II 列表编辑功能思路修改按钮的交互操作说明编辑页面的保存按钮交互设置取消标记I 列表删除 思路 中继器删除行交互事件; 操作说明 在操作列中添加删除标签,同步添加鼠标点击交互事件 在交互事件中插入中继器删除行动作 多选删除,勾选已标…

基于GPUGEEK 平台进行深度学习

一、平台简介 GPUGEEK 是一个专注于提供 GPU 算力租赁服务的平台&#xff0c;在人工智能与深度学习领域为用户搭建起便捷的算力桥梁。它整合了丰富多样的 GPU 资源&#xff0c;涵盖 RTX - 4090、RTX - 3090、A100 - PCIE 等多种型号&#xff0c;满足不同用户在模型训练、数据处…

【多模态】IMAGEBIND论文阅读

every blog every motto: Although the world is full of suffering&#xff0c; it is full also of the overcoming of it 0. 前言 IMAGEBIND 多模态论文梗概 IMAGEBIND是一种夸模态的神经网络&#xff0c;以图片为中心&#xff0c;联合六中模态的网络&#xff08;图片、文…

LeetCode LCR 007. 三数之和 (Java)

题目描述 给定一个整数数组 nums&#xff0c;判断是否存在三个元素 a, b, c&#xff0c;使得 a b c 0&#xff1f;找出所有满足条件且不重复的三元组。 解题思路 核心方法&#xff1a;排序 双指针 排序&#xff1a;首先将数组排序&#xff0c;便于后续去重和双指针操作。…

VTK|类似CloudCompare的比例尺实现1-源码分析

文章目录 CloudCompare源码分析void ccGLWindowInterface::drawScale(const ccColor::Rgbub& color)&#x1f9e9; 总体功能&#x1f9e0; 函数逐步解析✅ 1. 断言只在正交模式下使用✅ 2. 计算显示的实际长度✅ 3. 字体和图形区域准备✅ 4. 计算比例尺图形的绘制位置✅ 5.…

电子电器架构 --- 车载以太网拓扑

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…

phpstorm2024.3 设置中文

要在 PhpStorm 2024.3 中设置中文界面&#xff0c;你可以按照以下步骤进行操作。请注意&#xff0c;PhpStorm 2024.3 版本可能已经包括了中文语言包&#xff0c;但如果你使用的是较早的版本&#xff0c;可能需要下载额外的语言包。 方法一&#xff1a;直接在设置中切换&#x…

vxe-table 同时实现合并单元格与任意列展开行

前一段时间有一个需求&#xff0c;要求既要合并单元格&#xff0c;又要实现树状图的效果&#xff0c;但是展开节点tree-node 可以放在非第一列的任意位置&#xff0c;Vxe-table可以实现如下是效果图&#xff1a; 大家可以一起交流学习&#xff01; ~重点注意事项&#xff1a;…

ArcGIS Desktop使用入门(二)常用工具条——图形

系列文章目录 ArcGIS Desktop使用入门&#xff08;一&#xff09;软件初认识 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——标准工具 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——编辑器 ArcGIS Desktop使用入门&#xff08;二&#x…

神经网络语言模型(前馈神经网络语言模型)

神经网络语言模型 什么是神经网络&#xff1f;神经网络的基本结构是什么&#xff1f;输入层隐藏层输出层 神经网络为什么能解决问题&#xff1f;通用近似定理为什么需要权重和偏置&#xff1f;为什么需要激活函数&#xff1f;权重是如何确定的&#xff1f;1. 穷举2. 反向传播主…

CUDA编程——性能优化基本技巧

本文主要介绍下面三种技巧&#xff1a; 使用 __restrict__ 让编译器放心地优化指针访存想办法让同一个 Warp 中的线程的访存 Pattern 尽可能连续&#xff0c;以利用 Memory coalescing使用 Shared memory 0. 弄清Kernael函数是Compute-bound 还是 Memory-bound 先摆出一个知…

道通EVO MAX系列无人机-支持二次开发

道通EVO MAX系列无人机-支持二次开发 EVO Max 系列采用Autel Autonomy自主飞行技术&#xff0c;实现复杂环境下的全局路径规划、3D场景重建、自主绕障和返航&#xff1b;高精度视觉导航能力&#xff0c;使其在信号干扰强、信号遮挡、信号弱等复杂环境下&#xff0c;依然获得高精…

计算机网络-MPLS LDP基础实验配置

前面我们学习了LDP的会话建立、标签发布与交换、LDP的工作原理&#xff0c;今天通过一个基础实验来加深记忆。 一、LDP基础实验 实验拓扑&#xff1a; 1、IGP使用OSPF进行通告&#xff0c;使用Lookback接口作为LSR ID&#xff0c;LDP ID自动生成。 2、实验目的&#xff1a;使…

HPE ProLiant DL360 Gen11 服务器,配置 RAID 5 教程!

今天的任务&#xff0c;是帮客户的一台HPE ProLiant DL360 Gen11 服务器&#xff0c;配置RAID 5。依然是按照我的个人传统习惯&#xff0c;顺便做一个教程&#xff0c;分享给有需要的粉丝们。如果你在实际操作中&#xff0c;遇到了什么问题&#xff0c;欢迎在评论区留言&#x…

SARIMA-LSTM融合模型对太阳黑子数量预测分析|附智能体数据代码

全文智能体链接&#xff1a;https://tecdat.cn/?p41969 分析师&#xff1a;Peng Fan 本研究以太阳黑子活动数据为研究对象&#xff0c;旨在帮助客户探索其未来走势并提供预测分析。首先&#xff0c;通过对数据的清洗和处理&#xff0c;包括离群值的识别与处理以及时间序列的建…

C# WinForm DataGridView 非常频繁地更新或重新绘制慢问题及解决

非常频繁地更新 DataGridView问题描述&#xff1a; 在 C# 中无法在合理的时间内刷新我的 DataGridView &#xff0c;我每秒通过网络发送 20 个数据包&#xff0c;获取数据。我想解析这些数据并将其放入 DataGridView 中。我还想调整 DataGridView 的更新间隔&#xff0c;从 0.1…

【数据结构】红黑树(C++)

目录 一、红黑树的概念 二、红黑树的性质 三、红黑树结点定义 四、红黑树的操作 1. 插入操作 1.1 插入过程 1.2 调整过程 1.2.1 叔叔节点存在且为红色 1.2.2 叔叔节点存在且为黑色 1.2.3 叔叔节点不存在 2. 查找操作 2.1 查找逻辑 2.2 算法流程图 2.3 使用示例 …

Android Framework学习五:APP启动过程原理及速度优化

文章目录 APP启动优化概述APP启动流程点击图片启动APP的过程启动触发Zygote 与应用进程创建Zygote进程的创建应用进程初始化 ApplicationActivity 启动与显示 优化启动时黑白屏现象可优化的阶段Application阶段相关优化 Activity阶段数据加载阶段 Framework学习系列文章 APP启动…