- 核心概念:理解 Socket、IP 地址和端口号。
- 通信模型:客户端/服务器模型。
- 简单示例:一个基础的“请求-响应”模型,客户端发送消息,服务器返回消息。
- 多线程服务器:一个更高级的服务器,能够同时处理多个客户端连接。
- I/O 模式简介:传统的 BIO、NIO 和 AIO 的简单对比。
核心概念
- Socket (套接字):可以看作是网络通信的“端点”,一个 Socket 由一个 IP 地址和一个端口号唯一标识,程序通过 Socket 发送和接收数据,Java 中,
java.net.Socket代表客户端,java.net.ServerSocket代表服务端。 - IP 地址:网络中设备的唯一地址,
168.1.100或www.google.com。 - 端口号:计算机上应用程序的“门牌号”,一个 IP 地址可以同时运行多个网络服务,它们通过不同的端口号来区分,端口号范围是 0-65535,0-1023 是知名端口,一般不使用,HTTP 默认使用 80 端口,HTTPS 使用 443 端口,我们可以使用 1024 以上的任意空闲端口。
通信模型
客户端/服务器模型是最常见的网络通信模式:

-
服务器:
- 在一个特定的 IP 地址和端口上启动,并开始监听客户端的连接请求。
- 当一个客户端请求连接时,服务器接受连接,并建立一个与该客户端的通信通道(一个 Socket)。
- 通过这个通道,服务器可以读取客户端发送的数据,并向客户端发送数据。
-
客户端:
- 知道服务器的 IP 地址和端口号。
- 向服务器发起连接请求。
- 一旦连接成功,客户端也拥有一个与服务器通信的 Socket。
- 通过这个 Socket,客户端可以向服务器发送数据,并读取服务器的响应。
数据流向: 客户端 -> (发送数据) -> 服务器 服务器 -> (读取数据) -> 客户端 服务器 -> (发送响应) -> 客户端 客户端 -> (读取响应) -> 服务器
简单示例:单线程服务器
这个例子将展示最基本的交互:客户端发送一条消息,服务器接收后打印并发送一条确认消息,然后连接关闭。

1 服务器端代码
// 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 = 12345; // 定义服务器监听的端口号
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("服务器已启动,正在监听端口 " + port + "...");
// accept() 方法会阻塞,直到有客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
// 使用 try-with-resources 确保流被正确关闭
// 获取输入流,用于读取客户端发送的数据
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("收到客户端消息: " + inputLine);
// 如果客户端发送 "exit",则退出循环
if ("exit".equalsIgnoreCase(inputLine)) {
break;
}
// 发送响应给客户端
out.println("服务器已收到你的消息: " + inputLine);
}
System.out.println("客户端断开连接。");
} catch (IOException e) {
System.err.println("服务器异常: " + e.getMessage());
e.printStackTrace();
}
}
}
2 客户端代码
// 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 = "localhost"; // 或服务器的IP地址,如 "192.168.1.100"
int port = 12345;
try (Socket socket = new Socket(hostName, port)) {
System.out.println("已连接到服务器 " + hostName + ":" + port);
// 获取输入流,用于读取服务器发送的数据
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 获取输出流,用于向服务器发送数据
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
// 从控制台读取用户输入
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
String userInput;
System.out.println("请输入要发送的消息 (输入 'exit' 退出):");
while ((userInput = stdIn.readLine()) != null) {
// 发送消息到服务器
out.println(userInput);
// 如果用户输入 "exit",则退出循环
if ("exit".equalsIgnoreCase(userInput)) {
break;
}
// 读取并打印服务器的响应
String response = in.readLine();
System.out.println("服务器响应: " + response);
}
} catch (UnknownHostException e) {
System.err.println("无法找到主机: " + hostName);
e.printStackTrace();
} catch (IOException e) {
System.err.println("无法连接到主机 " + hostName + " 端口 " + port);
e.printStackTrace();
}
}
}
3 如何运行
- 先启动服务器:运行
SimpleServer,控制台会显示 "服务器已启动,正在监听端口 12345...",并在此处等待。 - 再启动客户端:运行
SimpleClient,控制台会显示 "已连接到服务器..."。 - 交互:
- 在客户端的控制台输入任意文本,"Hello, Server!",然后按回车。
- 服务器控制台会打印 "收到客户端消息: Hello, Server!",并向客户端发送响应。
- 客户端控制台会打印 "服务器响应: 服务器已收到你的消息: Hello, Server!"。
- 退出:在客户端输入 "exit" 并回车,客户端和服务器都会关闭连接并程序结束。
多线程服务器
上面的简单服务器一次只能处理一个客户端连接,当 serverSocket.accept() 阻塞时,它无法接受新的连接,要解决这个问题,我们需要为每个客户端连接创建一个新的线程来处理。
1 服务器端代码 (多线程版)
// 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 = 12345;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("多线程服务器已启动,正在监听端口 " + port + "...");
while (true) { // 循环接受所有客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("新客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
// 为每个客户端创建一个新的线程来处理
ClientHandler clientHandler = new ClientHandler(clientSocket);
new Thread(clientHandler).start();
}
} catch (IOException e) {
System.err.println("服务器异常: " + e.getMessage());
e.printStackTrace();
}
}
// 内部类,用于处理单个客户端的连接
private static 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() + "] 收到客户端消息: " + inputLine);
if ("exit".equalsIgnoreCase(inputLine)) {
break;
}
out.println("服务器已收到你的消息: " + inputLine);
}
} catch (IOException e) {
System.err.println("处理客户端时出错: " + e.getMessage());
} finally {
try {
clientSocket.close();
System.out.println("客户端连接已关闭.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
