Socket.close 卡住了 15 分钟

Socket.close stuck for 15 minutes

我使用 SSLSockets 开发了自己的 MMORPG 游戏客户端和服务器。新连接在它们自己的线程中获得 dataoutput/input 流。在我的主线程循环中,我有一个方法可以通过连接映射并关闭连接(不活动踢、请求注销等)。

我的游戏服务器随机挂起 15 分钟,所以每隔 60 秒,我在另一个线程中打印出一个堆栈跟踪以及主线程有多少个循环 运行。

main: [sun.misc.Unsafe.park(Native Method),
java.util.concurrent.locks.LockSupport.park(LockSupport.java:175),
java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836),
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870),
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199),
java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209),
java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285),
sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:863),
sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:735),
sun.security.ssl.SSLSocketImpl.sendAlert(SSLSocketImpl.java:2087),
sun.security.ssl.SSLSocketImpl.warning(SSLSocketImpl.java:1914),
sun.security.ssl.SSLSocketImpl.closeInternal(SSLSocketImpl.java:1677),
sun.security.ssl.SSLSocketImpl.close(SSLSocketImpl.java:1615),
com.jayavon.game.server.MyServer.processClientConnectionMapV2(MyServer.java:6427),
com.jayavon.game.server.MyServer.main(MyServer.java:708)]

由此我可以看出我的服务器在尝试关闭连接时卡住了:

clientConnectionEntry.getValue().getSocket().close();

这是它被卡住的浓缩方法 运行ning(挂起整个服务器):

private void processClientConnectionMapV2(boolean disconnectAll) {
    try {
        Iterator<Entry<String, ClientConnectionV2>> it = MyServer.ClientConnectionMapV2.entrySet().iterator();
        while (it.hasNext()) {
            Entry<String, ClientConnectionV2> clientConnectionEntry = it.next();
            
            if (clientConnectionEntry.getValue().isToDisconnect()
                    || disconnectAll){ //this connection should be disconnected
                if (!hasOutstandingBacklogMessagesV2(MyServer.ClientConnectionMapV2.get(clientConnectionEntry.getKey()))
                        || clientConnectionEntry.getValue().getDisconnectLoopsSkipped() > 4
                        || disconnectAll) {
                    MyServer.LOGGER.warn("----REMOVE CONNECTION----<processClientConnectionMapV2>----    " + clientConnectionEntry);
                    
                    //close the actual connection here
                    try {
                        clientConnectionEntry.getValue().getSocket().close();
                        clientConnectionEntry.getValue().getDataInputStream().close();
                        clientConnectionEntry.getValue().getDataOutputStream().close();
                        clientConnectionEntry.getValue().getCommandHandlerV2().loggedOut = true;
                        serverConnectionNumber--;
                    } catch (Exception e1) {
                        LOGGER.warn("unable to close in Server processClientConnectionsV2", e1);
                        LOGGER.warn("connection was already closed on client side");
                    }

                    //remove from iterator and ClientConnectionMapV2
                    it.remove();
                } else {
                    clientConnectionEntry.getValue().setDisconnectLoopsSkipped(clientConnectionEntry.getValue().getDisconnectLoopsSkipped() + 1);
                    MyServer.LOGGER.warn("----SKIP REMOVE CONNECTION----<processClientConnectionMapV2>----    " + clientConnectionEntry);
                }
            }
        }
    } catch (Exception e) {
        LOGGER.fatal("MAIN LOOP: processClientConnectionsV2: ", e);
    }
}

我最近重写了我的连接代码以尝试修复挂起,我终于找到了根本原因 - 但我不确定如何解决它。

对于任何可能在此处找到此问题的人,我都是如何解决它的。我不能仅仅依赖 setSoTimeout 方法,因为我想要临时踢球员的能力。

Thread closerThread = new Thread(){
    public void run(){
        //close the actual connection here
        try {
            Thread.currentThread().setName("closerThread-" + clientConnectionEntry.getKey() + "-" + tmpAccountName + "|" + tmpCharacterName);
            clientConnectionEntry.getValue().getCommandHandlerV2().outgoingMessageBacklog.clear();
            clientConnectionEntry.getValue().getCommandHandlerV2().loggedOut = true;
            serverConnectionNumber--;
            clientConnectionEntry.getValue().getSocket().close();
            clientConnectionEntry.getValue().getDataInputStream().close();
            clientConnectionEntry.getValue().getDataOutputStream().close();
        } catch (Exception e1) {
            LOGGER.warn("unable to close in Server processClientConnectionsV2", e1);
        }
    }
};
closerThread.start();