netty-socketio和Socket.IO实现网页在线聊天功能

2024-09-05 23:28

本文主要是介绍netty-socketio和Socket.IO实现网页在线聊天功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.前端框架 Socket.IO

官方文档:https://socket.io/docs/v4/client-api/#iourl

Socket.IO 是一个库,它支持客户端和服务器之间的低延迟、双向和基于事件的通信。

Socket.IO连接可以通过不同的底层传输建立:

  • HTTP长轮询
  • WebSocket
  • WebTransport
    Socket.IO将自动选择最佳可用选项

优点:

  • 在WebSocket连接无法建立的情况下,连接将退回到HTTP长轮询
  • 当客户端最终断开连接时,为了不使服务器不堪重负,它会自动以指数级的后退延迟重新连接。
  • 当客户端断开连接时,报文将被自动缓冲,并在重新连接时发送。

2. java后端框架 netty-socketio

特点:

  • 支持xhr-polling transport(xhr轮询传输)
  • 支持websocket传输
  • 支持namespaces and rooms
  • 支持返回(接收数据的确认)
  • 支持SSL
  • 支持客户端存储(Memory, Redisson, Hazelcast)
  • 支持跨网络socket节点的分布式广播(Redisson, Hazelcast)
  • 支持OSGi
  • 支持Spring
  • 包含JPMS的Java模块信息。
  • 无锁和线程安全的实现
  • 通过注解进行声明式处理程序配置

3.java服务端实现

3.1 创建maven项目netty-socketio-learn

maven pom.xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>netty-socketio-learn</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><hutool.version>5.8.11</hutool.version><spring.boot.version>3.3.1</spring.boot.version></properties><dependencyManagement><dependencies><!-- 统一依赖管理 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring.boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><!-- netty-socketio--><dependency><groupId>com.corundumstudio.socketio</groupId><artifactId>netty-socketio</artifactId><version>2.0.11</version></dependency><!-- hutool-all--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>${hutool.version}</version></dependency><!-- Web 相关 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies><!-- 使用 huawei / aliyun 的 Maven 源,提升下载速度 --><repositories><repository><id>huaweicloud</id><name>huawei</name><url>https://mirrors.huaweicloud.com/repository/maven/</url></repository><repository><id>aliyunmaven</id><name>aliyun</name><url>https://maven.aliyun.com/repository/public</url></repository><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>false</enabled></releases></repository></repositories></project>

3.2 ChatServer实现

package cn.netty.socketio.learn;import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.StrUtil;
import com.corundumstudio.socketio.*;
import com.corundumstudio.socketio.store.MemoryStoreFactory;
import com.corundumstudio.socketio.store.StoreFactory;import java.util.concurrent.atomic.AtomicInteger;/*** ChatServer*/
public class ChatServer {private static final AtomicInteger USER_NUMBER = new AtomicInteger();/*** host name*/private static final String HOST_NAME = "localhost";/*** port*/private static final int PORT = 9093;private static final String USER_NAME = "userName";private static final String USER_ID = "userId";public static void main(String[] args) {Configuration config = new Configuration();config.setHostname(HOST_NAME);config.setPort(PORT);// client 存储工厂StoreFactory storeFactory = new MemoryStoreFactory();config.setStoreFactory(storeFactory);// 解决对此重启服务时,netty端口被占用问题 重复使用地址config.getSocketConfig().setReuseAddress(true);// 支持在线人数config.setWorkerThreads(100);// tcpNoDelay (boolean):// 如果设置为true,则禁用Nagle算法,这可以减少小数据包的延迟。// 基本原理:Nagle算法试图将多个小的数据包合并成一个较大的数据包再发送出去,这样可以减少网络中的数据包数量,// 从而提高带宽利用率。当应用发送少量数据时,TCP并不会立即将这些数据发送出去,而是等待一段时间(比如等待应用发送更多的数据),直到数据量达到一定阈值或超时后再一起发送// tcpSendBufferSize (int):// 设置发送缓冲区大小。如果设置为-1,则使用系统默认值。// tcpReceiveBufferSize (int):// 设置接收缓冲区大小。如果设置为-1,则使用系统默认值。// tcpKeepAlive (boolean):// 如果设置为true,则启用TCP的保活机制,可以在长时间没有数据传输后检测连接是否仍然有效。// soLinger (int):// 设置关闭连接前等待未发送完的数据的时间。如果是-1,则立即关闭连接而不等待。// reuseAddress (boolean):// 如果设置为true,则允许重用地址和端口,即使之前绑定过这个端口也行。// acceptBackLog (int):// 设置监听队列长度,即内核为该socket保留的已完成三次握手但尚未被accept()接受的连接的最大数目。final SocketIOServer server = new SocketIOServer(config);// 设置命名空间 前端socket连接地址:http://localhost:9093/chatSocketIONamespace socketIONamespace = server.addNamespace("/chat");// token验证socketIONamespace.addAuthTokenListener((authToken, client) -> {// 根据token获取用户信息System.out.println("token:" + authToken);Integer userId = USER_NUMBER.incrementAndGet();// 设置用户信息client.set(USER_ID, userId);String userName = getUserName(userId);client.set(USER_NAME, userName);System.out.println(StrUtil.format("token:{},用户:{} 验证成功!", authToken, userName));return AuthTokenResult.AuthTokenResultSuccess;});// 创建链接socketIONamespace.addConnectListener(client -> {String userName = client.get(USER_NAME);System.out.println(StrUtil.format("用户:{} 连接成功!", userName));});// 用户信息socketIONamespace.addEventListener("user", Object.class, (client, data, ackSender) -> {String userName = client.get(USER_NAME);// 返回用户昵称ackSender.sendAckData(userName);});// 消息事件处理socketIONamespace.addEventListener("message", Object.class, (client, data, ackSender) ->Opt.ofEmptyAble(socketIONamespace.getAllClients()).ifPresent(clients -> {// 发送消息给所有用户socketIONamespace.getBroadcastOperations().sendEvent("message", data);}));// 用户主动退出处理socketIONamespace.addDisconnectListener(client -> {String userName = client.get(USER_NAME);System.out.println(StrUtil.format("用户:{} 断开连接!", userName));});server.start();}/*** 获取用户名称** @param userId* @return*/private static String getUserName(Integer userId) {Assert.notNull(userId);return "用户" + userId;}
}

4. vue3客户端实现

使用Vue 3和socket.io-client来实现一个网页在线聊天应用:
vue3安装教程:https://socket.io/docs/v4/client-installation/

4.1 核心代码Chat.vue

<template><div><h1>在线聊天室</h1><ul><li v-for="message in messages" :key="message.id">{{ message.nickName }}: {{ message.text }}</li></ul><input v-model="newMessage" @keyup.enter="sendMessage"><button @click="sendMessage">发送</button></div>
</template><script setup>
import {ref, onMounted} from 'vue';
import {Manager} from "socket.io-client";
// 连接地址
const URL = process.env.NODE_ENV === "production" ? undefined : "http://localhost:9093";
// 如果管理器初始化时autoConnect设置为false,则启动新的连接尝试。
const manager = new Manager(URL, { autoConnect: true });
const socket = manager.socket("/chat", { auth: { token: "123" } });// 数据绑定
const messages = ref([]);
const newMessage = ref('');
const nickName = ref('');// 初始化 Socket.IO 连接
onMounted(() => {// 连接成功事件socket.on('connect', () => {console.log('Connected to the server');socket.emit('user', nickName.value, (response) => {nickName.value = response;});});// 接收消息事件socket.on('message', (msg) => {messages.value.push(msg);});// 断开连接事件socket.on('disconnect', () => {console.log('Disconnected from the server');});
});// 发送消息方法
const sendMessage = () => {if (newMessage.value.trim() !== '') {socket.emit('message', {text: newMessage.value, id: Date.now(), nickName: nickName.value});newMessage.value = '';}
};
</script><style scoped>
ul {list-style-type: none;padding: 0;
}li {margin-bottom: 5px;
}input[type="text"] {padding: 5px;margin-right: 5px;
}button {padding: 5px 10px;
}
</style>

5.运行效果

浏览器1:
在这里插入图片描述
浏览器2:
在这里插入图片描述

6.spring boot3和netty-socketio整合

6.1 application.yaml配置

server:port: 8089spring:application:name: chat-servermain:allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。# Servlet 配置servlet:# 文件上传相关配置项multipart:max-file-size: 16MB # 单个文件大小max-request-size: 32MB # 设置总上传的文件大小# Jackson 配置项jackson:serialization:write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳fail-on-empty-beans: false # 允许序列化无属性的 Beandebug: false

6.2 ChatSocketIOServer.java

package cn.netty.socketio.learn;import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.StrUtil;
import com.corundumstudio.socketio.*;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.corundumstudio.socketio.annotation.OnEvent;
import com.corundumstudio.socketio.store.MemoryStoreFactory;
import com.corundumstudio.socketio.store.StoreFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicInteger;/*** ChatSocketIOServer* spring boot 应用启动后立即执行一些初始化任务*/
@Component
public class ChatSocketIOServer implements CommandLineRunner {private final SocketIOServer server;private final SocketIONamespace socketIONamespace;public ChatSocketIOServer() {// 创建 SocketIOServerserver = new SocketIOServer(buildConfig());// 创建 socketIONamespacesocketIONamespace = server.addNamespace("/chat");}@Beanpublic EventHandler eventHandler() {return new EventHandler(socketIONamespace);}private static final AtomicInteger USER_NUMBER = new AtomicInteger();/*** host name*/private static final String HOST_NAME = "localhost";/*** port*/private static final int PORT = 9093;private static final String USER_NAME = "userName";private static final String USER_ID = "userId";public Configuration buildConfig() {Configuration config = new Configuration();config.setHostname(HOST_NAME);config.setPort(PORT);// client 存储工厂StoreFactory storeFactory = new MemoryStoreFactory();config.setStoreFactory(storeFactory);// 解决对此重启服务时,netty端口被占用问题 重复使用地址config.getSocketConfig().setReuseAddress(true);// 支持在线人数config.setWorkerThreads(100);// tcpNoDelay (boolean):// 如果设置为true,则禁用Nagle算法,这可以减少小数据包的延迟。// 基本原理:Nagle算法试图将多个小的数据包合并成一个较大的数据包再发送出去,这样可以减少网络中的数据包数量,// 从而提高带宽利用率。当应用发送少量数据时,TCP并不会立即将这些数据发送出去,而是等待一段时间(比如等待应用发送更多的数据),直到数据量达到一定阈值或超时后再一起发送// tcpSendBufferSize (int):// 设置发送缓冲区大小。如果设置为-1,则使用系统默认值。// tcpReceiveBufferSize (int):// 设置接收缓冲区大小。如果设置为-1,则使用系统默认值。// tcpKeepAlive (boolean):// 如果设置为true,则启用TCP的保活机制,可以在长时间没有数据传输后检测连接是否仍然有效。// soLinger (int):// 设置关闭连接前等待未发送完的数据的时间。如果是-1,则立即关闭连接而不等待。// reuseAddress (boolean):// 如果设置为true,则允许重用地址和端口,即使之前绑定过这个端口也行。// acceptBackLog (int):// 设置监听队列长度,即内核为该socket保留的已完成三次握手但尚未被accept()接受的连接的最大数目。// ssl// config.setKeyStorePassword("test1234");// InputStream stream = ChatSocketIOServer.class.getResourceAsStream("/keystore.jks");// config.setKeyStore(stream);return config;}/*** EventHandler*/public static class EventHandler {private final SocketIONamespace socketIONamespace;public EventHandler(SocketIONamespace socketIONamespace) {this.socketIONamespace = socketIONamespace;}@OnConnectpublic void onConnect(SocketIOClient client) {String userName = client.get(USER_NAME);System.out.println(StrUtil.format("用户:{} 连接成功!", userName));}@OnDisconnectpublic void onDisconnect(SocketIOClient client) {String userName = client.get(USER_NAME);System.out.println(StrUtil.format("用户:{} 断开连接!", userName));}@OnEvent("user")public void onEventUser(SocketIOClient client, Object data, AckRequest ackSender) {String userName = client.get(USER_NAME);// 返回用户昵称ackSender.sendAckData(userName);}@OnEvent("message")public void onEventMessage(SocketIOClient client, Object data, AckRequest ackSender) {Opt.ofEmptyAble(socketIONamespace.getAllClients()).ifPresent(clients -> {// 发送消息给所有用户socketIONamespace.getBroadcastOperations().sendEvent("message", data);});}}@Overridepublic void run(String... args) throws Exception {// token验证socketIONamespace.addAuthTokenListener((authToken, client) -> {Integer userId = USER_NUMBER.incrementAndGet();// 设置用户信息client.set(USER_ID, userId);String userName = getUserName(userId);client.set(USER_NAME, userName);System.out.println(StrUtil.format("token:{},用户:{} 验证成功!", authToken, userName));return AuthTokenResult.AuthTokenResultSuccess;});socketIONamespace.addListeners(eventHandler());server.start();}/*** 获取用户名称** @param userId* @return*/private String getUserName(Integer userId) {Assert.notNull(userId);return "用户" + userId;}
}

6.3 ChatServerApplication.java

package cn.netty.socketio.learn;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** ChatServerApplication*/
@SpringBootApplication
public class ChatServerApplication {public static void main(String[] args) {SpringApplication.run(ChatServerApplication.class, args);}
}

启动ChatServerApplication效果和ChatServer一样

这篇关于netty-socketio和Socket.IO实现网页在线聊天功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1140362

相关文章

Android实现悬浮按钮功能

《Android实现悬浮按钮功能》在很多场景中,我们希望在应用或系统任意界面上都能看到一个小的“悬浮按钮”(FloatingButton),用来快速启动工具、展示未读信息或快捷操作,所以本文给大家介绍... 目录一、项目概述二、相关技术知识三、实现思路四、整合代码4.1 Java 代码(MainActivi

使用Python实现一个优雅的异步定时器

《使用Python实现一个优雅的异步定时器》在Python中实现定时器功能是一个常见需求,尤其是在需要周期性执行任务的场景下,本文给大家介绍了基于asyncio和threading模块,可扩展的异步定... 目录需求背景代码1. 单例事件循环的实现2. 事件循环的运行与关闭3. 定时器核心逻辑4. 启动与停

基于Python实现读取嵌套压缩包下文件的方法

《基于Python实现读取嵌套压缩包下文件的方法》工作中遇到的问题,需要用Python实现嵌套压缩包下文件读取,本文给大家介绍了详细的解决方法,并有相关的代码示例供大家参考,需要的朋友可以参考下... 目录思路完整代码代码优化思路打开外层zip压缩包并遍历文件:使用with zipfile.ZipFil

Python实现word文档内容智能提取以及合成

《Python实现word文档内容智能提取以及合成》这篇文章主要为大家详细介绍了如何使用Python实现从10个左右的docx文档中抽取内容,再调整语言风格后生成新的文档,感兴趣的小伙伴可以了解一下... 目录核心思路技术路径实现步骤阶段一:准备工作阶段二:内容提取 (python 脚本)阶段三:语言风格调

C#实现将Excel表格转换为图片(JPG/ PNG)

《C#实现将Excel表格转换为图片(JPG/PNG)》Excel表格可能会因为不同设备或字体缺失等问题,导致格式错乱或数据显示异常,转换为图片后,能确保数据的排版等保持一致,下面我们看看如何使用C... 目录通过C# 转换Excel工作表到图片通过C# 转换指定单元格区域到图片知识扩展C# 将 Excel

基于Java实现回调监听工具类

《基于Java实现回调监听工具类》这篇文章主要为大家详细介绍了如何基于Java实现一个回调监听工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录监听接口类 Listenable实际用法打印结果首先,会用到 函数式接口 Consumer, 通过这个可以解耦回调方法,下面先写一个

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

Qt中QGroupBox控件的实现

《Qt中QGroupBox控件的实现》QGroupBox是Qt框架中一个非常有用的控件,它主要用于组织和管理一组相关的控件,本文主要介绍了Qt中QGroupBox控件的实现,具有一定的参考价值,感兴趣... 目录引言一、基本属性二、常用方法2.1 构造函数 2.2 设置标题2.3 设置复选框模式2.4 是否

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

springboot整合阿里云百炼DeepSeek实现sse流式打印的操作方法

《springboot整合阿里云百炼DeepSeek实现sse流式打印的操作方法》:本文主要介绍springboot整合阿里云百炼DeepSeek实现sse流式打印,本文给大家介绍的非常详细,对大... 目录1.开通阿里云百炼,获取到key2.新建SpringBoot项目3.工具类4.启动类5.测试类6.测