【问题标题】:Differences on Java Sockets between Windows and Linux - How to handle them?Windows 和 Linux 之间 Java 套接字的差异 - 如何处理它们?
【发布时间】:2014-04-08 08:40:06
【问题描述】:

我很难理解 Java 如何在 Windows 和 Linux 上处理套接字的差异 - 特别是当一方(客户端或服务器)突然关闭连接时。

我编写了以下非常简单的服务器和客户端类,以使我的观点尽可能简单、客观和易于理解:

SimpleClient.java:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

import java.net.Socket;

public class SimpleClient {

    public static void main(String args[]) {
        try {
            Socket client_socket = new Socket("127.0.0.1", 9009);

            // Used to read from a terminal input:
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

            // Used for client/server communication:
            BufferedReader in = new BufferedReader(new InputStreamReader(client_socket.getInputStream()));
            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(client_socket.getOutputStream()));

            while(true) {
                System.out.print("Command: ");
                String msg = br.readLine();

                // Send:
                out.write(msg);
                out.newLine();
                out.flush();

                // Receive:
                int ifirst_char;
                char first_char;

                if((ifirst_char = in.read()) == -1) {  // Server Closed
                    System.out.println("Server was closed on the other side.");

                    break;
                }

                first_char = (char) ifirst_char;

                msg = String.valueOf(first_char);

                msg += in.readLine();

                // Shows the message received from the server on the screen:
                System.out.println(msg);
            }
        }
        catch(Exception e) {
            e.printStackTrace();
        }

    }
}


SimpleServer.java:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

import java.net.ServerSocket;
import java.net.Socket;

public class SimpleServer {

    public static void main(String args[]) {
        try {
            ServerSocket server_socket = new ServerSocket(9009);

            Socket client_socket = server_socket.accept();

            while(true) {
                BufferedReader in = new BufferedReader(new InputStreamReader(client_socket.getInputStream()));
                BufferedWriter out = new BufferedWriter(new OutputStreamWriter(client_socket.getOutputStream()));

                // Receive:
                int ifirst_char;
                char first_char;

                if((ifirst_char = in.read()) == -1) {  // Client Closed
                    System.out.println("Client was closed on the other side.");

                    break;
                }

                first_char = (char) ifirst_char;

                String msg = msg = String.valueOf(first_char);

                msg += in.readLine();

                msg = "Server Received: " + msg;

                // Send:
                out.write(msg);
                out.newLine();
                out.flush();
            }
        }
        catch(Exception e) {
            e.printStackTrace();
        }
    }
}


当然,我可以实现一个代码来正确关闭客户端或服务器,但正如我所说,目标是模拟任一端的突然关闭,其中无法发送或接收“断开代码”。这就是我创建这 2 个非常简单的类的原因。

在 Linux 上,它运行得很好:

$ java SimpleClient 
Command: echo
Server Received: echo
Command: test
Server Received: test
Command: (server now was closed on the other side)
Server was closed on the other side.
$


但是在 Windows 上:

C:\simplesocket>java SimpleClient
Command: echo
Server Received: echo
Command: test
Server Received: test
Command: (server now was closed on the other side)
java.net.SocketException: Connection reset by peer: socket write error
        at java.net.SocketOutputStream.socketWrite0(Native Method)
        at java.net.SocketOutputStream.socketWrite(Unknown Source)
        at java.net.SocketOutputStream.write(Unknown Source)
        at sun.nio.cs.StreamEncoder.writeBytes(Unknown Source)
        at sun.nio.cs.StreamEncoder.implFlushBuffer(Unknown Source)
        at sun.nio.cs.StreamEncoder.implFlush(Unknown Source)
        at sun.nio.cs.StreamEncoder.flush(Unknown Source)
        at java.io.OutputStreamWriter.flush(Unknown Source)
        at java.io.BufferedWriter.flush(Unknown Source)
        at SimpleClient.main(SimpleClient.java:32)


假设我尝试通过修改 SimpleClient.java 上的以下行来忽略此异常:

// Send:
try {
    out.write(msg);
    out.newLine();
    out.flush();
    }
catch(Exception e) {}


抛出另一个异常:

C:\simplesocket>java SimpleClient
Command: echo
Server Received: echo
Command: test
Server Received: test
Command: (server now was closed on the other side)
java.net.SocketException: Connection reset
        at java.net.SocketInputStream.read(Unknown Source)
        at java.net.SocketInputStream.read(Unknown Source)
        at sun.nio.cs.StreamDecoder.readBytes(Unknown Source)
        at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
        at sun.nio.cs.StreamDecoder.read(Unknown Source)
        at java.io.InputStreamReader.read(Unknown Source)
        at java.io.BufferedReader.fill(Unknown Source)
        at java.io.BufferedReader.read(Unknown Source)
        at SimpleClient.main(SimpleClient.java:42)


我不知道代码上的相应行是否会是这些异常中指出的行,但第一行在 out.flush() 上抛出,第二行在 in 上抛出.read().

所以基本上,正如您在 Linux 上看到的那样,即使在突然关闭服务器之后:

1. 发送数据时不抛出异常。
2.更重要的是,当我尝试接收它时,第一个字符是“-1”并且接收正确。

在 Windows 上,它在发送时抛出异常,更重要的是在接收时抛出异常 - 当调用 read() 方法时 - 我无法获得“流结束”(-1) 代码。

这就引出了一些问题:

1. 为什么在 Windows x Linux 上会有如此大的差异?为什么在 Linux 上不会抛出这些异常,而在 Windows 上却会抛出这些异常?

2. 具有跨平台特性的 Java 不应该尽量减少在运行时的差异吗?两个系统? (顺便说一句,我在两者上都使用了 JDK 7)

3. 有没有办法更改代码以实现突然关闭并使其更“类似于 Linux” " 在 Windows 上,不抛出所有这些异常并在我的 in.read() 上获得 -1?

4. 如果没有,是否推荐任何外部 API?


我试图在网上搜索几个小时关于这个特定主题但没有成功。

我也尝试过很多解决方案,比如在client_socket上调用isConnected()isBound()isClosed()等方法客户端没有成功。他们总是说有一个活动的连接并且没有问题,即使在关闭服务器之后也是如此。

希望有人会花时间回答至少一个这些问题。

提前向您表示最诚挚的感谢。

【问题讨论】:

  • isXXX() 方法告诉你的是套接字的状态,而不是连接的状态。 Linux 套接字和 Windows 套接字之间的主要区别在于缓冲区的大小,仅此一项就足以解释您在此处看到的所有内容。这不是 Java 试图解决的问题。

标签: java sockets


【解决方案1】:

您的代码不会任何关闭,所以我假设您实际上是指一个端点进程已停止,也就是被杀死。

Unix socket sd 是“just” fd,当 Unix 进程结束时没有关闭 fd,包括 JVM 停止的情况 并且您没有调用close(或shutdown-WR),fd被操作系统关闭,对于TCP套接字来说(至少尝试) 正常的又名优雅关闭:FIN/ACK 与 FIN-WAITs 和 TIME-WAIT 交换。我知道制作 Unix 套接字的唯一方法 在 TCP 级别 (RST) 进行无优雅关闭是在关闭之前将 linger 设置为 0(显式地或通过退出)。 中间盒也有可能强行断开您与 RST 的连接,而且这种情况并不少见;例如我见过防火墙 在 15 分钟不活动后回复您。我也很少看到伪造 FIN 的中间盒,或者试图但做错的中间盒。

Windows 套接字 (WinSock) 是与文件不同的 API。如果一个 Windows 进程没有调用 closesocket 就结束了(类似于 但与关闭分开)或至少关闭-WR,Winsock 执行 RST。要在 Windows 上优雅地关闭(FIN),你(通过 JVM) 必须调用其中之一。 JVM 大概可以跟踪 java.net.Sockets(但在 JNI 中没有)并在 JVM 退出时为您执行此操作,但是 它没有;您可以请求增强。如果您使用 TaskMgr 或类似工具从外部杀死它,即使这样也可能不起作用,并且 如果遇到 JVM 错误,可能无法正常工作:JVM 尝试捕获错误并提供 minidump,这将是一个尝试的地方 清理套接字,但如果有 JVM 错误,它可能会再次失败——而 IME 大多数 JVM 错误是由于 JVM 错误造成的。

如果它足以处理代码错误(泄漏)和信号,但不能处理 JVM 错误和故障,你可以只子类化 Socket 这样如果强制(优雅)关闭 .finalize 并使用 Runtime.addShutdownHook 退出,并使用它来代替。

在 Unix 或 Windows 套接字中,接收到的 FIN 被视为文件结尾,就像任何其他文件(例如磁盘文件)一样。 收到的 RST 作为错误 [WSA]ECONNRESET 返回给 JVM,这会引发异常。隐藏这个不好 差异,因为对于您的应用程序以外的应用程序,它可能很重要 - 足以使某些协议必须更改 防止 fake-FIN 成为安全漏洞,尤其是 SSLv2 和 HTTP/0.9。

如果您还考虑对等系统失败(不仅仅是JVM)或网络的某些部分失败的情况, 或者您的网络接口出现故障,您可以获得的异常会更加多样化。恕我直言,不要试图处理这些, 只需报告您看到的内容,然后让系统管理员和网络管理员对其进行整理。我见过程序员遇到异常 X 的情况 由于实验室条件下的问题 P 并为此编码,但在现实世界中,异常 X 发生的情况非常不同 原因和“有用的”处理实际上使解决问题变得更加困难。

旁白:服务器应该在不在 while(true)do-a-line 循环内之前创建 BufferedReader; 如果您曾经获得/想要一个一次发送多行的客户端,则显示的代码将丢失数据。 如果 first_char==-1 else 转换为字符串,则不需要该头发;只需使用 in.readLine,它会完全返回 null 与初始 in.read 返回 -1 的情况相同,对于 (TCP) Socket 来说,是在接收到 FIN 时。 相反,应该检查来自 System.in 的客户端 readLine;如果有人输入 ^Z 或 ^D 或任何你会得到 NPE 的东西。

【讨论】:

  • 非常感谢您的回答并花时间解释两个操作系统在 TCP 级别上发生的一切!我肯定会将它链接到可能有相同问题的其他人以供进一步参考。我没有意识到 Windows 和 Linux 在套接字上的差异达到了 TCP,并且在非实验室环境中也有可能在 Linux 上出现类似的错误 (RST)。更好的方法是捕获异常,向用户显示更“友好”的异常消息并将其记录在某处。最好的问候。
猜你喜欢
  • 2010-10-29
  • 1970-01-01
  • 1970-01-01
  • 2014-12-23
  • 2012-09-01
  • 2011-09-29
  • 1970-01-01
  • 1970-01-01
  • 2021-08-16
相关资源
最近更新 更多