前言
socket.io是一套通信组件,支持即时、双向与基于事件的交流。它可以在每个平台、每个浏览器和每个设备上工作,可靠性和速度同样稳定。socket.io有一套自己的交互协议,可以基于该基础协议,实现相应的应用功能,使得功能开发将会更加便捷。本文限于篇幅,对于协议不会做过多介绍,主要从实现服务端和客户端的交互来介绍,对协议感兴趣的可以查看官方的文档。
服务端开发
首先添加必要的依赖,我们以基于netty的netty-socketio为基础,采用Java实现一个服务端,为了方便起见,写在了一个方法中。
xml
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>2.0.13</version>
</dependency>
<!-- Netty 核心依赖(手动指定兼容版本) -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
<version>${netty.version}</version>
</dependency>
netty的版本这里采用了4.1.115.Final。
服务端方法代码主要有如下几个部分:
- 服务配置,比如host, port,以及跨域设置。
- 增加服务namespace。这个是比较重要的一个概念,服务会有一个默认的namespace,导致没有和自定义的关联起来,导致请求没有正确处理。
- 增加监听器。为了理清请求情况,增加了连接监听,数据监听,以及探针监听,分别是
ConnectListener,DataListener,PingListener,PongListener。此外还增加了一个事件拦截器interceptor。 - 最后是启动服务端。
完整代码:
java
import com.corundumstudio.socketio.*;
import com.corundumstudio.socketio.listener.*;
import com.corundumstudio.socketio.transport.NamespaceClient;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.concurrent.ExecutionException;
@Test
public void start() throws InterruptedException, ExecutionException {
Configuration configuration = new Configuration();
configuration.setAckMode(AckMode.AUTO);
configuration.setContext("/realtime");
configuration.setHostname("0.0.0.0");
configuration.setPort(8000);
configuration.setAllowHeaders("*");
configuration.setOrigin("*");
SocketIOServer server = new SocketIOServer(configuration);
SocketIONamespace namespace = server.addNamespace("/realtime");
namespace.addConnectListener(new ConnectListener() {
@Override
public void onConnect(SocketIOClient client) {
log.info("Connected ns:{}, remote:{} ", client.getNamespace().getName(), client.getRemoteAddress());
}
});
namespace.addEventListener("update", Object.class, new DataListener<Object>() {
@Override
public void onData(SocketIOClient client, Object data, AckRequest ackSender) throws Exception {
log.info("Data:{}", data);
ackSender.sendAckData("接受到客户端消息,类型:"+data.getClass().getSimpleName());
}
});
namespace.addPingListener(new PingListener() {
@Override
public void onPing(SocketIOClient client) {
log.info("Ping ns:{}, remote:{}", client.getNamespace().getName(),client.getRemoteAddress());
}
});
namespace.addPongListener(new PongListener() {
@Override
public void onPong(SocketIOClient client) {
log.info("Pong ns:{}, remote:{}", client.getNamespace(),client.getRemoteAddress());
}
});
namespace.addEventInterceptor(new EventInterceptor() {
@Override
public void onEvent(NamespaceClient client, String eventName, List<Object> args, AckRequest ackRequest) {
log.info("Event interceptor:{}", eventName);
}
});
//start
server.start();
Thread.currentThread().join();
}
客户端开发
客户端代码功能主要有连接服务端,发送自定义事件消息,以及断开连接功能,基于AI生成和修改。实际的运行效果如下图,完整代码如下:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Socket.IO 验证 Demo</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", sans-serif;
}
.container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.title {
text-align: center;
margin-bottom: 20px;
color: #333;
}
.config {
margin-bottom: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 6px;
}
.config label {
display: inline-block;
width: 120px;
margin-bottom: 10px;
color: #555;
}
.config input {
padding: 8px;
width: calc(100% - 130px);
border: 1px solid #ddd;
border-radius: 4px;
}
.btn-group {
margin-bottom: 20px;
display: flex;
gap: 10px;
}
button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
color: white;
font-size: 14px;
transition: background 0.3s;
}
#connectBtn { background: #2196F3; }
#connectBtn:hover { background: #1976D2; }
#sendBtn { background: #4CAF50; }
#sendBtn:hover { background: #388E3C; }
#disconnectBtn { background: #F44336; }
#disconnectBtn:hover { background: #D32F2F; }
button:disabled {
background: #9E9E9E;
cursor: not-allowed;
}
.message-input {
margin-bottom: 20px;
}
.message-input textarea {
width: 100%;
height: 100px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
resize: none;
font-size: 14px;
}
.log-area {
height: 300px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
overflow-y: auto;
background: #fafafa;
font-size: 13px;
line-height: 1.5;
}
.log-info { color: #2196F3; }
.log-success { color: #4CAF50; }
.log-error { color: #F44336; }
.log-time { color: #999; }
</style>
</head>
<body>
<div class="container">
<h1 class="title">Socket.IO 验证 Demo</h1>
<!-- 连接配置 -->
<div class="config">
<label>服务端地址:</label>
<input type="text" id="serverUrl" value="http://localhost:8000/realtime" placeholder="如:http://localhost:8000/realtime">
<br>
<label>消息内容:</label>
<input type="text" id="msgContent" value="测试消息内容" placeholder="消息内容">
<br>
<label>时间戳:</label>
<input type="text" id="msgTimestamp" value="" placeholder="自动生成">
</div>
<!-- 操作按钮 -->
<div class="btn-group">
<button id="connectBtn">连接服务端</button>
<button id="sendBtn" disabled>发送 update 事件</button>
<button id="disconnectBtn" disabled>断开连接</button>
</div>
<!-- 自定义消息输入框(可选) -->
<div class="message-input">
<textarea id="customMsg" placeholder="自定义JSON格式消息(可选,优先级高于上方表单)">
{
"content": "自定义测试消息",
"timestamp": 1719000000000
}
</textarea>
</div>
<!-- 日志输出 -->
<div class="log-area" id="logArea"></div>
</div>
<!-- 引入Socket.IO客户端(CDN) -->
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
<script>
// 全局变量
let socket = null;
const serverUrlInput = document.getElementById('serverUrl');
const msgContentInput = document.getElementById('msgContent');
const msgTimestampInput = document.getElementById('msgTimestamp');
const customMsgInput = document.getElementById('customMsg');
const connectBtn = document.getElementById('connectBtn');
const sendBtn = document.getElementById('sendBtn');
const disconnectBtn = document.getElementById('disconnectBtn');
const logArea = document.getElementById('logArea');
// 初始化时间戳
msgTimestampInput.value = Date.now();
// 日志输出函数
function log(message, type = 'info') {
const time = new Date().toLocaleTimeString();
const className = `log-${type}`;
const logItem = document.createElement('div');
logItem.innerHTML = `<span class="log-time">[${time}]</span> <span class="${className}">${message}</span>`;
logArea.appendChild(logItem);
// 自动滚动到底部
logArea.scrollTop = logArea.scrollHeight;
}
// 连接按钮点击事件
connectBtn.addEventListener('click', () => {
const serverUrl = serverUrlInput.value.trim();
if (!serverUrl) {
log('请输入服务端地址!', 'error');
return;
}
// 断开已有连接(防止重复连接)
if (socket) {
socket.disconnect();
socket = null;
}
try {
// 连接Socket.IO服务端
log(`正在连接:${serverUrl}`, 'info');
socket = io(serverUrl, {
path: '/realtime',
reconnection: false, // 关闭自动重连(便于测试)
timeout: 10000 // 连接超时10秒
});
// 连接成功回调
socket.on('connect', () => {
log('✅ 连接服务端成功!', 'success');
connectBtn.disabled = true;
sendBtn.disabled = false;
disconnectBtn.disabled = false;
});
// 连接失败回调
socket.on('connect_error', (error) => {
log(`❌ 连接失败:${error.message}`, 'error');
resetState();
});
// 断开连接回调
socket.on('disconnect', (reason) => {
log(`🔌 连接断开:${reason}`, 'info');
resetState();
});
// 接收服务端消息(可选,若服务端主动推送事件)
socket.onAny((event, ...args) => {
log(`📥 接收服务端事件 [${event}]:${JSON.stringify(args)}`, 'info');
});
} catch (error) {
log(`❌ 连接异常:${error.message}`, 'error');
resetState();
}
});
// 发送消息按钮点击事件
sendBtn.addEventListener('click', () => {
if (!socket || !socket.connected) {
log('❌ 未连接到服务端!', 'error');
return;
}
// 构建消息数据(优先使用自定义JSON,否则用表单数据)
let messageData;
try {
// 尝试解析自定义消息
const customMsg = customMsgInput.value.trim();
if (customMsg) {
messageData = JSON.parse(customMsg);
} else {
// 表单构建消息
messageData = {
content: msgContentInput.value.trim() || '默认测试消息',
timestamp: msgTimestampInput.value.trim() ? Number(msgTimestampInput.value) : Date.now()
};
}
} catch (error) {
log(`❌ 消息格式错误:${error.message}`, 'error');
return;
}
// 发送update事件,并等待ACK响应
log(`📤 发送 update 事件:${JSON.stringify(messageData)}`, 'info');
socket.emit('update', messageData, (ackData) => {
// 接收服务端的ACK响应
log(`✅ 收到服务端ACK:${JSON.stringify(ackData)}`, 'success');
});
// 更新时间戳(便于下次发送)
msgTimestampInput.value = Date.now();
});
// 断开连接按钮点击事件
disconnectBtn.addEventListener('click', () => {
if (socket) {
socket.disconnect();
log('🔌 主动断开连接', 'info');
} else {
log('❌ 无有效连接', 'error');
}
});
// 重置按钮状态
function resetState() {
connectBtn.disabled = false;
sendBtn.disabled = true;
disconnectBtn.disabled = true;
socket = null;
}
// 页面关闭时断开连接
window.addEventListener('beforeunload', () => {
if (socket) {
socket.disconnect();
}
});
</script>
</body>
</html>
效果演示
