Socket.close卡住了15分钟。

huangapple go评论76阅读模式
英文:

Socket.close stuck for 15 minutes

问题

我已经开发了自己的MMORPG游戏客户端和服务器,使用了SSLSockets。新的连接在它们自己的线程中获得数据输出/输入流。在我的主线程循环中,我有一个方法遍历连接映射并关闭连接(不活动踢出,请求注销等)。

我的游戏服务器会随机挂起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),
...
com.jayavon.game.server.MyServer.processClientConnectionMapV2(MyServer.java:6427),
com.jayavon.game.server.MyServer.main(MyServer.java:708)]

根据这些信息,我可以看出服务器在尝试关闭连接时出现了卡顿:

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

以下是导致卡顿的精简方法(这会导致整个服务器挂起):

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);
    }
}

我最近已经多次重写了连接代码,以尝试修复这些卡顿问题,现在终于找到了根本原因,但我不确定如何解决。

英文:

I've developed my own MMORPG game client and server using SSLSockets. New connections get a dataoutput/input stream in their own threads. In my main thread loop, I have a method that goes through the connection map and closes connections (inactivity kicks, requested logouts, etc.).

My game server randomly hangs for 15 minutes so every 60 seconds, in another thread, I print out a stacktrace and how many loops the main thread has run.

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)]

From this I can tell that my server gets stuck when trying to close connections:

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

Here is the condensed method it is getting stuck running (which hangs the whole server):

private void processClientConnectionMapV2(boolean disconnectAll) {
try {
Iterator&lt;Entry&lt;String, ClientConnectionV2&gt;&gt; it = MyServer.ClientConnectionMapV2.entrySet().iterator();
while (it.hasNext()) {
Entry&lt;String, ClientConnectionV2&gt; clientConnectionEntry = it.next();
if (clientConnectionEntry.getValue().isToDisconnect()
|| disconnectAll){ //this connection should be disconnected
if (!hasOutstandingBacklogMessagesV2(MyServer.ClientConnectionMapV2.get(clientConnectionEntry.getKey()))
|| clientConnectionEntry.getValue().getDisconnectLoopsSkipped() &gt; 4
|| disconnectAll) {
MyServer.LOGGER.warn(&quot;----REMOVE CONNECTION----&lt;processClientConnectionMapV2&gt;----    &quot; + 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(&quot;unable to close in Server processClientConnectionsV2&quot;, e1);
LOGGER.warn(&quot;connection was already closed on client side&quot;);
}
//remove from iterator and ClientConnectionMapV2
it.remove();
} else {
clientConnectionEntry.getValue().setDisconnectLoopsSkipped(clientConnectionEntry.getValue().getDisconnectLoopsSkipped() + 1);
MyServer.LOGGER.warn(&quot;----SKIP REMOVE CONNECTION----&lt;processClientConnectionMapV2&gt;----    &quot; + clientConnectionEntry);
}
}
}
} catch (Exception e) {
LOGGER.fatal(&quot;MAIN LOOP: processClientConnectionsV2: &quot;, e);
}
}

I've rewritten my connection code so much recently to try to fix the hangs and I have finally found the root cause - but I'm unsure how to address it.

答案1

得分: 0

对于可能会发现这个的任何人,以下是我是如何解决的。我不能仅仅依赖于setSoTimeout方法,因为我想要随时有能力踢出玩家。

Thread closerThread = new Thread(){
    public void run(){
        // 在这里关闭实际连接
        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("无法在服务器processClientConnectionsV2中关闭", e1);
        }
    }
};
closerThread.start();
英文:

For anyone who may find this here is how I solved it. I was unable to just rely on the setSoTimeout method as I wanted the ability to kick players ad hoc.

Thread closerThread = new Thread(){
public void run(){
//close the actual connection here
try {
Thread.currentThread().setName(&quot;closerThread-&quot; + clientConnectionEntry.getKey() + &quot;-&quot; + tmpAccountName + &quot;|&quot; + 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(&quot;unable to close in Server processClientConnectionsV2&quot;, e1);
}
}
};
closerThread.start();

huangapple
  • 本文由 发表于 2020年8月29日 11:38:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/63643173.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定