杰瑞科技汇

Java ServerSocket如何正确创建与绑定端口?

Of course! Let's dive deep into ServerSocket in Java. It's a fundamental class for creating network servers that listen for incoming client connections.

Java ServerSocket如何正确创建与绑定端口?-图1
(图片来源网络,侵删)

What is a ServerSocket?

Think of a ServerSocket as a door or a receptionist for your server application.

  1. It Listens: It binds to a specific network port on your machine and waits (listens) for a client to try and connect.
  2. It Accepts: When a client attempts to connect, the accept() method "picks up the phone" or "opens the door," creating a new Socket that represents a direct, two-way communication channel with that specific client.
  3. It's the Entry Point: You create one ServerSocket for your entire server. It's not the communication channel itself; it's the factory for communication channels (Socket objects).

Key Concepts and Workflow

Here is the typical lifecycle of a server using ServerSocket:

  1. Create a ServerSocket: Specify the port number you want to listen on.
  2. Call accept(): This is a blocking method. The program will pause here and wait indefinitely until a client connects. When a client connects, accept() returns a new Socket object for that client.
  3. Communicate: Use the Socket object's InputStream and OutputStream (or getInputStream() and getOutputStream()) to send and receive data with the client.
  4. Close: After you're done communicating with the client, close the client's Socket. The ServerSocket can then go back to listening for new clients on the accept() call.

A Simple Example: Echo Server

An "Echo Server" is a classic example. It listens for a connection, receives a message from the client, and sends the exact same message back.

The Server Code (EchoServer.java)

import java.io.*;
import java.net.*;
public class EchoServer {
    public static void main(String[] args) {
        // The port number we will listen on
        int port = 6789;
        // Use a try-with-resources block to automatically close the ServerSocket
        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());
            // Use try-with-resources for the streams to ensure they are closed
            try (InputStream input = clientSocket.getInputStream();
                 BufferedReader reader = new BufferedReader(new InputStreamReader(input));
                 OutputStream output = clientSocket.getOutputStream();
                 PrintWriter writer = new PrintWriter(output, true)) { // autoFlush=true
                String text;
                // Read lines from the client until "exit" is sent
                while ((text = reader.readLine()) != null) {
                    System.out.println("Received from client: " + text);
                    if ("exit".equalsIgnoreCase(text)) {
                        System.out.println("Client requested to close the connection.");
                        break;
                    }
                    // Echo the received message back to the client
                    writer.println("Server: " + text);
                }
            } catch (IOException e) {
                System.err.println("Error handling client communication: " + e.getMessage());
            } finally {
                // Ensure the client socket is closed
                clientSocket.close();
                System.out.println("Client disconnected.");
            }
        } catch (IOException e) {
            System.err.println("Server exception: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

How to Run It:

  1. Save the code as EchoServer.java.
  2. Compile it: javac EchoServer.java
  3. Run it: java EchoServer
  4. You will see the output: Server is listening on port 6789. The program will now wait.

A Simple Client to Test the Server

To test our server, we need a client. Here's a simple one that connects and sends messages.

Java ServerSocket如何正确创建与绑定端口?-图2
(图片来源网络,侵删)

The Client Code (EchoClient.java)

import java.io.*;
import java.net.*;
public class EchoClient {
    public static void main(String[] args) {
        String hostname = "localhost"; // or "127.0.0.1"
        int port = 6789;
        try (Socket socket = new Socket(hostname, port);
             BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
             BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {
            System.out.println("Connected to the echo server.");
            System.out.println("Type a message and press Enter. Type 'exit' to quit.");
            String text;
            // Read from the console and send to the server
            while (true) {
                System.out.print("You: ");
                text = consoleReader.readLine();
                if (text == null || "exit".equalsIgnoreCase(text)) {
                    break;
                }
                writer.println(text);
                // Read the echoed message from the server
                String serverResponse = reader.readLine();
                if (serverResponse != null) {
                    System.out.println("Server: " + serverResponse);
                }
            }
        } catch (UnknownHostException e) {
            System.err.println("Don't know about host " + hostname);
        } catch (IOException e) {
            System.err.println("Couldn't get I/O for the connection to " +
                    hostname + ". Is the server running?");
        }
    }
}

How to Run the Test:

  1. Open two terminal/command prompt windows.
  2. In the first window, run the server: java EchoServer
  3. In the second window, compile and run the client: javac EchoClient.java then java EchoClient
  4. In the client window, you can type messages and see them echoed back by the server.

Important Methods of ServerSocket

Method Description
ServerSocket(int port) Constructor that creates a server socket, bound to the specified port.
Socket accept() Listens for a connection to be made to this socket and accepts it. This is a blocking call.
close() Closes the server socket. If a thread is blocked in accept(), it will throw a SocketException.
getInetAddress() Returns the local address to which this socket is bound.
getLocalPort() Returns the port number on which this socket is listening.
isBound() Returns the binding state of the socket.
isClosed() Returns the closed state of the socket.

Advanced Topics and Best Practices

Handling Multiple Clients (Threading)

The simple EchoServer can only handle one client at a time because accept() is blocking and the communication loop runs in the main thread. Real-world servers need to handle many clients simultaneously.

The solution is multithreading:

  1. The main thread's only job is to call serverSocket.accept() in a loop.
  2. When accept() returns a new Socket, a new thread is created to handle all communication with that client.
  3. The main thread immediately goes back to accept() to wait for the next client.

Here's a modified EchoServer that uses a thread pool:

import java.io.*;
import java.net.*;
public class MultiThreadedEchoServer {
    public static void main(String[] args) {
        int port = 6789;
        // Use a fixed-size thread pool to handle client connections
        int maxThreads = 10; 
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Server is listening on port " + port);
            while (true) {
                Socket clientSocket = serverSocket.accept();
                System.out.println("New client connected: " + clientSocket.getInetAddress().getHostAddress());
                // Create a new thread for each client
                ClientHandler clientHandler = new ClientHandler(clientSocket);
                new Thread(clientHandler).start();
            }
        } catch (IOException e) {
            System.err.println("Server exception: " + e.getMessage());
        }
    }
}
// A separate class to handle the client's communication
class ClientHandler implements Runnable {
    private final Socket clientSocket;
    public ClientHandler(Socket socket) {
        this.clientSocket = socket;
    }
    @Override
    public void run() {
        try (InputStream input = clientSocket.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(input));
             OutputStream output = clientSocket.getOutputStream();
             PrintWriter writer = new PrintWriter(output, true)) {
            String text;
            while ((text = reader.readLine()) != null) {
                System.out.println("Received from " + clientSocket.getInetAddress() + ": " + text);
                writer.println("Server: " + text);
            }
        } catch (IOException e) {
            System.err.println("Error handling client: " + e.getMessage());
        } finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("Client disconnected.");
        }
    }
}

Setting a Timeout

The accept() method can block forever. You can set a timeout so that if no client connects within a specified time, it throws an SocketTimeoutException, allowing your code to do other things (like check a shutdown flag).

ServerSocket serverSocket = new ServerSocket(6789);
serverSocket.setSoTimeout(5000); // Wait for 5 seconds
System.out.println("Waiting for a client (5 second timeout)...");
try {
    Socket socket = serverSocket.accept();
    // ... handle client
} catch (SocketTimeoutException e) {
    System.out.println("No client connected within 5 seconds.");
}

Reusing an Address (SO_REUSEADDR)

If your server crashes and you restart it quickly, you might get a BindException (Address already in use). This is because the old socket is in a TIME_WAIT state. You can tell the OS to reuse the address.

ServerSocket serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true); // Enable SO_REUSEADDR
serverSocket.bind(new InetSocketAddress(6789));
分享:
扫描分享到社交APP
上一篇
下一篇