我在我的应用程序中使用TCP。即使优雅地关闭套接字,我也面临数据丢失问题。 这是复制场景的两个示例程序。
// TCP Sender程序不断发送数据
public class TCPClient {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket soc = new Socket("xx.xx.xx.xx",9999);
OutputStream stream = soc.getOutputStream();
int i=1 ;
while(true) {
System.out.println("Writing "+ i);
stream.write(i++);
stream.flush();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
// TCP服务器/接收器
public class TCPServer2 {
public static void main(String[] args) throws IOException {
System.out.println("program started");
ServerSocket serverSock = new ServerSocket(9999);
Socket socket =serverSock.accept();
System.out.println("client connected");
InputStream stream = socket.getInputStream();
InputStreamReader reader = null;
reader = new InputStreamReader(stream);
socket.setTcpNoDelay(true);
int n=0;
int i=1;
while (true) {
try {
n = reader.read();
System.out.println("Msg No.: "+n);
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
break;
}
if(i==5) {
System.out.println("closing socket");
socket.close();
//client should get exception while sending 6th msg
break;
}i++;
}
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
br.readLine();
}
}
现在理想情况下,TCPClient程序在发送第6条消息时会出现异常,但在发送第7条消息时会出现异常。除了使用高级协议和SO_LINGER之外,还有什么办法可以避免这种数据丢失问题(在这个程序中,linger确实有帮助,但在其他几种情况下可能会导致数据丢失问题)?
注意:如果我们使用两台不同的Windows机器,就会出现此消息丢失问题。它在同一台机器上工作正常。
答案 0 :(得分:3)
EJP是正确的,但我认为您的问题是有没有办法避免数据丢失而不使用更高级别的协议?在你的场景中,答案是否定的。
为什么? 要理解为什么我们需要理解流产的密切与优雅的套接字关闭行为之间的区别。
要理解这种区别,我们需要了解TCP协议级别会发生什么。将已建立的TCP连接想象成实际上是两个独立的,半独立的数据流是有帮助的。如果两个对等体是A和B,则一个流将数据从A传送到B,另一个流从B传送到A.有序释放分两个阶段进行。第一个方面(比如A)决定停止发送数据并将FIN消息发送到B.当B方的TCP堆栈接收到FIN时,它知道没有更多的数据来自A,并且只要B读取关闭套接字的所有前面的数据,进一步读取将返回值-1以指示文件结束。此过程称为TCP半关闭,因为只有一半的连接被关闭。毫不奇怪,另一半的程序完全相同。 B向A发送FIN消息,A在读取A发送的所有先前数据后最终收到-1。
相反,中止关闭使用RST(重置)消息。如果任何一方发出RST,这意味着整个连接被中止,并且TCP堆栈可以丢弃任何应用程序尚未发送或接收的排队数据。
发送TCP FIN消息表示"我已完成发送",而Socket.close()表示"我已完成发送和接收。"当你调用Socket.close()时,显然不再可能发送数据;但是,也不可能接收数据。那么会发生什么,例如,当A通过关闭套接字尝试有序释放时,但B继续发送数据?这在TCP规范中是完全允许的,因为就TCP而言,只有一半的连接已经关闭。但是,由于A的套接字已关闭,如果B应继续发送,则没有人可以读取数据。在这种情况下,A的TCP堆栈必须发送RST以强制终止连接。
您的方案中可能的解决方案: 使用更高级别的协议。
来源: https://docs.oracle.com/javase/8/docs/technotes/guides/net/articles/connection_release.html
答案 1 :(得分:2)
客户端在发送第6个消息时会出现异常
完全没有。如果发送者继续发送,发送者最终会得到一个异常,但由于发送者和接收者都有TCP缓冲,并且因为TCP发送是异步的,所以在下一次写入后肯定不会发生同伴关闭。之后会发生:
send()
。答案 2 :(得分:1)
EJP的答案描述了您当前解决方案无法运作的原因。
至于替代方案,做你想做的事的唯一方法是使协议双向直接。换句话说,您需要为收到的每条消息设置某种服务器确认。 “简单”的方法是让服务器立即确认收到的每条消息。更复杂的协议可以允许某种周期性的ack。即使有了这个解决方案,仍然存在问题。例如,客户端可能不会接收一个ack(如果套接字过早关闭),因此服务器可能会看到两次相同的消息。长话短说,您需要某种可靠的消息传输协议,并且已有大量已在线的文档。
答案 3 :(得分:0)
有趣的问题。也许在每个客户端写入后尝试刷新OutputStream。