Java套接字:同一机器上同一端口的多个客户端线程?

20

我对Java中的Socket编程还不熟悉,尝试理解以下代码是否有错。我的问题是:

我可以在同一个程序中的每个线程上拥有多个客户端尝试连接到服务器实例,并期望服务器在客户端之间读取和写入数据时进行隔离吗?

public class Client extends Thread
{
    ...
    void run()
    {
        Socket socket = new Socket("localhost", 1234);
        doIO(socket);  
    }
}

public class Server extends Thread
{
    ...
    void run()
    {
        // serverSocket on "localhost", 1234
        Socket clientSock = serverSocket.accept();
        executor.execute(new ClientWorker(clientSock));
    }
}

现在我可以在不同的线程上有多个客户端实例,尝试连接到当前机器的同一端口吗?

例如:

   Server s = new Server("localhost", 1234);
   s.start();
   Client[] c = new Client[10];
   for (int i = 0; i < c.length; ++i)
   {
        c.start();
   }
6个回答

11

是的,但是根据当前编写方式,每个线程执行时只能连接一个客户端。

你可以将服务器的run()放在一个while循环内,以便让多个客户端连接。根据执行器的不同,它们将逐个或并行执行。

   public class Server extends Thread  
   {  
       ...  
       void run()  
       {  
           while(true){
              // serverSocket on "localhost", 1234  
              Socket clientSock = serverSocket.accept();  
              executor.execute(new ClientWorker(clientSock));  
           }
       }  
   } 

4
只要您只有一个对象尝试绑定端口进行监听,那么多个客户端连接就不会有问题。

谢谢大家,我之前认为端口是一个单一的物理实体(比如一根电线),因为它只有一个号码。所以我的想法是它只能被一个客户端套接字使用,否则多个客户端套接字可能会同时写入同一根电线。但从你们的回答中,我认为端口本身由多个资源组成(比如内存块),但套接字将绑定到其中一个资源块,可能由某个绑定键索引。 - espcorrupt
5
端口只是一个数字,它没有对应任何物理设备。连接由元组{协议、源地址、源端口、目标地址、目标端口}定义。客户端的操作系统将确保为每个出站连接使用不同的出站端口号码。因此,即使它们都来自同一客户端源主机,多个入站连接到相同的目标主机/端口也没有问题。 - user207421

1
在本例中,你的 Server 一次只接受并处理一个客户端连接。你可以有尽可能多的 Client 尝试连接,但每次只处理一个。
由于你没有提供实现,无法确定执行器逻辑是否为多线程。如果执行器委托给线程池或类似的东西,你需要确保你的 ClientWorker 是线程安全的,因为会有多个实例同时执行。
当然,我假设你的 Client 也是线程安全的,因为你的问题只涉及 Server

0

你可以尝试类似这样的东西

public class MultiThreadServer extends Application {
  // Text area for displaying contents
  private TextArea ta = new TextArea();

  // Number a client
  private int clientNo = 0;

  @Override // Override the start method in the Application class
  public void start(Stage primaryStage) {
    // Create a scene and place it in the stage
    Scene scene = new Scene(new ScrollPane(ta), 450, 200);
    primaryStage.setTitle("MultiThreadServer"); // Set the stage title
    primaryStage.setScene(scene); // Place the scene in the stage
    primaryStage.show(); // Display the stage

    new Thread( () -> {
      try {
        // Create a server socket
        ServerSocket serverSocket = new ServerSocket(8000);
        ta.appendText("MultiThreadServer started at " 
          + new Date() + '\n');

        while (true) {
          // Listen for a new connection request
          Socket socket = serverSocket.accept();

          // Increment clientNo
          clientNo++;

          Platform.runLater( () -> {
            // Display the client number
            ta.appendText("Starting thread for client " + clientNo +
              " at " + new Date() + '\n');

            // Find the client's host name, and IP address
            InetAddress inetAddress = socket.getInetAddress();
            ta.appendText("Client " + clientNo + "'s host name is "
              + inetAddress.getHostName() + "\n");
            ta.appendText("Client " + clientNo + "'s IP Address is "
              + inetAddress.getHostAddress() + "\n");
          });

          // Create and start a new thread for the connection
          new Thread(new HandleAClient(socket)).start();
        }
      }
      catch(IOException ex) {
        System.err.println(ex);
      }
    }).start();
  }

  // Define the thread class for handling new connection
  class HandleAClient implements Runnable {
    private Socket socket; // A connected socket

    /** Construct a thread */
    public HandleAClient(Socket socket) {
      this.socket = socket;
    }

    /** Run a thread */
    public void run() {
      try {
        // Create data input and output streams
        DataInputStream inputFromClient = new DataInputStream(
          socket.getInputStream());
        DataOutputStream outputToClient = new DataOutputStream(
          socket.getOutputStream());

        // Continuously serve the client
        while (true) {
          // Receive radius from the client
          double radius = inputFromClient.readDouble();

          // Compute area
          double area = radius * radius * Math.PI;

          // Send area back to the client
          outputToClient.writeDouble(area);

          Platform.runLater(() -> {
            ta.appendText("radius received from client: " +
              radius + '\n');
            ta.appendText("Area found: " + area + '\n');
          });
        }
      }
      catch(IOException e) {
        ex.printStackTrace();
      }
    }
  }

  /**
   * The main method is only needed for the IDE with limited
   * JavaFX support. Not needed for running from the command line.
   */
  public static void main(String[] args) {
    launch(args);
  }
}

0

是的,你的客户端是本地的还是远程的并不重要。在你的例子中,重要的是ClientWorker是线程安全的,因为你的服务器将拥有该类的多个实例(每个客户端连接一个实例)。


0

好的,让我们开始:

您可以使用一个服务器套接字接受更多客户端,因为您只在run方法中接受一个客户端。您只需要再次调用accept()

然后,在您的for循环中:首先,您每次都必须创建一个新的Client对象。然后,您可以调用c[i].start();而不是c.start()

现在我可以在不同线程上拥有多个客户端实例,尝试连接到当前机器的同一端口吗?

是的,您可以。只需创建新线程并运行它们即可。这应该完美地工作。

期望服务器在客户端之间具有隔离读写数据

您可以使用基本IO技术的经验,就像使用文件IO一样:

OutputStream os = socket.getOutputStream();
PrintStream pw = new PrintStream(os, true); // Or PrintWriter, I don't know what the best one is.
pw.println("Hello, other side of the connection!");

而且为了读取数据,使用 BufferedReader。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接