Java 套接字无法使用子网 IP 地址访问服务器

Java Sockets Can't Access Server using Subnet IP Address

使用 JDK 10,我正在尝试编写一个客户端/服务器程序,它将 运行 在使用 TCP/IP 套接字的多台计算机上分开。

所有计算机都应位于同一本地子网 192.168 中。1.x(其中 x 可以在 1 到 254 之间变化)。

各个服务器从客户端程序接收字符串并打印出来。

ServerThread.java:

import java.io.IOException;
import java.net.ServerSocket;
import java.util.concurrent.Executors;

public class ServerThread implements Runnable {

    private Socket socket;

    public ServerThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        System.out.println("Connected" + socket);
        try {
            var in = new Scanner(socket.getInputStream());
            var out = new PrintWriter(socket.getOutputStream(), true);
            while (in.hasNextLine()) {
                out.println(in.nextLine());
            }
        }
        catch (Exception e) {
            System.out.println("Error:" + socket);
        }
        finally {
            try {
                socket.close();
            }
            catch (IOException e) {
            }
            System.out.println("Closed: " + socket);
        }
    }

    public static void main(String[] args) throws IOException {
        try (var listener = new ServerSocket(6500)) {
            System.out.println("Server has started...");
            var pool = Executors.newFixedThreadPool(20);
            while (true) {
                pool.execute(new ServerThread(listener.accept()));
            }
        }
    }

}

最初,它将使用 "localhost" 作为主机名:

public class Client {

    public static void connect(String hostName, String portNumber) throws Exception {
        int port = Integer.parseInt(portNumber);
        try (var socket = new Socket(hostName, port)) {
            System.out.println("Enter lines of text then Ctrl+D or Ctrl+C to quit");
            var scanner = new Scanner(System.in);
            var in = new Scanner(socket.getInputStream());
            var out = new PrintWriter(socket.getOutputStream(), true);
            while (scanner.hasNextLine()) {
                out.println(scanner.nextLine());
                System.out.println(in.nextLine());
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Client.connect("localhost", "6500");
    }
}

现在使用 new Socket(InetAddress.getByName(ipAddress), port) 时,我得到 java.net.ConnectException: Connection refused 异常:

import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

public class Client {

    public static void connect(String iPAddress, String portNumber) throws Exception {
        int port = Integer.parseInt(portNumber);

        try (var socket = new Socket(InetAddress.getByName(iPAddress), port)) {
            System.out.println("Enter lines of text then Ctrl+D or Ctrl+C to quit");
            var scanner = new Scanner(System.in);
            var in = new Scanner(socket.getInputStream());
            var out = new PrintWriter(socket.getOutputStream(), true);
            while (scanner.hasNextLine()) {
                out.println(scanner.nextLine());
                System.out.println(in.nextLine());
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Client.connect("192.168.1.1", "6500");
    }
}

Exception in thread "main" java.net.ConnectException: Connection refused (Connection refused)
    at java.base/java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.base/java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:400)
    at java.base/java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:243)
    at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:225)
    at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:402)
    at java.base/java.net.Socket.connect(Socket.java:591)
    at java.base/java.net.Socket.connect(Socket.java:540)
    at java.base/java.net.Socket.<init>(Socket.java:436)
    at java.base/java.net.Socket.<init>(Socket.java:246)
    at com.sample.Client.connect(Client.java:13)
    at com.sample.Client.main(Client.java:26)

然而,当我尝试使用 "192.168.1.2" 时,没有任何反应(它甚至没有打印出来:Enter lines of text then Ctrl+D or Ctrl+C to quit

最终,它通过抛出此异常而超时:

Exception in thread "main" java.net.ConnectException: Operation timed out (Connection timed out)
    at java.base/java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.base/java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:400)
    at java.base/java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:243)
    at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:225)
    at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:402)
    at java.base/java.net.Socket.connect(Socket.java:591)
    at java.base/java.net.Socket.connect(Socket.java:540)
    at java.base/java.net.Socket.<init>(Socket.java:436)
    at java.base/java.net.Socket.<init>(Socket.java:246)
    at com.sample.Client.connect(Client.java:13)
    at com.sample.Client.main(Client.java:26)


问题:

  1. 为什么抛出这个异常:java.net.ConnectException: Connection refused 当使用 192.168.1.1java.net.ConnectException: Operation timed out (Connection timed out) 当主机(最后一位)使用除 1 以外的任何数字时,例如192.168.1.2 ?

  2. 是否有其他一些 Java10API(例如反应流或 NIO 通道)比服务器只使用线程更好?

错误是假设 Socket 构造函数同时使用 IP 和主机名,实际上:

new Socket("192.168.1.2", 6500)

不起作用,因为它需要主机名。来自 this 文档:

host - the host name, or null for the loopback address.
. . .
public Socket(String host,int port) throws UnknownHostException, IOException

你要用到的是:

new Socket(InetAddress.getByName("192.168.1.2"), 6500)

我认为您还应该知道一些系统工具可以在黑盒级别调试您的应用程序,以便找出可能的问题:

我是 Linux 人,所以只知道 Linux 实用程序:

"sudo netstat -tpln" 查看您的服务器应用程序是否正在侦听所需的端口,最重要的是它应该侦听 IP 0.0.0.0 而不仅仅是 127.0.0.1 以便网络计算机能够能够连接到它。

"sudo iptables -vnL" 以查明防火墙是否未阻止外部发起的与您的服务器应用程序的连接。如果是这种情况,那么您的服务器应用程序所在的同一系统上的客户端应用程序将能够进行通信,但同一网络上其他系统上的同一客户端应用程序将无法通信。

根据我的经验,这些调试有助于在 90% 以上的时间内解决此类问题,而且这通常不是编程问题。希望这些信息对您有所帮助。

明白了 - 我的路由器/防火墙将我网络内的不同计算机分配给不同的基于子网的 IP 地址,但不一定会将第二台计算机(随后)分配给:

191.168.1.2

运行 ifconfig 并发现了特定的基于子网的 IP 地址 192.168.1.x 出于安全原因省略了 "x" 的实际值。