【问题标题】:Multithreading Socket communication Client/Server多线程套接字通信客户端/服务器
【发布时间】:2012-09-17 07:07:15
【问题描述】:

我完成了一个运行良好的客户端/服务器套接字通信程序。现在我正试图弄清楚如何做到这一点,以便我可以一次有多个客户端连接到服务器。我环顾四周,似乎有不止几种不同的方法可以做到这一点。所以我来这里向你们寻求帮助/建议。

我的服务器:

public class Server {
    private ServerSocket serverSocket = null;
    private Socket clientSocket = null;

    public Server() {
        try {
            serverSocket = new ServerSocket(7003);
        } catch (IOException e) {
            System.err.println("Could not listen on port: 7003");
            System.exit(1);
        }

        try {
            clientSocket = serverSocket.accept();
        } catch (IOException e) {
            System.err.println("Accept failed");
            System.exit(1);
        }
    }

    public void startServer() throws IOException {
        PrintWriter output = new PrintWriter(clientSocket.getOutputStream(), true);
        BufferedReader input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

        String inputLine, outputLine;

        outputLine = "Connected to Server";
        output.println(outputLine);

        while ((inputLine = input.readLine()) != null) {
            // This just determines users input and server ruturns output based on that

            outputLine = this.getServerOutput(inputLine);
            output.println(outputLine);

            if (outputLine.equals("Bye"))
                break;
        }

        output.close();
        input.close();
        clientSocket.close();
        serverSocket.close();
    }
}

我需要让我的构造函数创建线程和startServer() 还是我的运行方法?

【问题讨论】:

    标签: java multithreading concurrency client-server


    【解决方案1】:

    您应该使用ExecutorService。您的客户端请求处理将是Runnablerun(),并且在每次接受后,您可以调用ExecutorService.submit(runnableTask) 以异步服务客户端。

    使用 ExecutorService 的示例。

    public class MyServer {
    
        private static MyServer server; 
        private ServerSocket serverSocket;
    
        /**
         * This executor service has 10 threads. 
         * So it means your server can process max 10 concurrent requests.
         */
        private ExecutorService executorService = Executors.newFixedThreadPool(10);        
    
        public static void main(String[] args) throws IOException {
            server = new MyServer();
            server.runServer();
        }
    
        private void runServer() {        
            int serverPort = 8085;
            try {
                System.out.println("Starting Server");
                serverSocket = new ServerSocket(serverPort); 
    
                while(true) {
                    System.out.println("Waiting for request");
                    try {
                        Socket s = serverSocket.accept();
                        System.out.println("Processing request");
                        executorService.submit(new ServiceRequest(s));
                    } catch(IOException ioe) {
                        System.out.println("Error accepting connection");
                        ioe.printStackTrace();
                    }
                }
            }catch(IOException e) {
                System.out.println("Error starting Server on "+serverPort);
                e.printStackTrace();
            }
        }
    
        //Call the method when you want to stop your server
        private void stopServer() {
            //Stop the executor service.
            executorService.shutdownNow();
            try {
                //Stop accepting requests.
                serverSocket.close();
            } catch (IOException e) {
                System.out.println("Error in server shutdown");
                e.printStackTrace();
            }
            System.exit(0);
        }
    
        class ServiceRequest implements Runnable {
    
            private Socket socket;
    
            public ServiceRequest(Socket connection) {
                this.socket = connection;
            }
    
            public void run() {
    
                //Do your logic here. You have the `socket` available to read/write data.
    
                //Make sure to close
                try {
                    socket.close();
                }catch(IOException ioe) {
                    System.out.println("Error closing client connection");
                }
            }        
        }
    }
    

    【讨论】:

    • 几个 cmets:你的 shutdownNow() 不会杀死服务器,因为 accept() 忽略了中断。关闭服务器套接字是正确的方法。 startServer() 应该是 runServer(),因为它永远不会返回。具有启动和停止方法具有误导性。如果从未使用过,为什么还要提交Callablesocket.close() 应该在 try / finally 块内。服务器接受循环也是如此。
    • 嘿 basiljames 我不知道您是否看到了我在下面的最后一篇文章,但是按照您发布的示例,我没有遇到什么问题。由于我以前从未使用过 ExecutorService,我将如何将其实现到我的程序中。
    • @Nick 抱歉没有回复。我不得不离开家。看来格雷已经消除了你的疑虑。让我知道是否需要任何澄清。
    • 我尝试在服务器中使用它,但输出流不起作用。 serverSocket = new ServerSocket(serverPort); PrintWriter output = new PrintWriter(socket.getOutputStream(), true); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); while(input.readLine() != null) { output.println(pro.processInput(input.readLine())); }
    【解决方案2】:

    如何使我可以一次有多个客户端连接到服务器

    现在您正在启动服务器并立即在构造函数中等待单个客户端连接。

    clientSocket = serverSocket.accept();
    

    然后您在 startServer() 方法中处理该单个套接字连接。这意味着不会处理其他客户端。

    public void startServer() throws IOException {
        PrintWriter output = new PrintWriter(clientSocket.getOutputStream(), true);
        ...
    

    通常使用这样的服务器模式,您会执行以下操作:

    1. 在构造函数中设置您的服务器套接字。
    2. 创建一个acceptClients() 方法,该方法将循环等待客户端被接受。这可能会派生一个线程以在后台自己的线程中接受客户端。
    3. 对于每个客户端,或者派生一个线程来处理连接,将线程传递给客户端套接字。正如@basiljames 所示,最好使用ExecutorService 为您管理线程。

    这里有一些示例代码:

    public class Server {
        private ServerSocket serverSocket = null;
    
        public Server(int portNumber) throws IOException {
            serverSocket = new ServerSocket(portNumber);
        }
    
        // this could be run in a thread in the background
        public void acceptClients() throws IOException {
            // create an open ended thread-pool
            ExecutorService threadPool = Executors.newCachedThreadPool();
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    // wait for a client to connect
                    Socket clientSocket = serverSocket.accept();
                    // create a new client handler object for that socket,
                    // and fork it in a background thread
                    threadPool.submit(new ClientHandler(clientSocket));
                }
            } finally {
                // we _have_ to shutdown the thread-pool when we are done
                threadPool.shutdown();
            }
        }
    
        // if server is running in background, you stop it by killing the socket
        public void stop() throws IOException {
            serverSocket.close();
        }
    
        // this class handles each client connection
        private static class ClientHandler implements Runnable {
            private final Socket clientSocket;
            public ClientHandler(Socket clientSocket) {
                this.clientSocket = clientSocket;
            }
            public void run() {
                // use the client socket to handle the client connection
                ...
            }
        }
    }
    

    推荐使用ExecutorService 线程池用于几乎所有类似这样的Thread 实现。但是,如果由于某种原因您坚持使用原始 Thread,则可以在您的 acceptClients() 方法中执行以下操作:

        public void acceptClients() throws IOException {
            while (!Thread.currentThread().isInterrupted()) {
                // wait for a client to connect
                Socket clientSocket = serverSocket.accept();
                // fork a background client thread
                new Thread(new ClientHandler(clientSocket)).start();
            }
        }
    

    【讨论】:

    • 我将如何分叉线程来处理每个客户端连接?我是否只是使用 new Thread(new Runnable()) 启动新线程并将客户端套接字作为参数传递?我认为这是我需要采取的方法,因为我不太了解 ExecutorService。
    • 花时间了解ExecutorService@Nick。推荐作为大多数new Thread() 场景的替代品。也就是说,我已经在我的答案中添加了如何使用原始线程来做到这一点。
    • 我肯定会在 ExecutorService 上做一些阅读,但现在我被要求处理线程。我还可以指定要创建多少个线程,因为这将全部从单个客户端程序运行?非常感谢您的帮助,我是 Threading 新手并试图理解它。
    • 这将为每个客户端创建 1 个线程。如果您想控制有多少客户端可以同时连接到您的服务器,那么这要困难得多。使用Executors.newFixedThreadPool(numThreads) 定义线程池将限制线程数,但不会限制连接数。
    • 基本上我要求服务器执行一项低负载操作和一项高负载操作。低要求它返回当前日期和时间,高负载是诸如空闲内存之类的东西,并需要时间测量执行每一个所需的时间。我想要做的是创建客户端线程,以便每个线程/客户端对这两个进行自己的测量。
    【解决方案3】:

    改变这个:public void startServer() throws IOException 对此:public void startServer(Socket clientSocket) throws IOException

    那么你需要做的就是:

    public Server()
    {
        try
        {
            serverSocket = new ServerSocket(7003);
        }
        catch (IOException e)
        {
            System.err.println("Could not listen on port: 7003");
            System.exit(1);
        }
    
        try
        {
            while(true) {
                final Socket socket = serverSocket.accept();
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            startServer(socket);
                        } catch(IOException e) {e.printStackTrace();}
                    }
                }).start();
            }
        }
        catch(IOException e)
        {
            System.err.println("Accept failed");
            System.exit(1);
        }
    }
    

    最后,您可以删除private Socket clientSocket = null;

    这应该可以让你到达那里。或者至少非常接近。

    【讨论】:

    • 这行得通,但是有没有办法让客户端可以指定他们想要运行的线程数?例如,现在我只运行了多个客户端程序,但我想让它只运行一个客户端程序,但可以创建多个执行相同功能的线程。
    • 是的,这就是线程池管理。看看@basiljames 的回复。基本上你创建一个池:ExecutorService pool = Executors.newFixedThreadPool(5); 然后你向它提交工作而不是创建一个新线程。
    • 我在遵循他发布的代码时遇到了问题,你有一个我能理解的代码。你有什么办法可以告诉我这将如何转化为我所拥有的?再次感谢!
    【解决方案4】:
    private static final int SERVER_PORT = 35706;
    private ServerSocket serverSocket;
    private final ArrayList<ClientThread> activeClients = new ArrayList<>();
    
    public void startServer() {
    
        try {
            serverSocket = new ServerSocket(SERVER_PORT);
            
            final ExecutorService clientPool = Executors.newCachedThreadPool();
    
            while (!serverSocket.isClosed()) {
    
                try {
                    Future<Socket> future = clientPool.submit(() -> {
                           Socket socket = serverSocket.accept();
                           ClientThread clientThread= new ClientThread(socket);
                           return (socket);
                    });
    
                    activeClients.add(future.get());
                } catch (IOException e) {
                    clientPool.shutdownNow();
                    System.out.println(e.getMessage());
                } catch (InterruptedException | ExecutionException e) {
                    System.out.println(e.getMessage());
                }
            }
    
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
    
    
    
    public void stopServer() {  
    
       try {
            serverSocket.close();
            activeClients.forEach(socket -> {
                try {
                    socket.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            });
                
       } catch (IOException ex) {
            System.out.println(e.getMessage());
       }
    
    }
    
    
    
    private static class ClientThread implements Runnable{
        private final Socket socket;
    
        public ClientThread(Socket socket) throws IOException {
           this.socket = socket;
        }
            
        @Override
        public void run() {
            /* Your implementation */
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-30
      • 2012-11-02
      • 1970-01-01
      • 2017-08-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多