繁体   English   中英

Windows和Linux之间Java套接字的差异 - 如何处理它们?

[英]Differences on Java Sockets between Windows and Linux - How to handle them?

我在理解Java如何处理Windows和Linux上的套接字方面遇到了很多麻烦 - 特别是当其中一方(客户端或服务器)突然关闭连接时。

我编写了以下非常简单的 Server和Client类,以使我的观点变得简单,客观,并且易于理解:

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();
        }
    }
}


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

在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()而第二个抛出on.read()

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

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

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

这导致了一些问题:

1.为什么在Windows x Linux上有这么大的差异? 为什么在Linux上这些异常在Windows上不被抛出?

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

3.有没有办法改变突然关闭的代码并让它在Windows上更像“Linux-like”,而不抛出所有这些异常并在我的in.read()上得到-1?

4.如果没有,建议使用任何外部API?


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

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

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

对于任何答案,您都会提前致以最诚挚的谢意。

你的代码没有任何关闭,所以我假设你实际上意味着一个端点进程被停止,也就是被杀死。

Unix套接字sd是“只是”fd的,当Unix进程结束而没有关闭fd时,包括JVM停止并且你没有调用close(或shutdown-WR)的情况,fd由操作系统关闭,对于TCP套接字执行(至少尝试)正常也称为优雅关闭:FIN / ACK与FIN-WAIT和TIME-WAIT交换。 我知道使Unix套接字在TCP级别(RST)上无风格关闭的唯一方法是在关闭之前将其设置为0(显式或退出)。 中间盒强行破坏与RST的连接也是可能的并且并不罕见; 例如,我看到防火墙在15分钟不活动后让你RST。 我也更少见过伪造FIN的中间盒,或试图做错但做错了。

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

如果它足以处理代码错误(泄漏)和信号而不是JVM错误和失败,那么你可以将Socket子类化,这样如果force(graceful)关闭.finalize并在退出时使用Runtime.addShutdownHook,并使用它。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM