基于Tomcat Servlet实现HTTP与WebSocket代理的问题求助
问题修复方案
1 初始版本报错修复
问题原因
你初始实现的核心问题是:当WebSocket握手成功返回101 Switching Protocols响应后,Tomcat仍然将该连接作为普通HTTP连接处理,会继续尝试解析后续收到的二进制WebSocket帧为HTTP请求头,因此抛出Error parsing HTTP request header错误;同时浏览器收到被Tomcat处理过的异常响应内容,就会报Invalid frame header错误。
修复方案
你后续找到的HttpServletRequest.upgrade方法是正确的解决方案:该方法会通知Tomcat当前连接需要切换协议,不再走HTTP协议解析逻辑,将连接的管理权交给你自定义的HttpUpgradeHandler实现类,握手后的双向数据传输直接在Handler中进行透传即可,这部分你更新后的逻辑已经解决了初始版本的功能问题。
2 连接断开后CPU过高问题修复
问题原因
CPU飙升的核心诱因有两个:
- 你创建的后端Socket仍然在
try-with-resources代码块中声明,当Servlet的service方法执行完成后,try-with-resources会自动关闭这个Socket,导致WsUpgradeHandler中持有的sockIn、sockOut对应的底层连接提前释放。 - 你使用单字节读写流,当流处于非阻塞模式(Tomcat NIO连接器默认特性)时,连接断开后
read()方法会返回0而非-1,你的循环条件仅判断!=-1,就会进入死循环,不断执行读写逻辑消耗CPU。
修复步骤
步骤1:调整Socket生命周期管理
将后端Socket的创建逻辑从try-with-resources中移除,把Socket的生命周期完全交给WsUpgradeHandler管理,仅在Handler的destroy方法中关闭Socket。
步骤2:改用缓冲区批量读写
替换单字节读写逻辑,使用字节缓冲区批量读写,同时判断返回值,避免死循环:
// 替换原有的单字节读循环 byte[] buffer = new byte[4096]; int len; while ((len = servletIn.read(buffer)) != -1) { if (len > 0) { sockOut.write(buffer, 0, len); sockOut.flush(); c += len; } }
后端往浏览器写的逻辑也做相同修改。
步骤3:补充资源联动关闭逻辑
任意一个拷贝任务结束后,主动取消另一个任务,同时关闭所有关联流和Socket,避免资源泄漏:
// 在两个拷贝任务的finally块中都添加触发关闭的逻辑 finally { if (!f1.isDone()) f1.cancel(true); if (!f2.isDone()) f2.cancel(true); try { wc.close(); } catch (IOException e) { // 忽略关闭异常 } destroy(); }
步骤4:配置Socket参数
创建后端Socket时添加必要的TCP配置,避免连接假死:
Socket sock = new Socket(u.getHost(), u.getPort()); sock.setKeepAlive(true); sock.setTcpNoDelay(true);
内容的提问来源于stack exchange,提问作者akarnokd
相关产品推荐
相关产品推荐

