核心概念
在开始编码前,我们先理解几个核心概念:

- Socket (套接字):是网络编程的 API,它代表了一个网络连接的端点,你可以把它想象成一个电话插孔,一旦连接建立,双方就可以通过这个“插孔”进行数据收发。
- ServerSocket (服务器套接字):这是服务器端用来“监听”特定端口,等待客户端连接请求的特殊 Socket,它就像一个总机,负责接听所有打进来的电话,并将电话转接给一个具体的接线员(一个处理连接的线程)。
- IP 地址:网络中设备的唯一标识,
0.0.1(本机地址)。 - 端口号:同一台设备上,不同应用程序的标识,一个 IP 地址可以对应多个服务,端口号用来区分它们,范围是 0-65535,1024 以下的端口通常需要管理员权限才能使用。
- 流:
InputStream:输入流,用于从连接中读取数据。OutputStream:输出流,用于向连接中写入数据。
模型一:单线程服务器
这是最简单的模型,服务器一次只能处理一个客户端连接,当处理当前客户端时,其他客户端必须等待。
代码示例
这个服务器会监听 8888 端口,当有客户端连接时,它会读取客户端发送的一行消息,然后回复 "Server: " + 客户端消息,最后关闭连接。
SimpleServer.java
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 SimpleServer {
public static void main(String[] args) {
int port = 8888;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("服务器已启动,正在监听端口 " + port + "...");
// 1. 阻塞等待客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
// 2. 获取输入流和输出流
// 使用 try-with-resources 确保 stream 会被自动关闭
try (
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
) {
String inputLine;
// 3. 读取客户端发送的数据
while ((inputLine = in.readLine()) != null) {
System.out.println("收到客户端消息: " + inputLine);
// 4. 向客户端发送响应
out.println("Server: " + inputLine);
// 如果客户端发送 "exit",则退出循环
if ("exit".equalsIgnoreCase(inputLine)) {
break;
}
}
} finally {
// 5. 关闭客户端连接
System.out.println("客户端已断开连接。");
clientSocket.close();
}
} catch (IOException e) {
System.err.println("服务器异常: " + e.getMessage());
e.printStackTrace();
}
}
}
SimpleClient.java (用于测试)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class SimpleClient {
public static void main(String[] args) {
String hostname = "127.0.0.1"; // 或 "localhost"
int port = 8888;
try (
Socket socket = new Socket(hostname, port);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))
) {
System.out.println("已连接到服务器。");
String userInput;
// 从控制台读取用户输入
while ((userInput = stdIn.readLine()) != null) {
// 发送消息到服务器
out.println(userInput);
// 从服务器读取响应
String response = in.readLine();
System.out.println("服务器响应: " + response);
if ("exit".equalsIgnoreCase(userInput)) {
break;
}
}
} catch (UnknownHostException e) {
System.err.println("不知道的主机: " + hostname);
e.printStackTrace();
} catch (IOException e) {
System.err.println("I/O 错误: " + e.getMessage());
e.printStackTrace();
}
}
}
如何运行
- 先运行
SimpleServer。 - 然后运行
SimpleClient。 - 在客户端的控制台输入消息,按回车,你会在服务器控制台看到消息,并在客户端看到服务器的回复。
缺点:这个模型效率极低,如果服务器正在处理一个耗时操作(如一个大文件读写),那么其他所有客户端都将被阻塞,无法得到响应。
模型二:多线程服务器
为了解决单线程的阻塞问题,我们引入多线程,当一个客户端连接时,服务器创建一个独立的线程来处理这个客户端的通信,而主线程(监听线程)则继续等待新的客户端连接。
代码示例
我们将服务器的核心逻辑(处理客户端)封装到一个 ClientHandler 类中。
MultiThreadServer.java

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 MultiThreadServer {
public static void main(String[] args) {
int port = 8888;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("多线程服务器已启动,正在监听端口 " + port + "...");
while (true) {
// 1. 阻塞等待客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("新客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
// 2. 为每个客户端创建一个新线程
ClientHandler clientHandler = new ClientHandler(clientSocket);
new Thread(clientHandler).start();
}
} catch (IOException e) {
System.err.println("服务器异常: " + e.getMessage());
e.printStackTrace();
}
}
}
// 客户端处理器类
class ClientHandler implements Runnable {
private final Socket clientSocket;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try (
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("线程 [" + Thread.currentThread().getName() + "] 收到客户端 [" + clientSocket.getInetAddress() + "] 消息: " + inputLine);
out.println("Server (Thread " + Thread.currentThread().getName() + "): " + inputLine);
if ("exit".equalsIgnoreCase(inputLine)) {
break;
}
}
} catch (IOException e) {
System.err.println("处理客户端时发生错误: " + e.getMessage());
} finally {
try {
clientSocket.close();
System.out.println("客户端 [" + clientSocket.getInetAddress() + "] 已断开连接。");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
优点:
- 并发处理:服务器可以同时与多个客户端通信,大大提高了响应能力。
- 逻辑清晰:将客户端处理逻辑分离到
ClientHandler中,代码结构更清晰。
缺点:
- 资源消耗:每个客户端连接都会创建一个线程,如果客户端数量非常大(例如数万),创建和管理这么多线程会消耗大量内存和 CPU,可能导致服务器性能下降甚至崩溃,这就是所谓的“C10K 问题”。
模型三:线程池服务器
为了解决多线程模型在大量连接下的资源消耗问题,我们引入线程池,线程池会预先创建一定数量的线程,并将客户端任务放入一个任务队列中,线程池中的线程从队列中取出任务来执行,避免了频繁创建和销毁线程的开销。
代码示例
我们将使用 Java 自带的 ExecutorService 来管理线程池。
ThreadPoolServer.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolServer {
// 使用固定大小的线程池
private static final int THREAD_POOL_SIZE = 10;
public static void main(String[] args) {
int port = 8888;
ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("线程池服务器已启动,正在监听端口 " + port + "...");
System.out.println("线程池大小: " + THREAD_POOL_SIZE);
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("新客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
// 将客户端处理任务提交给线程池
threadPool.execute(new ClientHandler(clientSocket));
}
} catch (IOException e) {
System.err.println("服务器异常: " + e.getMessage());
e.printStackTrace();
} finally {
// 关闭线程池
threadPool.shutdown();
System.out.println("服务器已关闭。");
}
}
}
// ClientHandler 类与多线程服务器中的完全相同
class ClientHandler implements Runnable {
private final Socket clientSocket;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
// ... (代码与 MultiThreadServer 中的 ClientHandler 完全一样)
try (
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("线程 [" + Thread.currentThread().getName() + "] 收到客户端 [" + clientSocket.getInetAddress() + "] 消息: " + inputLine);
out.println("Server (ThreadPool): " + inputLine);
if ("exit".equalsIgnoreCase(inputLine)) {
break;
}
}
} catch (IOException e) {
System.err.println("处理客户端时发生错误: " + e.getMessage());
} finally {
try {
clientSocket.close();
System.out.println("客户端 [" + clientSocket.getInetAddress() + "] 已断开连接。");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
优点:
- 资源可控:限制了线程的最大数量,防止了因过多连接导致服务器资源耗尽。
- 性能更优:通过复用线程,减少了线程创建和销毁的开销,提高了系统的响应速度和稳定性。
缺点:
- 如果任务队列中的任务积压过多,新的客户端连接可能会在
serverSocket.accept()阶段就因为无法被处理而延迟。
更高级的模型:NIO (New I/O)
对于需要处理成千上万个并发连接的超高性能服务器,Java NIO (New I/O) 是更好的选择,它使用非阻塞 I/O 和多路复用技术(如 Selector),可以用一个或几个线程来管理成千上万的连接。
- 核心组件:
Channel:类似Socket,但双向且可以非阻塞。Buffer:数据被写入Buffer,从Buffer中读取。Selector:一个Selector可以监控多个Channel的事件(如连接、读、写),当某个Channel有事件发生时,Selector会通知你。
NIO 的实现比传统的 BIO (Blocking I/O) 模型复杂得多,但它能极大地扩展服务器的并发能力。
总结与建议
| 模型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 单线程服务器 | 实现简单,逻辑清晰 | 并发能力极差,性能低 | 学习、演示,或处理单任务的场景 |
| 多线程服务器 | 并发处理能力强,结构清晰 | 资源消耗大,连接数多时性能差 | 中小型应用,连接数不多(几百个) |
| 线程池服务器 | 资源可控,性能稳定,实现相对简单 | 连接数远超线程池大小时,任务会积压 | 大多数 Java 应用的首选,能很好地平衡性能和资源 |
| NIO 服务器 | 并发能力极强,资源利用率高 | 实现复杂,编程模型不直观 | 超高性能服务器(如聊天室、游戏服务器、代理服务器) |
给你的建议:
- 如果你是初学者:从 单线程服务器 开始,理解基本流程。
- 如果你要开发一个实际的应用:线程池服务器 是最常用、最稳妥的选择,它在性能、资源管理和开发难度之间取得了很好的平衡。
- 如果你有超高性能的需求:再去深入研究 NIO,Java NIO 是构建高性能网络服务的基础框架,Netty、Tomcat 等知名框架都是基于 NIO 构建的。
