107

我遇到了 Java 套接字 API 的一些问题。我正在尝试显示当前连接到我的游戏的玩家数量。很容易确定玩家何时连接。但是,使用套接字 API 确定玩家何时断开连接似乎是不必要的困难。

调用isConnected()已远程断开的套接字似乎总是返回true。同样,调用isClosed()已远程关闭的套接字似乎总是返回false。我已经读过,要实际确定套接字是否已关闭,必须将数据写入输出流并且必须捕获异常。这似乎是处理这种情况的一种非常不干净的方式。我们只需要不断地通过网络发送垃圾消息,才能知道套接字何时关闭。

还有其他解决方案吗?

4

9 回答 9

208

没有 TCP API 可以告诉您连接的当前状态。isConnected()并告诉您套接字isClosed()的当前状态。不是一回事。

  1. isConnected()告诉你是否连接这个套接字。你有,所以它返回true。

  2. isClosed()告诉您是否关闭此套接字。在你拥有之前,它会返回 false。

  3. 如果对端已经有序地关闭了连接

    • read()返回 -1
    • readLine()返回null
    • readXXX()抛出EOFException任何其他 XXX。

    • 写入将抛出IOException:“对等方重置连接”,最终会受到缓冲延迟的影响。

  4. 如果连接由于任何其他原因而断开,则写入IOException最终会抛出一个 ,如上所述,而读取可能会做同样的事情。

  5. 如果对等方仍然连接但未使用连接,则可以使用读取超时。

  6. 与您在其他地方可能读到的相反,ClosedChannelException并没有告诉您这一点。[也没有SocketException: socket closed.] 它只告诉你关闭了频道,然后继续使用它。换句话说,您的编程错误。它并不表示已关闭连接。

  7. 作为在 Windows XP 上使用 Java 7 进行的一些实验的结果,如果:

    • 你正在选择OP_READ
    • select()返回大于零的值
    • 关联SelectionKey的已经无效(key.isValid() == false

    这意味着对等方已重置连接。然而,这可能是 JRE 版本或平台所特有的。

于 2012-04-20T06:00:43.773 回答
11

在各种消息传递协议中,通常的做法是保持彼此心跳(不断发送 ping 数据包),数据包不需要非常大。探测机制将允许您检测断开连接的客户端,甚至在 TCP 发现它之前(TCP 超时时间要长得多)发送一个探测并等待 5 秒的回复,如果您没有看到回复说 2-3随后的探测,您的播放器已断开连接。

另外,相关问题

于 2012-04-20T05:37:02.100 回答
2

我看到刚刚发布的另一个答案,但我认为您与玩游戏的客户互动,所以我可能会提出另一种方法(而 BufferedReader 在某些情况下绝对有效)。

如果您想...您可以将“注册”责任委托给客户。即,您将拥有一组已连接用户,并在从每个用户收到的最后一条消息上带有时间戳……如果客户端超时,您将强制重新注册客户端,但这会导致下面的引用和想法。

我已经读过,要实际确定套接字是否已关闭,必须将数据写入输出流并且必须捕获异常。这似乎是处理这种情况的一种非常不干净的方式。

如果您的 Java 代码没有关闭/断开 Socket,那么如何通知您远程主机关闭了您的连接?最终,您的 try/catch 所做的事情与在 ACTUAL 套接字上侦听事件的轮询器所做的事情大致相同。考虑以下:

  • 您的本地系统可以在不通知您的情况下关闭您的套接字......这只是 Socket 的实现(即它不会轮询硬件/驱动程序/固件/任何状态更改)。
  • 新的 Socket(Proxy p)... 多方(实际上是 6 个端点)可能正在关闭您的连接...

我认为抽象语言的特点之一是你从细节中抽象出来。想想 C# 中的 using 关键字(try/finally)用于 SqlConnection 或其他什么......这只是做生意的成本......我认为 try/catch/finally 是 Socket 使用的公认和必要模式。

于 2012-04-20T05:40:27.057 回答
2

我遇到了类似的问题。在我的情况下,客户端必须定期发送数据。我希望你有同样的要求。然后我设置 SO_TIMEOUT ,当指定时间到期时socket.setSoTimeout(1000 * 60 * 5);抛出。java.net.SocketTimeoutException然后我可以很容易地检测到死客户。

于 2020-08-24T02:30:34.940 回答
1

我认为这是 tcp 连接的本质,在该标准中,在我们断定输出连接消失之前,传输需要大约 6 分钟的静默时间!所以我不认为你可以找到这个问题的确切解决方案。也许更好的方法是编写一些方便的代码来猜测服务器何时应该假设用户连接关闭。

于 2012-04-20T05:55:20.703 回答
1

正如@user207421 所说,由于 TCP/IP 协议体系结构模型,无法知道连接的当前状态。所以服务器必须在关闭连接之前通知你,或者你自己检查。
这是一个简单的例子,展示了如何知道服务器关闭了套接字:

sockAdr = new InetSocketAddress(SERVER_HOSTNAME, SERVER_PORT);
socket = new Socket();
timeout = 5000;
socket.connect(sockAdr, timeout);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream());
while ((data = reader.readLine())!=null) 
      log.e(TAG, "received -> " + data);
log.e(TAG, "Socket closed !");
于 2019-01-03T12:29:26.170 回答
1

在这里,您是任何数据类型的另一种通用解决方案。

int offset = 0;
byte[] buffer = new byte[8192];

try {
    do {
        int b = inputStream.read();

        if (b == -1)
           break;

        buffer[offset++] = (byte) b;

        //check offset with buffer length and reallocate array if needed
    } while (inputStream.available() > 0);
} catch (SocketException e) {
    //connection was lost
}

//process buffer
于 2020-11-26T21:48:41.957 回答
-2

我就是这样处理的

 while(true) {
        if((receiveMessage = receiveRead.readLine()) != null ) {  

        System.out.println("first message same :"+receiveMessage);
        System.out.println(receiveMessage);      

        }
        else if(receiveRead.readLine()==null)
        {

        System.out.println("Client has disconected: "+sock.isClosed()); 
        System.exit(1);
         }    } 

如果 result.code == null

于 2019-08-01T23:03:23.583 回答
-4

在 Linux 上,当 write() 到您不知道的另一端已关闭的套接字时,将引发 SIGPIPE 信号/异常,但是您想调用它。但是,如果您不想被 SIGPIPE 发现,您可以使用带有 MSG_NOSIGNAL 标志的 send()。send() 调用将返回 -1,在这种情况下,您可以检查 errno,它会告诉您您尝试使用 EPIPE 值写入损坏的管道(在这种情况下为套接字),根据 errno.h 相当于32. 作为对 EPIPE 的反应,您可以加倍返回并尝试重新打开套接字并尝试再次发送您的信息。

于 2017-05-31T13:29:02.083 回答