- 服务器端:在指定端口上监听客户端的连接请求,接收客户端发送的消息,并将其打印出来,然后向客户端发送一个响应。
- 客户端:连接到服务器的指定端口,向服务器发送一条消息,并接收服务器的响应。
我们将使用 Java 8 中引入的 try-with-resources 语句,它能自动关闭资源(如 Socket 和 ServerSocket),使代码更简洁、更安全。

服务器端代码
服务器将执行以下步骤:
- 创建一个
ServerSocket并绑定到一个特定的端口(8888)。 - 调用
accept()方法,等待客户端连接,这个方法是阻塞式的,直到有客户端连接上,它会返回一个Socket对象。 - 通过
Socket获取输入流,用来读取客户端发送的数据。 - 通过
Socket获取输出流,用来向客户端发送响应。 - 关闭连接和资源。
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-with-resources 确保 ServerSocket 和 Socket 被正确关闭
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("服务器已启动,等待客户端连接...");
// accept() 方法会阻塞,直到有客户端连接
try (Socket clientSocket = serverSocket.accept();
// 从客户端获取输入流,用于读取数据
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
// 向客户端获取输出流,用于发送数据
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
System.out.println("客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
// 读取客户端发送的消息
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("收到客户端消息: " + inputLine);
// 如果客户端发送 "exit",则关闭连接
if ("exit".equalsIgnoreCase(inputLine)) {
System.out.println("客户端请求断开连接。");
break;
}
// 向客户端发送响应
String response = "服务器已收到你的消息: " + inputLine;
out.println(response);
}
}
} catch (IOException e) {
System.err.println("服务器异常: " + e.getMessage());
e.printStackTrace();
}
}
}
客户端代码
客户端将执行以下步骤:
- 创建一个
Socket对象,指定服务器的 IP 地址(这里是本地localhost)和端口号。 - 通过
Socket获取输出流,向服务器发送消息。 - 通过
Socket获取输入流,接收服务器的响应。 - 关闭连接和资源。
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;
import java.util.Scanner;
public class SimpleClient {
public static void main(String[] args) {
// 服务器的地址和端口
String host = "localhost";
int port = 8888;
// 使用 try-with-resources 确保 Socket 和相关流被正确关闭
try (Socket socket = new Socket(host, port);
// 向服务器获取输出流,用于发送数据
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
// 从服务器获取输入流,用于读取数据
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
System.out.println("已连接到服务器 " + host + ":" + port);
System.out.println("请输入要发送的消息 (输入 'exit' 退出):");
Scanner scanner = new Scanner(System.in);
String userInput;
// 循环读取用户输入并发送到服务器
while (true) {
System.out.print("> ");
userInput = scanner.nextLine();
// 发送消息到服务器
out.println(userInput);
// 如果用户输入 "exit",则退出循环
if ("exit".equalsIgnoreCase(userInput)) {
break;
}
// 读取并打印服务器的响应
String serverResponse = in.readLine();
System.out.println("服务器响应: " + serverResponse);
}
} catch (UnknownHostException e) {
System.err.println("无法找到主机: " + host);
e.printStackTrace();
} catch (IOException e) {
System.err.println("I/O error: " + e.getMessage());
e.printStackTrace();
}
}
}
如何运行这个实例
这是一个典型的客户端-服务器模型,你需要先启动服务器,然后再启动客户端。
步骤 1: 编译代码
打开你的终端或命令提示符,进入两个 .java 文件所在的目录,然后编译它们:
javac SimpleServer.java SimpleClient.java
这会生成 SimpleServer.class 和 SimpleClient.class 文件。
步骤 2: 运行服务器 在同一个终端窗口中,先运行服务器:

java SimpleServer
你会看到以下输出,并且程序会在这里等待:
服务器已启动,等待客户端连接...
服务器已经启动,正在监听 8888 端口。
步骤 3: 运行客户端 打开一个新的终端窗口(不要关闭第一个),然后运行客户端:
java SimpleClient
你会看到以下输出:
已连接到服务器 localhost:8888
请输入要发送的消息 (输入 'exit' 退出):
>
步骤 4: 进行交互 在客户端的终端中输入一些消息,然后按回车。
- 在客户端输入:
> Hello, Server! - 在客户端终端会看到服务器的响应:
服务器响应: 服务器已收到你的消息: Hello, Server! - 在服务器端的终端会看到:
客户端已连接: 127.0.0.1 收到客户端消息: Hello, Server!
你可以继续输入多条消息进行测试,当你在客户端输入 exit 并回车时:
- 客户端会收到服务器的响应,然后程序退出。
- 服务器端会打印 "客户端请求断开连接。",
accept()方法会再次被调用,等待下一个客户端连接(如果有的话)。
代码核心概念解析
-
ServerSocketvsSocketServerSocket:服务器端的“门”,它负责在指定的端口上监听,等待客户端的“敲门”(连接请求),当有请求时,它会打开一个新的“门”(即Socket)与这个客户端进行通信。Socket:客户端和服务器之间通信的“通道”,一旦连接建立,双方都通过这个Socket来发送和接收数据。
-
输入流/输出流
socket.getInputStream():获取一个输入流,用于读取从对方(客户端或服务器)发来的数据。socket.getOutputStream():获取一个输出流,用于向对方写入(发送)数据。- 我们用
InputStreamReader和BufferedReader来包装输入流,这样可以方便地按行读取文本。 - 我们用
PrintWriter包装输出流,并设置autoFlush为true,这样每次调用println()方法后都会自动刷新缓冲区,确保数据被立即发送。
-
阻塞
serverSocket.accept()是一个阻塞方法,线程会在此处暂停,直到有新的连接进来,这是实现并发服务器的一个关键点。in.readLine()也是一个阻塞方法,它会一直等待,直到流中有数据可读或连接被关闭。
-
异常处理
- 网络编程充满了不确定性,比如网络中断、服务器未启动、客户端断开连接等,所有与网络相关的操作(
new ServerSocket,new Socket,accept,read,write)都必须放在try-catch块中,以处理可能发生的IOException。
- 网络编程充满了不确定性,比如网络中断、服务器未启动、客户端断开连接等,所有与网络相关的操作(
-
try-with-resourcesSocket、ServerSocket、InputStream、OutputStream等都是需要手动关闭的资源。try-with-resources语句确保了无论try块是正常结束还是抛出异常,这些资源都会在语句结束时被自动调用close()方法,有效避免了资源泄露。
这个例子是 Socket 编程的基础,理解了它,你就可以进一步学习更高级的主题,如多线程服务器(一个客户端一个线程)、使用 NIO(New I/O)实现高性能服务器、以及如何传输对象和文件等。
