杰瑞科技汇

Java TCP服务器如何实现?

Of course! Here is a comprehensive guide to creating a TCP server in Java, starting with a simple, single-threaded example and progressing to a more robust, multi-threaded version.

Java TCP服务器如何实现?-图1
(图片来源网络,侵删)

Core Concepts

Before diving into the code, let's understand the key components involved in a TCP server in Java:

  1. ServerSocket: This is the entry point for your server. Its primary job is to listen for incoming connection requests from clients on a specific port. It doesn't handle the communication itself; it just accepts connections.
  2. Socket: When a client connects, the ServerSocket returns a Socket object for that specific connection. This Socket represents the communication channel between your server and that one client. You use it to get input and output streams.
  3. InputStream & OutputStream: Once you have a Socket, you get its input stream to read data from the client and its output stream to send data back to the client.
  4. BufferedReader & PrintWriter: These are wrappers around the basic streams that make reading and writing text data much easier and more efficient. BufferedReader reads text line by line, and PrintWriter writes text with convenient methods like println().

Simple Single-Threaded TCP Server

This is the most basic example. It can only handle one client at a time. After a client connects, the server waits for that client to send a message and then closes the connection.

How it works:

  1. Create a ServerSocket on a specific port (e.g., 6789).
  2. Call serverSocket.accept(). This method blocks (waits) until a client connects.
  3. Once a client connects, accept() returns a Socket object for that client.
  4. Get the InputStream and OutputStream from the Socket.
  5. Read data from the client, process it, and send a response.
  6. Close the client's Socket and the server's ServerSocket.

SimpleTcpServer.java

Java TCP服务器如何实现?-图2
(图片来源网络,侵删)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleTcpServer {
    public static void main(String[] args) {
        int port = 6789;
        // The try-with-resources statement ensures that the server socket is closed automatically.
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Server is listening on port " + port);
            // The accept() method blocks until a client connects.
            Socket clientSocket = serverSocket.accept();
            System.out.println("Client connected: " + clientSocket.getInetAddress().getHostAddress());
            // Set up streams for communication with the client
            // BufferedReader for reading text from the client
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            // PrintWriter for sending text to the client
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); // 'true' for auto-flush
            // Read a line of text from the client
            String inputLine = in.readLine();
            System.out.println("Received from client: " + inputLine);
            // Send a response back to the client
            out.println("Hello from server! I received your message: " + inputLine);
            // Close the client socket
            clientSocket.close();
            System.out.println("Client disconnected. Server shutting down.");
        } catch (IOException e) {
            System.out.println("Server exception: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

How to Test It:

  1. Save the code as SimpleTcpServer.java.
  2. Compile it: javac SimpleTcpServer.java
  3. Run it: java SimpleTcpServer
  4. You'll see: Server is listening on port 6789
  5. Open a new terminal and use a command-line tool like netcat (or telnet) to connect:
    nc localhost 6789
  6. Type a message and press Enter. For example, type Hello Server.
  7. You will see the response from your server in the netcat terminal: Hello from server! I received your message: Hello Server
  8. The server terminal will show the received message and then shut down.

Multi-Threaded TCP Server (More Robust)

A single-threaded server is impractical for real-world applications because it can only handle one client at a time. If a client is slow or disconnected, no other clients can connect. The solution is to handle each client connection in a separate thread.

How it works:

  1. The main thread's only job is to start the ServerSocket and run in a loop, continuously calling accept() to wait for new clients.
  2. Every time a new client connects, the accept() method returns a Socket.
  3. We immediately create a new ClientHandler thread, passing the new Socket to it.
  4. The main thread then goes back to accept() to wait for the next client, while the new thread handles the communication with the client it was assigned.
  5. This allows the server to handle many clients concurrently.

MultiThreadedTcpServer.java

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class MultiThreadedTcpServer {
    public static void main(String[] args) {
        int port = 6789;
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Server is listening on port " + port);
            while (true) { // Keep the server running indefinitely
                Socket clientSocket = serverSocket.accept();
                System.out.println("New client connected: " + clientSocket.getInetAddress().getHostAddress());
                // Create a new thread for the client
                ClientHandler clientHandler = new ClientHandler(clientSocket);
                new Thread(clientHandler).start();
            }
        } catch (IOException e) {
            System.out.println("Server exception: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

ClientHandler.java This class implements the Runnable interface, meaning it contains the code that will be executed in a new thread for each client.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class ClientHandler implements Runnable {
    private final Socket clientSocket;
    public ClientHandler(Socket socket) {
        this.clientSocket = socket;
    }
    @Override
    public void run() {
        System.out.println("Handler started for client: " + clientSocket.getInetAddress().getHostAddress());
        try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
             PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
            String inputLine;
            // Read lines from the client until "exit" is sent or the connection is closed
            while ((inputLine = in.readLine()) != null) {
                System.out.println("Received from " + clientSocket.getInetAddress().getHostAddress() + ": " + inputLine);
                if ("exit".equalsIgnoreCase(inputLine)) {
                    System.out.println("Client " + clientSocket.getInetAddress().getHostAddress() + " requested to close.");
                    break;
                }
                // Echo the message back to the client
                out.println("Server echo: " + inputLine);
            }
        } catch (IOException e) {
            System.out.println("Error handling client " + clientSocket.getInetAddress().getHostAddress() + ": " + e.getMessage());
        } finally {
            try {
                // Ensure the client socket is closed when the handler is done
                if (clientSocket != null && !clientSocket.isClosed()) {
                    clientSocket.close();
                    System.out.println("Connection with " + clientSocket.getInetAddress().getHostAddress() + " closed.");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

How to Test It:

  1. Save both files: MultiThreadedTcpServer.java and ClientHandler.java.

  2. Compile them: javac MultiThreadedTcpServer.java ClientHandler.java

  3. Run the server: java MultiThreadedTcpServer

  4. Open two or more new terminals.

  5. In each terminal, connect to the server using nc:

    # In terminal 1
    nc localhost 6789
    # In terminal 2
    nc localhost 6789
  6. Type messages in each terminal. You will see the server echoing messages from both clients simultaneously in the server's console output.

  7. Type exit in one of the client terminals. That connection will close, but the other client will remain connected.


Using a ThreadPoolExecutor (More Efficient)

Creating a new thread for every single client can be inefficient if you have thousands of connections. A better approach is to use a thread pool. A thread pool reuses a fixed number of threads to handle client requests, which reduces the overhead of thread creation and destruction.

This is a very common and production-ready pattern.

ThreadPoolTcpServer.java

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolTcpServer {
    // Use a fixed-size thread pool. 10 threads is a good starting point.
    private static final int THREAD_POOL_SIZE = 10;
    private final ExecutorService pool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
    public static void main(String[] args) {
        int port = 6789;
        new ThreadPoolTcpServer().start(port);
    }
    public void start(int port) {
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Server is listening on port " + port + " with a thread pool of size " + THREAD_POOL_SIZE);
            while (true) {
                Socket clientSocket = serverSocket.accept();
                System.out.println("New client connected: " + clientSocket.getInetAddress().getHostAddress());
                // Submit a new task to the thread pool instead of creating a new thread
                pool.execute(new ClientHandler(clientSocket));
            }
        } catch (IOException e) {
            System.out.println("Server exception: " + e.getMessage());
            e.printStackTrace();
        } finally {
            // Shutdown the thread pool when the server is stopped
            pool.shutdown();
        }
    }
}

Note: You can reuse the exact same ClientHandler.java class from the previous example.

This version is more scalable and resource-efficient, making it suitable for applications that need to handle a large number of concurrent clients.

分享:
扫描分享到社交APP
上一篇
下一篇