簡體   English   中英

多線程套接字通信客戶端/服務器

[英]Multithreading Socket communication Client/Server

我完成了一個工作正常的客戶端/服務器套接字通信程序。 現在我想弄清楚如何做到這一點,以便我可以同時擁有到服務器的多個客戶端連接。 我環顧四周,似乎有不止幾種不同的方法可以做到這一點。 所以我來這里是為了向你們尋求幫助/建議。

我的服務器:

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()還是我的 run 方法?

您應該使用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");
            }
        }        
    }
}

如何使我可以同時與服務器建立多個客戶端連接

現在您正在啟動您的服務器並立即等待單個客戶端在構造函數中連接。

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
            ...
        }
    }
}

建議對幾乎所有像這樣的Thread實現使用ExecutorService線程池。 但是,如果您出於某種原因堅持使用原始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();
        }
    }

更改此: 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;

那應該會讓你到達那里。 或者至少非常接近。

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 */
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM