引言
在实际项目中需要去查询内网环境中的数据库,远程工具之类的都不太太好使,因此考虑到使用 websocket ,实现思路websocket client 实现简单的数据操作然后保持和服务器端长连接,查询内网数据库是通过服务器端向websocket客户端发送操作请求,客户端处理返回数据给服务器端。
这中思路实现的是在本机测试没发现什么问题,但部署到服务器上运行几天后调用的时候经常性的出现 java.io.IOException: Connection reset by peer 异常,而且这种异常在websocket客户端的 @OnError 还捕获不到。于是尝试在本机重现此异常,发现客户端与服务器端网络通信不稳定的情况下会出现这个问题。
异常信息
java.io.IOException: Connection reset by peer
at sun.nio.ch.FileDispatcherImpl.read0(Native Method)
at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39)
at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
at sun.nio.ch.IOUtil.read(IOUtil.java:197)
at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:380)
at org.apache.tomcat.util.net.NioChannel.read(NioChannel.java:147)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1219)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.read(NioEndpoint.java:1161)
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:63)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148)
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
解决
出现此异常之前建立的连接已经不能使用啦因此考虑检查连接是否有效,无效则重新建立连接。
Client client = new Client(); String url = "ws://127.0.0.1:8080/drs-server/db-client.ws/"; URI uri = URI.create(url); WebSocketContainer container = ContainerProvider.getWebSocketContainer(); Session session = null; while(true){ //客户端服务器断开,则重新连接 if(null == session || !session.isOpen()){ try { log.info("检测到与服务器未连接,尝试连接服务器..."); session = container.connectToServer(client,uri); } catch (Exception e) { log.info("连接服务器,"+e.getMessage()); } }else{ //定时发送消息用于心跳检查websocket长连接 session.getBasicRemote().sendPing(ByteBuffer.wrap("PING".getBytes())); log.debug("服务器连接正常"); } Thread.sleep(10*1000); }
已连接的websocket 在网络不通的情况下发送ping数据包会抛异常,以上通过简单的ping 数据库包的发送,客户端没有抛异常就简单认为连接是正常的逻辑在服务器上部署运行几天后发现并不靠谱(不抛异常但连接确实是不能用)。这种客户端没有抛异常的坏死连接,没办法检测到,因此优化逻辑,服务器端定时发送特定的信息作为心跳信息,客户端记录这种心跳信息,超过一定时间没有收到来自服务器端的心跳消息就认为连接有问题就发起重新连接请求。
后续发现性能问题
客户端程序运行十天半个月后程序耗CPU非常厉害,最终升级 websocket 包版本解决问题,应用现在的使用版本:
<dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-websocket</artifactId> <version>8.5.20</version> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-api</artifactId> <version>8.5.20</version> </dependency>