Java套接字API:如何判断连接是否已关闭?


92

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

呼叫isConnected()已断开远程似乎总是返回一个插座上true。同样,调用isClosed()已远程关闭的套接字似乎总是会返回false。我读过要真正确定套接字是否已关闭,必须将数据写入输出流,并且必须捕获异常。这似乎是处理这种情况的一种非常不干净的方法。我们只需要不断地在网络上发送垃圾邮件来知道套接字何时关闭。

还有其他解决方案吗?

Answers:


183

没有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版本或平台所特有的。


18
很难相信面向连接的TCP协议甚至都不知道其连接状态...提出该协议的家伙难道闭着眼睛开车吗?
PedroD 2014年

31
@PedroD相反:这是故意的。以前的协议套件(例如SNA)具有“拨号音”。TCP旨在经受核战争的考验,更重要的是,它可以经受路由器的起伏之苦:因此,完全没有拨号音,连接状态等信息;这也是为什么TCP保持连接在RFC中被描述为有争议的功能的原因,以及为什么默认情况下始终将其关闭的原因。TCP仍在我们身边。SNA?IPX?ISO?不。他们说对了。
2014年

3
我认为这不是向我们隐藏此信息的好借口。知道连接丢失并不一定意味着该协议的容错性较低,它始终取决于我们对该知识的处理方式...对我来说,来自Java的isBound和isConnected方法是纯模拟方法,它们没有用,但表示需要连接事件侦听器...但我重申:知道连接丢失并不会使协议更糟。现在,如果您所说的那些协议一旦发现连接丢失就终止了连接,那就另当别论了。
PedroD 2014年

21
@Pedro你不明白。这不是“借口”。没有任何信息可以保留。没有拨号音。TCP 直到您尝试对其进行操作之前都不知道连接是否失败那是基本的设计标准。
2014年

2
@EJP感谢您的回答。您也许知道如何检查插座是否在没有“阻塞”的情况下关闭了吗?使用read或readLine将阻塞。有没有一种方法可以实现是否用可用的方法关闭了另一个套接字,它似乎返回0而不是-1
总结2014年

9

在各种消息传递协议中,通常的做法是保持彼此心跳(保持发送ping数据包),数据包不需要很大。探测机制将使您甚至可以在TCP识别出断开连接的客户端之前就检测出断开连接的客户端(TCP超时要高得多),发送探测并等待5秒钟才能得到答复,如果您没有看到2-3的答复随后的探测,您的播放器已断开连接。

另外,相关问题


6
某些协议中,这是“通用做法” 。我注意到,HTTP是地球上最常用的应用程序协议,没有PING操作。
罗恩侯爵,

2
@ user207421,因为HTTP / 1是一种协议。您发送请求,您会收到响应。您完成了。套接字已关闭。Websocket扩展确实具有PING操作,HTTP / 2也具有PING操作。
米哈尔Zabielski

如果可能的话,我建议使用单个字节进行心跳:)
Stefan Reich

@MichałZabielski这是因为HTTP的设计者没有指定它。你不知道为什么不。HTTP / 1.1不是一次性协议。套接字未关闭:默认情况下,HTTP连接是持久的。FTP,SMTP,POP3,IMAP,TLS等...没有心跳信号。
罗恩侯爵,

2

我看到了刚刚发布的其他答案,但我认为您正在与玩游戏的客户互动,因此我可能提出另一种方法(而BufferedReader在某些情况下肯定是有效的)。

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

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

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

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

我认为抽象语言的特征之一是您是从细节中抽象出来的。考虑一下SqlConnection的C#中的using关键字(try / finally)或其他什么……这只是做生意的代价……我认为try / catch / finally是Socket使用的公认且必要的模式。


无论有没有通知您,本地系统都无法“关闭套接字”。您的问题开始于“如果您的Java代码没有关闭/断开套接字...?” 也没有任何意义。
罗恩侯爵,

1
究竟是什么阻止系统(例如Linux)强制关闭套接字?是否仍然可以使用“调用关闭”命令从“ gdb”执行此操作?
Andrey Lebedenko '16

@AndreyLebedenko没有“完全阻止它”的功能,但没有做到。
罗恩侯爵

@EJB是谁呢?
安德烈·列别登科

@AndreyLebedenko没有人这样做。只有应用程序可以关闭自己的套接字,除非不退出就退出,在这种情况下,操作系统将被清理。
罗恩侯爵

1

我认为这是tcp连接的本性,按照标准,传输大约需要6分钟的静默时间,然后我们才能得出输出连接消失的结论!因此,我认为您无法找到此问题的确切解决方案。也许更好的方法是编写一些方便的代码来猜测服务器何时应该关闭用户连接。


2
最多可能要花两个小时。
罗恩侯爵,

0

正如@ 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 !");

0

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


-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


您不需要打readLine()两次电话。您已经知道它在else块中为空。
罗恩侯爵,

-4

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


send()仅当传出的数据被缓冲足够长的时间以使发送计时器到期时,调用才会返回-1。由于两端都有缓冲并且在后台具有异步特性,因此几乎可以肯定,断开连接后的第一次发送不会发生这种情况send()
罗恩侯爵,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.