在单独的线程中创建ServerSocket?

28

我的应用程序在使用 ServerSocket 时出了问题。

我在应用程序的构造函数中创建了 ServerSocket,并调用了套接字的构造函数来等待客户端连接。

问题是 accept() 方法会阻塞整个应用程序,直到有客户端连接。因此,我想问一下是否有替代方法可以在单独的线程中创建整个 ServerSocket,并在我的主应用程序之外调用它的构造函数和 accept() 方法?

编辑:

感谢Olivier的建议,将 .accept() 方法放入一个可运行对象,并创建一个线程池来处理客户端连接。

现在这是我的代码:

  public void start(){

      final ExecutorService clientProcessingPool = Executors.newFixedThreadPool(10);

      Runnable serverTask = new Runnable() {
          @Override
          public void run() {

              try {
                  serverSocket = new ServerSocket(port);

                  while (true) {
                      Socket clientSocket = serverSocket.accept();
                      objectout = new ObjectOutputStream(clientSocket.getOutputStream());
                      clientProcessingPool.submit(new ClientTask(clientSocket,objectout)); 
                  }
              } catch (IOException e) {
                  System.err.println("Accept failed.");
              }

          }
      };

一切运行良好!谢谢!


尝试将套接字相关的代码移动到单独的线程中了吗?是否遇到了任何特定的问题? - Ankit
看一下 java.nio.channels.AsynchronousServerSocketChannel,看它是否符合您的要求。 - Crollster
或者更好的方法是,看看这个问题的链接:https://dev59.com/fmox5IYBdhLWcg3w1Xs3 - Crollster
我的问题是仅仅将ServerSocket实现为一个线程是不够的,因为.accept方法已经在其构造函数中被调用而不是在其运行方法中被调用。所以一旦我尝试创建线程,它就会立即冻结我的应用程序。 - Chocolate
1个回答

53
通常,我会使用N+1个线程:一个用于ServerSocket,以避免阻塞整个应用程序等待客户端连接; N个线程用于处理客户端请求,其中N是线程池的大小(建议使用线程池而不是为每个客户端创建新线程)。
这里是一个示例(只编写了它,您可能希望进行更好的异常管理等,但这是一个最小的工作示例)。
public class Server {

    public static void main(String[] args) {
        new Server().startServer();
    }

    public void startServer() {
        final ExecutorService clientProcessingPool = Executors.newFixedThreadPool(10);

        Runnable serverTask = new Runnable() {
            @Override
            public void run() {
                try {
                    ServerSocket serverSocket = new ServerSocket(8000);
                    System.out.println("Waiting for clients to connect...");
                    while (true) {
                        Socket clientSocket = serverSocket.accept();
                        clientProcessingPool.submit(new ClientTask(clientSocket));
                    }
                } catch (IOException e) {
                    System.err.println("Unable to process client request");
                    e.printStackTrace();
                }
            }
        };
        Thread serverThread = new Thread(serverTask);
        serverThread.start();

    }

    private class ClientTask implements Runnable {
        private final Socket clientSocket;

        private ClientTask(Socket clientSocket) {
            this.clientSocket = clientSocket;
        }

        @Override
        public void run() {
            System.out.println("Got a client !");

            // Do whatever required to process the client's request

            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

使用线程池处理客户端后,如何处理与客户端之间的数据接收和发送?几周前我看到的一个解决方案是,处理程序也有两个线程,一个用于发送数据,另一个用于接收数据。 - Loki
1
此外,如果您想了解更详细的相关内容,请参阅以下文章:http://tutorials.jenkov.com/java-multithreaded-servers/thread-pooled-server.html。 - Priidu Neemre
这段代码无法防止拒绝服务攻击,攻击者会通过阻塞(或限制)accept 函数来阻止服务器接受新的客户端连接。 - Alexander Farber
既然已经是2017年了,使用Java 8时,你应该使用Lambda而不是匿名函数:Runnable serverTask = () -> { /* 在这里编写代码 */ }` - Christopher Will

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