作为后续行动,以一个最近的问题 ,我不知道为什么它是不可能在Java中,没有尝试读/写在TCP套接字,检测到该插座已经被对被正常关闭? 这似乎是这样,无论一个人是否使用预NIO Socket
或NIO SocketChannel
。
当对等正常关闭TCP连接时,TCP协议栈在连接的双方都知道的事实。 服务器端(即启动关闭的)状态结束FIN_WAIT2
,而客户端(就是那个没有明确的回应关机)状态结束CLOSE_WAIT
。 为什么没有在方法Socket
或SocketChannel
可以查询TCP堆栈看到底层TCP连接是否已经终止? 难道TCP堆栈不能提供这种状态信息? 或者它是一个设计原则以避免昂贵的呼叫到内核?
与谁已经发布了一些这个问题的答案的用户的帮助,我想我看到的问题可能是来自何处。 不明确关闭连接的侧TCP状态结束CLOSE_WAIT
意思是连接在关闭的过程中,等待在一旁发出自己的CLOSE
操作。 我想这是公平的,以至于isConnected
返回true
和isClosed
返回false
,但为什么没有像isClosing
?
以下是使用预NIO套接字的测试类。 但相同的结果用NIO获得。
import java.net.ServerSocket;
import java.net.Socket;
public class MyServer {
public static void main(String[] args) throws Exception {
final ServerSocket ss = new ServerSocket(12345);
final Socket cs = ss.accept();
System.out.println("Accepted connection");
Thread.sleep(5000);
cs.close();
System.out.println("Closed connection");
ss.close();
Thread.sleep(100000);
}
}
import java.net.Socket;
public class MyClient {
public static void main(String[] args) throws Exception {
final Socket s = new Socket("localhost", 12345);
for (int i = 0; i < 10; i++) {
System.out.println("connected: " + s.isConnected() +
", closed: " + s.isClosed());
Thread.sleep(1000);
}
Thread.sleep(100000);
}
}
当测试客户端连接到测试服务器的输出保持不变,即使在服务器发起连接的关闭:
connected: true, closed: false
connected: true, closed: false
...
Answer 1:
我一直在使用套接字的时候,大多是选择器,虽然不是网络OSI专家,从我的理解,调用shutdownOutput()
的插座上实际发送一些网络(FIN)是唤醒我的另一面选择上(同在C语言中的行为)。 在这里,你有检测 :实际检测的读操作,当你尝试将失败。
在你给的代码,关闭套接字将关闭输入和输出流,没有阅读,可能是可用的数据的可能性,因此失去他们。 在Java Socket.close()
方法执行在离开输出流中的数据将被发送后跟一个FIN发信号紧密其一个“正常”断开(相对正如我最初以为)。 翅片将由另一侧ACK'd,因为任何常规分组将1。
如果您需要等待对方关闭其插座,你需要等待它的FIN。 而要达到这一点,你必须检测Socket.getInputStream().read() < 0
这意味着你不应该关闭插座,因为这将关闭其InputStream
。
从我用C在Java中那样,现在,实现这种同步的紧密应该做的事是这样的:
- 关闭插座输出(发送在另一端FIN,这是永远不会使用此套接字发送的最后一件事)。 输入仍然是开放的,所以你可以
read()
和检测远程close()
- 阅读套接字
InputStream
直到我们从另一端收到答复FIN(因为它会检测到FIN,它会经过同样的优雅diconnection过程)。 因为他们实际上并不只要其缓冲区的一个仍然包含数据关闭套接字这是在一些重要的操作系统。 他们所谓的“鬼”插座,并在操作系统(用现代的操作系统了,可能不是一个问题)使用了描述符号码 - 关闭插座(通过调用
Socket.close()
或关闭它InputStream
和OutputStream
)
正如下面的Java代码片段所示:
public void synchronizedClose(Socket sok) {
InputStream is = sok.getInputStream();
sok.shutdownOutput(); // Sends the 'FIN' on the network
while (is.read() > 0) ; // "read()" returns '-1' when the 'FIN' is reached
sok.close(); // or is.close(); Now we can close the Socket
}
当然, 双方都有使用结束以同样的方式,或发送部分可能总是发送足够的数据来保持while
循环忙(例如,如果发送部分只发送数据,而不会读来检测连接终止。这是笨拙,但你可能没有这方面的控制)。
作为@WarrenDew在他的评论中指出,在程序(应用层)丢弃该数据导致在应用层的非正常断开:虽然在TCP层(被接收的所有数据while
环),它们将被放弃。
1:从“ Java中的基本网络 ”:见图。 3.3第45页,和整个第3.7节,第43-48
Answer 2:
我认为这更多的是socket编程问题。 Java的只是跟随套接字编程的传统。
从维基百科 :
TCP提供的字节从一个程序在一台计算机上的流的另一台计算机上的另一个程序可靠,有序传递。
一旦握手完成,TCP不会使两个端点(客户端和服务器)之间的任何区别。 术语“客户端”和“服务器”主要是为了方便。 因此,“服务器”可以发送数据,“客户”可以同时发送一些其他的数据给对方。
术语“关闭”也是误导。 这里只有FIN声明,表示“我不会向您发送任何更多的东西。” 但是,这并不意味着不存在数据包在飞行中,或其他有没有更多的话要说。 如果您实现作为数据链路层,或者如果你的分组行经路线不同蜗牛邮件,则可能是接收器在错误的顺序接收数据包。 TCP知道如何解决这个给你。
另外你,作为一个节目,可能没有时间继续检查什么的缓冲区。 所以,在你方便的时候可以检查什么的缓冲区。 总而言之,当前套接字实现不是那么糟糕。 如果确实是isPeerClosed(),这是你必须让你想打电话每次读取额外调用。
Answer 3:
底层套接字API没有这样的通知。
发送TCP堆栈将不会发送FIN位,直到最后一个数据包无论如何,所以有可能是很多从当发送应用程序逻辑关闭了其套接字之前甚至被发送的数据缓存数据。 同样地,这是缓冲,因为网络比接收应用程序(我不知道,也许你中继它在较慢的连接)可能是接收器显著,你不想接收应用程序放弃它更快的数据只是因为FIN位已被堆栈接收。
Answer 4:
由于没有一个答案,到目前为止完全回答这个问题,我总结我目前对问题的理解。
当建立了TCP连接,并一个对等调用close()
或shutdownOutput()
其插座上,在连接的过渡的另一侧插入插座CLOSE_WAIT
状态。 原则上,它可以从TCP堆栈找出一个插座是否处于CLOSE_WAIT
状态,而无需调用read/recv
(例如, getsockopt()
在Linux上: http://www.developerweb.net/forum/showthread.php?t = 4395 ),但是这不便于携带。
Java的Socket
类似乎旨在提供一个抽象媲美BSD TCP套接字,可能是因为这是一个抽象的水平,人们使用的编程TCP / IP的应用程序时,其。 BSD套接字是支持不仅仅是INET(如TCP)以外的其他插槽的推广,因此他们不提供找出插座的TCP状态的可移植的方式。
有没有像法isCloseWait()
因为在由BSD套接字提供抽象的层次用来编程TCP的应用程序的人不希望的Java提供任何额外的方法。
Answer 5:
检测(TCP)套接字连接的远程端是否已经关闭可以与java.net.Socket.sendUrgentData(int)方法来进行,并捕获它抛出IOException - 如果远程端是向下。 这已经被Java的Java和Java的-C之间进行测试。
这避免了设计的通信协议使用某种查验机制的问题。 通过在插座(setOOBInline(假)禁用OOBINLINE,任何接收到的OOB数据被丢弃,但OOB数据仍然被发送。如果远程侧是封闭的,一个连接重置尝试,失败,并导致一些抛出IOException 。
如果你真的在你的协议使用OOB数据,那么你可能会有所不同。
Answer 6:
当它被上突然拆除破坏了Java IO栈绝对发送FIN。 它只是没有任何意义,你不能检测出来,B / C,如果他们要关闭的连接大多数客户只发送了FIN。
...另一个原因我真的开始恨NIO Java类。 好像一切是一点半屁股。
Answer 7:
这是一个有趣的话题。 我已经通过Java代码挖刚才检查。 从我发现,有两个不同的问题:第一是TCP RFC本身,它允许用于远程关闭套接字在半双工传输数据,所以远程关闭插座仍然是半开着的。 ,按照RFC,RST不关闭连接,你需要发送一个明确的终止命令; 所以Java允许通过半封闭套接字发送数据
(有两种方法用于在两个端点的读取关闭状态)。
另一个问题是,实现说,这种行为是可选的。 从Java努力成为便携式的,他们实现最佳的共同特征。 维护地图(OS,实现半双工)的会是一个问题,我猜。
Answer 8:
这是Java的(和所有其他人,我已经看了)OO套接字类的缺陷 - 不能访问select系统调用。
正确答案在C:
struct timeval tp;
fd_set in;
fd_set out;
fd_set err;
FD_ZERO (in);
FD_ZERO (out);
FD_ZERO (err);
FD_SET(socket_handle, err);
tp.tv_sec = 0; /* or however long you want to wait */
tp.tv_usec = 0;
select(socket_handle + 1, in, out, err, &tp);
if (FD_ISSET(socket_handle, err) {
/* handle closed socket */
}
Answer 9:
这里是一个蹩脚的解决方法。 使用SSL)和SSL不上拆卸紧密握手,这样你就通知被关闭插座(大多数实现似乎做一个PROPERT握手拆除即是)。
Answer 10:
这样做的原因行为(这是不是Java的具体)是事实,你没有得到从TCP协议栈的任何状态信息。 毕竟,插座是另一个文件句柄,你不能找出是否有实际的数据从中读取数据,而无需实际尝试( select(2)
不会帮助那里,只是信号,您可以尝试不阻塞) 。
欲了解更多信息,请参阅Unix套接字常见问题解答 。
Answer 11:
只写要求的数据包交换,允许连接的损失来确定。 一个常见的解决办法是使用保持连接的选项。
Answer 12:
当谈到与半开放的Java插座,一个可能想看看isInputShutdown()和isOutputShutdown() 。
文章来源: Why is it impossible, without attempting I/O, to detect that TCP socket was gracefully closed by peer?