如果没有宽恕之心,生命会被无休止的仇恨和报复所支配。——阿萨吉奥
WebSocket 是一种轻量级、双向的实时通信协议,在现代 Web 应用中非常流行。它为客户端和服务端提供了长连接能力,适用于需要频繁数据交互的场景。然而,在实际开发中,我们经常需要处理 WebSocket 的关闭事件,而 关闭状态(CloseStatus) 是其中一个重要的概念,它能够帮助开发者理解连接关闭的原因,从而采取相应的措施。
在 WebSocket 协议中,每次连接关闭都会携带一个 关闭码(close code) 和可选的 关闭原因(reason phrase)。这些关闭码由 RFC 6455 定义,表示连接关闭的原因。例如:
在 Spring Framework 中,org.springframework.web.socket.CloseStatus 提供了对这些状态的封装,便于我们处理 WebSocket 关闭事件。
12345678910 | @Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception { String deviceId = (String) session.getAttributes().get("deviceId"); if (deviceId == null || deviceId.isEmpty()) { session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Missing deviceId")); return; } deviceSessionMap.put(deviceId, session); session.sendMessage(new TextMessage("连接成功,设备ID: " + deviceId));} |
|---|
Spring 提供了 CloseStatus 类来封装关闭码和原因。以下是 CloseStatus 的关键方法和属性:
getCode(): 获取关闭码。getReason(): 获取关闭的原因(可能为空)。CloseStatus.NORMAL 和 CloseStatus.PROTOCOL_ERROR。123456789101112131415 | // 使用 CloseStatus 处理关闭事件@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { System.out.println("WebSocket 连接已关闭"); System.out.println("关闭码: " + status.getCode()); System.out.println("关闭原因: " + status.getReason()); if (status.equals(CloseStatus.NORMAL)) { System.out.println("连接正常关闭"); } else if (status.getCode() == 1006) { // Abnormal closure System.out.println("连接异常关闭"); } else { System.out.println("关闭状态: " + status); }} |
|---|
12345 | @Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { System.err.println("WebSocket 传输错误: " + exception.getMessage()); session.close(CloseStatus.SERVER_ERROR);} |
|---|
在客户端中,我们也可以捕获 onclose 事件,并基于关闭状态码进行不同的操作。例如:
12345678910111213141516171819 | const socket = new WebSocket("wss://example.com/socket");socket.onclose = (event) => { console.log(`WebSocket 关闭: 关闭码 ${event.code}, 原因: ${event.reason}`); if (event.code === 1000) { console.log("连接正常关闭"); } else if (event.code === 1006) { console.log("异常关闭,尝试重连..."); reconnect(); }};function reconnect() { setTimeout(() => { console.log("尝试重连..."); // 重新连接逻辑 }, 3000);} |
|---|
1006 是由客户端生成的关闭码,通常用于无法与服务端正常通信的场景(例如网络中断)。建议在服务端日志中查看异常原因。
Spring 提供了 WebSocketSession.close(CloseStatus) 方法,可以指定关闭码和原因。
1 | session.close(new CloseStatus(4001, "自定义错误: Token 无效")); |
|---|
客户端会在 onclose 事件中接收到此信息。
含义: 连接正常关闭,表明 WebSocket 通信已完成。
应用场景: 客户端或服务端主动关闭连接,释放资源。
示例:
1 | session.close(CloseStatus.NORMAL); |
|---|
含义: 连接关闭是由于某一方离开,例如服务器关闭或浏览器跳转页面。
应用场景: 服务器维护期间关闭连接,或者用户关闭浏览器窗口。
示例:
1 | session.close(CloseStatus.GOING_AWAY); |
|---|
含义: 由于协议错误而关闭连接。
应用场景: 客户端或服务端未遵循 WebSocket 协议(例如发送非法帧)。
示例:
1 | session.close(CloseStatus.PROTOCOL_ERROR); |
|---|
含义: 收到了无法处理的数据类型(例如服务端只接受文本,但收到了二进制消息)。
应用场景: 数据类型不匹配时关闭连接。
示例:
1 | session.close(CloseStatus.NOT_ACCEPTABLE); |
|---|
含义: 连接非正常关闭,例如未发送关闭帧。
应用场景: 网络中断、客户端或服务端崩溃等。
注意: 此状态码仅在客户端或工具中报告,不会出现在关闭帧中。
示例:
123 | if (status.equals(CloseStatus.NO_CLOSE_FRAME)) { // 记录异常并尝试重连} |
|---|
含义: 收到了与消息类型不一致的数据(例如,非 UTF-8 数据)。
应用场景: 数据格式验证失败时关闭连接。
示例:
1 | session.close(CloseStatus.BAD_DATA); |
|---|
含义: 收到的消息违反了服务器的策略。
应用场景: 服务器限制了某些操作或内容(例如,未授权访问)。
示例:
1 | session.close(CloseStatus.POLICY_VIOLATION.withReason("Unauthorized access")); |
|---|
含义: 收到的消息太大,无法处理。
应用场景: 限制消息大小的服务器可能在超出限制时关闭连接。
示例:
1 | session.close(CloseStatus.TOO_BIG_TO_PROCESS); |
|---|
含义: 客户端期望服务器支持某些扩展,但服务器未提供。
应用场景: 客户端无法与服务器达成握手协议。
示例:
1 | session.close(CloseStatus.REQUIRED_EXTENSION.withReason("Missing compression extension")); |
|---|
含义: 服务器由于内部错误无法处理请求。
应用场景: 服务器发生未知异常时关闭连接。
示例:
1 | session.close(CloseStatus.SERVER_ERROR.withReason("Unexpected internal error")); |
|---|
含义: 服务端正在重启,客户端可以稍后重连。
应用场景: 定期维护或部署新版本时关闭连接。
示例:
1 | session.close(CloseStatus.SERVICE_RESTARTED); |
|---|
含义: 服务端过载,建议客户端切换到其他服务器或稍后再试。
应用场景: 服务器资源不足时主动关闭连接。
示例:
1 | session.close(CloseStatus.SERVICE_OVERLOAD); |
|---|
含义: 会话变得不可靠,例如在超时发送消息时。
应用场景: 服务器检测到会话不稳定时可主动关闭连接。
示例:
1 | session.close(CloseStatus.SESSION_NOT_RELIABLE); |
|---|