最近在做一个实时股票行情推送系统时,遇到了一个让人头疼的问题:WebSocket连接总是莫名其妙断开,浏览器控制台不断报错"WebSocket is already in CLOSING or CLOSED state"。刚开始以为是网络问题,后来发现每次推送大段K线数据时就会触发这个错误。
经过排查发现,这其实是WebSocket的一个自我保护机制。就像快递员送包裹,如果包裹太大超过了他的承载能力,他就会直接拒绝派送。WebSocket默认设置了一个8192字节(约8KB)的数据长度限制,当传输的数据超过这个限制时,连接就会自动进入关闭流程(CLOSING状态),最终完全断开(CLOSED状态)。
这个限制在不同的服务器环境中表现略有不同:
有趣的是,这个限制其实是为了防止恶意攻击和内存溢出。想象一下如果有人故意发送超大数据包,服务器内存可能就会被撑爆。所以这个限制就像是一个安全阀,虽然有时候会给开发带来麻烦,但确实很有必要。
遇到WebSocket连接异常时,首先要确认是不是数据长度导致的。这里分享几个我常用的排查方法:
方法一:监控数据包大小
javascript复制// 前端发送数据时计算大小
const data = JSON.stringify({...});
console.log('Data size:', new Blob([data]).size);
// 后端接收时打印长度
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("Received message length: " + message.length());
}
方法二:逐步增加测试数据
我通常会准备几组测试数据:
通过对比测试,如果发现只有大数据包会触发问题,那基本可以确定是长度限制导致的。
方法三:查看服务器日志
Tomcat会在日志中输出类似这样的警告:
code复制org.apache.tomcat.websocket.pojo.PojoMessageHandlerWhole - Message too large
对于传统的Java Web应用,修改web.xml是最直接的解决方案。就像给快递员换辆更大的货车,让他能运送更大的包裹。
具体配置如下:
xml复制<context-param>
<param-name>org.apache.tomcat.websocket.textBufferSize</param-name>
<param-value>52428800</param-value> <!-- 50MB文本缓冲区 -->
</context-param>
<context-param>
<param-name>org.apache.tomcat.websocket.binaryBufferSize</param-name>
<param-value>52428800</param-value> <!-- 50MB二进制缓冲区 -->
</context-param>
这里有几个注意事项:
我曾经在一个在线协作编辑项目中,把这个值设成了100MB,结果服务器内存使用量明显上升。后来经过测试发现20MB就足够用了,调整后内存占用下降了60%。
Spring Boot项目没有web.xml,需要采用编程式配置。这就好比要给快递公司总部打电话,让他们给所有快递员都换上更大的货车。
配置类示例如下:
java复制@Configuration
public class WebSocketConfig implements ServletContextInitializer {
@Override
public void onStartup(ServletContext servletContext) {
servletContext.setInitParameter(
"org.apache.tomcat.websocket.textBufferSize",
"52428800"); // 50MB
servletContext.setInitParameter(
"org.apache.tomcat.websocket.binaryBufferSize",
"52428800"); // 50MB
}
}
这里有个坑我踩过:如果你使用外置Tomcat部署,必须确保Tomcat版本≥8.5.x。我在客户现场调试时发现Tomcat 8.0.36不认这个配置,升级到8.5.31后问题解决。
Nginx代理的情况
如果WebSocket经过Nginx转发,还需要调整Nginx配置:
nginx复制server {
location /ws/ {
proxy_buffer_size 64k;
proxy_buffers 4 128k;
proxy_busy_buffers_size 256k;
}
}
前端优化建议
即使后端调整了限制,前端也应该做好数据分片:
javascript复制function sendLargeData(data) {
const chunkSize = 8000; // 留点余量
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
websocket.send(chunk);
}
}
监控与告警
建议在项目中添加连接状态监控:
java复制@OnError
public void onError(Session session, Throwable error) {
metrics.increment("websocket.error");
if (error.getMessage().contains("too large")) {
alertManager.notify("WebSocket数据超限警告");
}
}
在实际项目中,我总结了几个常见问题:
版本兼容性问题
内存占用考量
在调整缓冲区大小时要平衡性能和内存:
java复制// 不合理的超大配置
servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize", "524288000"); // 500MB
这样设置可能会导致服务器内存快速耗尽。
混合内容问题
有些项目同时使用WebSocket和HTTP,要注意:
properties复制# application.properties中需要同时配置
server.tomcat.max-http-post-size=50MB
测试策略
建议采用阶梯测试法:
在金融行情推送系统中,我们最终采用的方案是:
这套方案稳定运行了两年多,日均处理消息量超过5000万条,再也没有出现过因数据长度导致的连接中断问题。