多线程套接字通信客户/服务器

Nic*_*ick 7 java concurrency multithreading client-server

我写完了一个工作正常的Client/Server Socket通信程序.现在我正在试图弄清楚如何制作它,以便我可以同时与服务器建立多个客户端连接.我环顾四周,似乎有不止两种不同的方法来做到这一点.所以我来这里是为了向你们寻求帮助/建议.

我的服务器:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我需要让我的构造函数创建线程和/ startServer()或将是我的run方法吗?

bas*_*mes 11

你应该用ExecutorService.您的客户端请求处理将是run()a,Runnable并且在每次接受之后,您可以调用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");
            }
        }        
    }
}
Run Code Online (Sandbox Code Playgroud)


Gra*_*ray 5

如何使我可以同时与服务器建立多个客户端连接

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

clientSocket = serverSocket.accept();
Run Code Online (Sandbox Code Playgroud)

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

public void startServer() throws IOException {
    PrintWriter output = new PrintWriter(clientSocket.getOutputStream(), true);
    ...
Run Code Online (Sandbox Code Playgroud)

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

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

这是一些示例代码:

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
            ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ExecutorService建议对几乎所有Thread此类实现使用线程池。但是,如果您Thread出于某种原因坚持使用 raw ,则可以在您的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();
        }
    }
Run Code Online (Sandbox Code Playgroud)


mpr*_*vat 2

改变这个: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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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