12.4.4 处理连接异常——连接管理:断线重连与状态同步
一句话破题
网络不可靠是常态,优秀的实时应用必须优雅地处理断线、重连和状态同步。
连接状态管理
tsx
'use client';
import { useEffect, useState } from 'react';
import { io, Socket } from 'socket.io-client';
type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'reconnecting';
export function useSocket(url: string) {
const [socket, setSocket] = useState<Socket | null>(null);
const [status, setStatus] = useState<ConnectionStatus>('connecting');
useEffect(() => {
const newSocket = io(url, {
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
});
newSocket.on('connect', () => {
setStatus('connected');
console.log('已连接');
});
newSocket.on('disconnect', (reason) => {
setStatus('disconnected');
console.log('断开连接:', reason);
});
newSocket.on('reconnect_attempt', (attempt) => {
setStatus('reconnecting');
console.log(`重连尝试 ${attempt}`);
});
newSocket.on('reconnect', () => {
setStatus('connected');
console.log('重连成功');
});
newSocket.on('reconnect_failed', () => {
setStatus('disconnected');
console.log('重连失败');
});
setSocket(newSocket);
return () => {
newSocket.close();
};
}, [url]);
return { socket, status };
}连接状态 UI
tsx
function ConnectionIndicator({ status }: { status: ConnectionStatus }) {
const config = {
connecting: { color: 'bg-yellow-500', text: '连接中...' },
connected: { color: 'bg-green-500', text: '已连接' },
disconnected: { color: 'bg-red-500', text: '已断开' },
reconnecting: { color: 'bg-orange-500', text: '重连中...' },
};
const { color, text } = config[status];
return (
<div className="flex items-center gap-2">
<span className={`w-2 h-2 rounded-full ${color}`} />
<span className="text-sm text-gray-600">{text}</span>
</div>
);
}状态同步策略
断线重连后,需要同步期间错过的状态:
typescript
// 服务端:记录消息历史
const messageHistory = new Map<string, Message[]>();
io.on('connection', (socket) => {
socket.on('joinRoom', (roomId: string) => {
socket.join(roomId);
// 发送历史消息
const history = messageHistory.get(roomId) || [];
socket.emit('messageHistory', history.slice(-50)); // 最近 50 条
});
socket.on('sendMessage', ({ roomId, content }) => {
const message = { id: Date.now().toString(), content, timestamp: Date.now() };
// 保存到历史
if (!messageHistory.has(roomId)) {
messageHistory.set(roomId, []);
}
messageHistory.get(roomId)!.push(message);
io.to(roomId).emit('newMessage', message);
});
});
// 客户端:处理历史消息
socket.on('messageHistory', (history: Message[]) => {
setMessages((prev) => {
// 去重合并
const existingIds = new Set(prev.map((m) => m.id));
const newMessages = history.filter((m) => !existingIds.has(m.id));
return [...newMessages, ...prev].sort((a, b) => a.timestamp - b.timestamp);
});
});心跳机制
Socket.io 内置了心跳机制,但你也可以自定义:
typescript
// 服务端配置
const io = new Server(httpServer, {
pingTimeout: 60000, // 等待 pong 响应的超时时间
pingInterval: 25000, // 发送 ping 的间隔
});
// 客户端可以监听
socket.on('ping', () => {
console.log('收到心跳');
});离线消息队列
网络不好时,先缓存消息,恢复后再发送:
typescript
class MessageQueue {
private queue: Array<{ event: string; data: unknown }> = [];
private socket: Socket | null = null;
setSocket(socket: Socket) {
this.socket = socket;
this.flush();
}
send(event: string, data: unknown) {
if (this.socket?.connected) {
this.socket.emit(event, data);
} else {
this.queue.push({ event, data });
}
}
flush() {
if (this.socket?.connected) {
while (this.queue.length > 0) {
const { event, data } = this.queue.shift()!;
this.socket.emit(event, data);
}
}
}
}AI 协作指南
- 核心意图:让 AI 帮你实现健壮的连接管理。
- 需求定义公式:
"请帮我实现一个带有连接状态显示、自动重连和离线消息队列的 Socket.io 客户端封装。" - 关键术语:
重连 (reconnection)、心跳 (heartbeat)、状态同步、离线队列
避坑指南
- 指数退避:重连间隔应该逐渐增加,避免服务器过载。
- 最大重试次数:不要无限重试,给用户反馈让他们手动刷新。
- 幂等性:重发的消息不应该产生副作用(如重复扣款)。
