Socket.IO 基础使用 #
安装 Socket.IO #
服务端安装 #
bash
npm install socket.io
客户端安装 #
bash
npm install socket.io-client
CDN 引入 #
html
<script src="/socket.io/socket.io.js"></script>
或使用 CDN:
html
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
创建服务端 #
基础服务端 #
javascript
const { Server } = require('socket.io');
const io = new Server({
cors: {
origin: 'http://localhost:3000',
methods: ['GET', 'POST']
}
});
io.on('connection', (socket) => {
console.log('用户连接:', socket.id);
socket.on('disconnect', () => {
console.log('用户断开:', socket.id);
});
});
io.listen(3001);
console.log('Socket.IO 服务运行在 3001 端口');
与 Express 集成 #
javascript
const express = require('express');
const { createServer } = require('http');
const { Server } = require('socket.io');
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: 'http://localhost:3000',
methods: ['GET', 'POST']
}
});
app.get('/', (req, res) => {
res.send('Hello World');
});
io.on('connection', (socket) => {
console.log('用户连接:', socket.id);
});
httpServer.listen(3001, () => {
console.log('服务器运行在 http://localhost:3001');
});
与 Koa 集成 #
javascript
const Koa = require('koa');
const { createServer } = require('http');
const { Server } = require('socket.io');
const app = new Koa();
const httpServer = createServer(app.callback());
const io = new Server(httpServer);
io.on('connection', (socket) => {
console.log('用户连接:', socket.id);
});
httpServer.listen(3001);
TypeScript 支持 #
typescript
import express from 'express';
import { createServer } from 'http';
import { Server, Socket } from 'socket.io';
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer);
interface ClientToServerEvents {
chatMessage: (msg: string) => void;
}
interface ServerToClientEvents {
chatMessage: (msg: string) => void;
}
interface InterServerEvents {
ping: () => void;
}
interface SocketData {
name: string;
age: number;
}
const io = new Server<
ClientToServerEvents,
ServerToClientEvents,
InterServerEvents,
SocketData
>(httpServer);
io.on('connection', (socket: Socket) => {
console.log('用户连接:', socket.id);
socket.data.name = 'John';
});
创建客户端 #
浏览器客户端 #
html
<!DOCTYPE html>
<html>
<head>
<title>Socket.IO 客户端</title>
</head>
<body>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io('http://localhost:3001');
socket.on('connect', () => {
console.log('连接成功:', socket.id);
});
socket.on('disconnect', () => {
console.log('连接断开');
});
</script>
</body>
</html>
Node.js 客户端 #
javascript
const { io } = require('socket.io-client');
const socket = io('http://localhost:3001');
socket.on('connect', () => {
console.log('连接成功:', socket.id);
});
socket.on('disconnect', () => {
console.log('连接断开');
});
React 客户端 #
jsx
import { useEffect, useState } from 'react';
import { io } from 'socket.io-client';
function App() {
const [socket, setSocket] = useState(null);
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
const newSocket = io('http://localhost:3001');
newSocket.on('connect', () => {
setIsConnected(true);
console.log('连接成功:', newSocket.id);
});
newSocket.on('disconnect', () => {
setIsConnected(false);
console.log('连接断开');
});
setSocket(newSocket);
return () => {
newSocket.close();
};
}, []);
return (
<div>
<p>连接状态: {isConnected ? '已连接' : '未连接'}</p>
</div>
);
}
export default App;
Vue 客户端 #
vue
<template>
<div>
<p>连接状态: {{ isConnected ? '已连接' : '未连接' }}</p>
</div>
</template>
<script>
import { io } from 'socket.io-client';
export default {
data() {
return {
socket: null,
isConnected: false
};
},
mounted() {
this.socket = io('http://localhost:3001');
this.socket.on('connect', () => {
this.isConnected = true;
console.log('连接成功:', this.socket.id);
});
this.socket.on('disconnect', () => {
this.isConnected = false;
});
},
beforeUnmount() {
if (this.socket) {
this.socket.disconnect();
}
}
};
</script>
连接流程 #
连接建立过程 #
text
┌─────────────────────────────────────────────────────────────┐
│ Socket.IO 连接流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ 客户端 │ │ 服务端 │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ │ 1. HTTP 握手请求 │ │
│ │─────────────────────────>│ │
│ │ │ │
│ │ 2. 返回 Session ID │ │
│ │<─────────────────────────│ │
│ │ │ │
│ │ 3. WebSocket 升级请求 │ │
│ │─────────────────────────>│ │
│ │ │ │
│ │ 4. WebSocket 连接建立 │ │
│ │<─────────────────────────│ │
│ │ │ │
│ │ 5. 心跳包(Ping/Pong) │ │
│ │<────────────────────────>│ │
│ │
└─────────────────────────────────────────────────────────────┘
连接选项配置 #
javascript
const socket = io('http://localhost:3001', {
path: '/socket.io',
transports: ['websocket', 'polling'],
reconnection: true,
reconnectionAttempts: Infinity,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
timeout: 20000,
autoConnect: true,
query: {
token: 'your-auth-token'
},
auth: {
token: 'your-auth-token'
},
extraHeaders: {
'X-Custom-Header': 'value'
}
});
连接选项详解 #
text
┌─────────────────────────────────────────────────────────────┐
│ 连接选项详解 │
├─────────────────────────────────────────────────────────────┤
│ │
│ path Socket.IO 路径 │
│ ───────────────────────────────────────────────────────── │
│ 默认: /socket.io │
│ 示例: path: '/custom-path' │
│ │
│ transports 传输方式 │
│ ───────────────────────────────────────────────────────── │
│ 默认: ['polling', 'websocket'] │
│ 示例: transports: ['websocket'] │
│ │
│ reconnection 是否自动重连 │
│ ───────────────────────────────────────────────────────── │
│ 默认: true │
│ 示例: reconnection: false │
│ │
│ reconnectionAttempts 重连次数 │
│ ───────────────────────────────────────────────────────── │
│ 默认: Infinity │
│ 示例: reconnectionAttempts: 10 │
│ │
│ reconnectionDelay 重连延迟 │
│ ───────────────────────────────────────────────────────── │
│ 默认: 1000 (毫秒) │
│ 示例: reconnectionDelay: 2000 │
│ │
│ reconnectionDelayMax 最大重连延迟 │
│ ───────────────────────────────────────────────────────── │
│ 默认: 5000 (毫秒) │
│ 示例: reconnectionDelayMax: 10000 │
│ │
│ timeout 连接超时 │
│ ───────────────────────────────────────────────────────── │
│ 默认: 20000 (毫秒) │
│ 示例: timeout: 10000 │
│ │
│ autoConnect 是否自动连接 │
│ ───────────────────────────────────────────────────────── │
│ 默认: true │
│ 示例: autoConnect: false │
│ │
│ query URL 查询参数 │
│ ───────────────────────────────────────────────────────── │
│ 示例: query: { token: 'xxx' } │
│ │
│ auth 认证信息 │
│ ───────────────────────────────────────────────────────── │
│ 示例: auth: { token: 'xxx' } │
│ │
└─────────────────────────────────────────────────────────────┘
连接事件 #
客户端连接事件 #
javascript
const socket = io('http://localhost:3001');
socket.on('connect', () => {
console.log('连接成功');
console.log('Socket ID:', socket.id);
});
socket.on('connect_error', (error) => {
console.error('连接错误:', error.message);
});
socket.on('disconnect', (reason) => {
console.log('连接断开:', reason);
});
socket.on('reconnect', (attemptNumber) => {
console.log('重连成功:', attemptNumber);
});
socket.on('reconnect_attempt', (attemptNumber) => {
console.log('尝试重连:', attemptNumber);
});
socket.on('reconnect_error', (error) => {
console.error('重连错误:', error);
});
socket.on('reconnect_failed', () => {
console.log('重连失败');
});
断开原因 #
text
┌─────────────────────────────────────────────────────────────┐
│ 断开原因说明 │
├─────────────────────────────────────────────────────────────┤
│ │
│ io server disconnect │
│ ───────────────────────────────────────────────────────── │
│ 服务端主动断开连接 │
│ 客户端不会自动重连 │
│ │
│ io client disconnect │
│ ───────────────────────────────────────────────────────── │
│ 客户端主动断开连接 │
│ 客户端不会自动重连 │
│ │
│ ping timeout │
│ ───────────────────────────────────────────────────────── │
│ 心跳超时 │
│ 客户端会自动重连 │
│ │
│ transport close │
│ ───────────────────────────────────────────────────────── │
│ 传输层关闭 │
│ 客户端会自动重连 │
│ │
│ transport error │
│ ───────────────────────────────────────────────────────── │
│ 传输层错误 │
│ 客户端会自动重连 │
│ │
└─────────────────────────────────────────────────────────────┘
服务端连接事件 #
javascript
io.on('connection', (socket) => {
console.log('用户连接:', socket.id);
socket.on('disconnect', (reason) => {
console.log('用户断开:', socket.id, '原因:', reason);
});
socket.on('disconnecting', (reason) => {
console.log('用户正在断开:', socket.id);
console.log('当前房间:', socket.rooms);
});
});
手动连接与断开 #
手动连接 #
javascript
const socket = io('http://localhost:3001', {
autoConnect: false
});
document.getElementById('connect').addEventListener('click', () => {
socket.connect();
});
手动断开 #
javascript
socket.disconnect();
socket.on('disconnect', () => {
console.log('已断开连接');
});
重连 #
javascript
socket.disconnect();
setTimeout(() => {
socket.connect();
}, 3000);
发送和接收消息 #
发送消息 #
javascript
socket.emit('chat message', 'Hello World');
socket.emit('chat message', {
user: 'John',
message: 'Hello World',
timestamp: Date.now()
});
socket.emit('private message', {
to: 'user-123',
content: 'Hello!'
});
接收消息 #
javascript
socket.on('chat message', (msg) => {
console.log('收到消息:', msg);
});
socket.on('chat message', (data) => {
console.log(`${data.user}: ${data.message}`);
});
socket.onAny((eventName, ...args) => {
console.log('收到事件:', eventName, args);
});
消息确认 #
javascript
socket.emit('chat message', 'Hello', (acknowledgement) => {
console.log('消息已确认:', acknowledgement);
});
socket.on('chat message', (msg, callback) => {
console.log('收到消息:', msg);
callback('消息已收到');
});
服务端广播 #
广播给所有客户端 #
javascript
io.emit('news', { message: 'Hello everyone!' });
广播给除发送者外的所有客户端 #
javascript
socket.broadcast.emit('news', { message: 'Hello others!' });
广播给特定房间 #
javascript
io.to('room-1').emit('news', { message: 'Hello room 1!' });
socket.to('room-1').emit('news', { message: 'Hello room 1!' });
广播示例 #
text
┌─────────────────────────────────────────────────────────────┐
│ 广播方式对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ io.emit() │
│ ───────────────────────────────────────────────────────── │
│ 发送给所有连接的客户端 │
│ │
│ socket.broadcast.emit() │
│ ───────────────────────────────────────────────────────── │
│ 发送给除当前 socket 外的所有客户端 │
│ │
│ io.to('room').emit() │
│ ───────────────────────────────────────────────────────── │
│ 发送给特定房间的所有客户端 │
│ │
│ socket.to('room').emit() │
│ ───────────────────────────────────────────────────────── │
│ 发送给特定房间除当前 socket 外的客户端 │
│ │
│ socket.emit() │
│ ───────────────────────────────────────────────────────── │
│ 只发送给当前 socket │
│ │
└─────────────────────────────────────────────────────────────┘
完整示例:聊天室 #
服务端代码 #
javascript
const express = require('express');
const { createServer } = require('http');
const { Server } = require('socket.io');
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: '*'
}
});
io.on('connection', (socket) => {
console.log('用户连接:', socket.id);
socket.on('join', (username) => {
socket.username = username;
socket.broadcast.emit('user joined', {
username,
message: `${username} 加入了聊天室`
});
});
socket.on('chat message', (msg) => {
io.emit('chat message', {
username: socket.username,
message: msg,
timestamp: Date.now()
});
});
socket.on('typing', () => {
socket.broadcast.emit('typing', socket.username);
});
socket.on('disconnect', () => {
if (socket.username) {
socket.broadcast.emit('user left', {
username: socket.username,
message: `${socket.username} 离开了聊天室`
});
}
});
});
httpServer.listen(3001, () => {
console.log('聊天室服务器运行在 http://localhost:3001');
});
客户端代码 #
html
<!DOCTYPE html>
<html>
<head>
<title>聊天室</title>
<style>
#messages { height: 300px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px; }
#typing { color: #666; font-style: italic; }
</style>
</head>
<body>
<div id="messages"></div>
<div id="typing"></div>
<input type="text" id="username" placeholder="用户名">
<button onclick="join()">加入</button>
<br>
<input type="text" id="messageInput" placeholder="消息" disabled>
<button onclick="sendMessage()" id="sendBtn" disabled>发送</button>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io('http://localhost:3001');
const messages = document.getElementById('messages');
const typing = document.getElementById('typing');
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
let typingTimeout;
socket.on('connect', () => {
console.log('连接成功');
});
socket.on('chat message', (data) => {
const div = document.createElement('div');
div.textContent = `${data.username}: ${data.message}`;
messages.appendChild(div);
messages.scrollTop = messages.scrollHeight;
});
socket.on('user joined', (data) => {
const div = document.createElement('div');
div.style.color = 'green';
div.textContent = data.message;
messages.appendChild(div);
});
socket.on('user left', (data) => {
const div = document.createElement('div');
div.style.color = 'red';
div.textContent = data.message;
messages.appendChild(div);
});
socket.on('typing', (username) => {
typing.textContent = `${username} 正在输入...`;
clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => {
typing.textContent = '';
}, 1000);
});
function join() {
const username = document.getElementById('username').value;
if (username) {
socket.emit('join', username);
messageInput.disabled = false;
sendBtn.disabled = false;
document.getElementById('username').disabled = true;
}
}
function sendMessage() {
const message = messageInput.value;
if (message) {
socket.emit('chat message', message);
messageInput.value = '';
}
}
messageInput.addEventListener('input', () => {
socket.emit('typing');
});
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>
服务端配置选项 #
javascript
const io = new Server(httpServer, {
cors: {
origin: ['http://localhost:3000', 'https://example.com'],
methods: ['GET', 'POST'],
allowedHeaders: ['my-custom-header'],
credentials: true
},
pingInterval: 25000,
pingTimeout: 20000,
upgradeTimeout: 10000,
maxHttpBufferSize: 1e6,
transports: ['polling', 'websocket'],
allowEIO3: true,
cookie: {
name: 'io',
httpOnly: true,
sameSite: 'strict'
}
});
配置选项详解 #
text
┌─────────────────────────────────────────────────────────────┐
│ 服务端配置选项 │
├─────────────────────────────────────────────────────────────┤
│ │
│ cors 跨域配置 │
│ ───────────────────────────────────────────────────────── │
│ cors: { │
│ origin: 'http://localhost:3000', │
│ methods: ['GET', 'POST'], │
│ credentials: true │
│ } │
│ │
│ pingInterval 心跳间隔 │
│ ───────────────────────────────────────────────────────── │
│ 默认: 25000 (毫秒) │
│ 服务端发送 ping 的间隔 │
│ │
│ pingTimeout 心跳超时 │
│ ───────────────────────────────────────────────────────── │
│ 默认: 20000 (毫秒) │
│ 等待 pong 响应的超时时间 │
│ │
│ upgradeTimeout 升级超时 │
│ ───────────────────────────────────────────────────────── │
│ 默认: 10000 (毫秒) │
│ HTTP 长轮询升级到 WebSocket 的超时 │
│ │
│ maxHttpBufferSize 最大消息大小 │
│ ───────────────────────────────────────────────────────── │
│ 默认: 1e6 (1MB) │
│ 单条消息的最大字节数 │
│ │
│ transports 传输方式 │
│ ───────────────────────────────────────────────────────── │
│ 默认: ['polling', 'websocket'] │
│ 允许的传输方式 │
│ │
│ allowEIO3 兼容 v3 客户端 │
│ ───────────────────────────────────────────────────────── │
│ 默认: false │
│ 是否允许 Socket.IO v3 客户端连接 │
│ │
└─────────────────────────────────────────────────────────────┘
调试技巧 #
启用调试日志 #
javascript
const io = new Server(httpServer, {
cors: { origin: '*' }
});
io.engine.on('connection_error', (err) => {
console.log('连接错误:', err.req);
console.log('错误码:', err.code);
console.log('错误消息:', err.message);
console.log('错误上下文:', err.context);
});
客户端调试 #
javascript
localStorage.debug = 'socket.io-client:*';
const socket = io('http://localhost:3001');
查看连接信息 #
javascript
io.on('connection', (socket) => {
console.log('连接信息:');
console.log('- ID:', socket.id);
console.log('- Handshake:', socket.handshake);
console.log('- Rooms:', socket.rooms);
console.log('- Transport:', socket.conn.transport.name);
});
下一步 #
最后更新:2026-03-29