2

我在我的应用程序中使用 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 机器,则会出现此消息丢失问题。在同一台机器上它工作正常。

4

4 回答 4

3

EJP 是正确的,但我认为您的问题是有什么方法可以在不使用更高级别协议的情况下避免数据丢失?在您的情况下,答案是否定的。

为什么 ?要了解为什么我们需要了解异常关闭与优雅套接字关闭行为之间的区别。

为了理解这种区别,我们需要看看 TCP 协议级别发生了什么。将已建立的 TCP 连接想象为实际上是两个独立的、半独立的数据流是有帮助的。如果两个对等点是 A 和 B,那么一个流将数据从 A 传递到 B,另一个流从 B 传递到 A。有序发布分两个阶段进行。第一方(比如 A)决定停止发送数据并向 B 发送 FIN 消息。当 B 方的 TCP 堆栈收到 FIN 时,它知道不再有数据来自 A,并且每当 B 读取所有先前的数据时套接字,进一步读取将返回值 -1 以指示文件结束。这个过程被称为 TCP 半关闭,因为只有一半的连接被关闭。毫不奇怪,另一半的程序完全相同。B 向 A 发送 FIN 消息,

相比之下,异常关闭使用 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

于 2016-03-27T19:57:06.870 回答
2

客户端在发送第 6 条消息时应该得到异常

一点也不。如果发送方继续发送,最终会得到一个异常,但是由于发送方和接收方的 TCP 缓冲,并且由于 TCP 发送是异步的,所以在对等方关闭后的下一次写入时肯定不会发生这种情况。它会在之后发生,之后:

  1. TCP 已决定发送其发送缓冲区。
  2. TCP 检测到传入的 RST 响应。
  3. 应用程序接下来调用send().
于 2013-07-24T00:33:40.430 回答
1

EJP 的回答描述了为什么您当前的解决方案不起作用。

至于替代方案,做你想做的唯一方法是使协议双向。换句话说,您需要为收到的每条消息设置某种服务器确认。执行此操作的“简单”方法是让服务器立即确认收到的每条消息。更复杂的协议可以允许某种周期性的确认。即使采用这种解决方案,仍然存在问题区域。例如,客户端可能不会收到确认(如果套接字过早关闭),因此服务器可能会两次看到相同的消息。长话短说,您需要某种可靠的消息传输协议,并且已经有大量在线文档。

于 2013-07-24T00:56:32.323 回答
0

有趣的问题。也许在每次客户端写入后尝试刷新 OutputStream。

于 2013-07-23T07:16:36.403 回答