Java 实例 – ServerSocket 和 Socket 通信实例(超详细)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
在 Java 编程中,网络通信是构建分布式系统和客户端-服务器架构的核心能力。无论是开发聊天应用、在线游戏,还是搭建简单的数据传输工具,掌握 ServerSocket 和 Socket 的使用方法都至关重要。本文将通过循序渐进的实例,结合形象的比喻和代码演示,帮助读者理解 Java 网络通信的底层逻辑,并逐步构建一个完整的通信案例。
一、基础概念解析:Socket 与 ServerSocket 的角色分工
1.1 Socket:网络通信的“邮差”
想象一下,你给朋友写信时需要一个邮差(Socket)来传递信件。在 Java 中,Socket 是客户端与服务器端建立连接的核心类。它负责:
- 主动发起连接:客户端通过
new Socket("服务器地址", 端口号)
向服务器发起请求。 - 传输数据:通过输入/输出流(InputStream/OutputStream)发送或接收数据。
1.2 ServerSocket:监听端口的“邮局”
服务器端则像一个邮局(ServerSocket),它通过以下方式工作:
- 监听指定端口:使用
new ServerSocket(端口号)
告诉系统“我在这里等待信件”。 - 被动接受连接:当客户端连接时,通过
accept()
方法“接收信件”并返回一个 Socket 对象。
1.3 主动与被动的比喻
- 客户端(Socket):像主动投递信件的邮差,必须知道邮局(ServerSocket)的位置和端口号(邮局编号)。
- 服务器端(ServerSocket):像静候收发信件的邮局,端口是它的唯一标识。
二、第一个通信实例:Hello World
2.1 服务器端代码实现
import java.io.*;
import java.net.*;
public class SimpleServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888); // 监听8888端口
System.out.println("服务器已启动,等待连接...");
Socket clientSocket = serverSocket.accept(); // 阻塞等待客户端连接
System.out.println("客户端已连接!");
// 创建输入流读取客户端数据
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
// 创建输出流向客户端发送数据
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
String clientMessage = in.readLine();
System.out.println("客户端消息:" + clientMessage);
out.println("Hello Client! 服务器已收到你的消息!");
// 关闭资源
in.close();
out.close();
clientSocket.close();
serverSocket.close();
}
}
2.2 客户端代码实现
import java.io.*;
import java.net.*;
public class SimpleClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8888); // 连接本地服务器的8888端口
// 创建输出流发送数据
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello Server! 我是客户端!");
// 创建输入流读取服务器响应
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String serverResponse = in.readLine();
System.out.println("服务器回复:" + serverResponse);
// 关闭资源
out.close();
in.close();
socket.close();
}
}
2.3 运行步骤与验证
- 先运行 SimpleServer,控制台输出:“服务器已启动,等待连接...”。
- 再运行 SimpleClient,客户端控制台显示:“服务器回复:Hello Client! 服务器已收到你的消息!”。
- 服务器控制台显示客户端消息:“Hello Server! 我是客户端!”。
三、数据传输的细节处理
3.1 字符编码与流的选择
在代码中,我们使用了 PrintWriter
和 BufferedReader
处理字符数据。若需传输二进制数据(如图片、文件),应改用字节流:
// 服务器端发送字节流示例
OutputStream out = clientSocket.getOutputStream();
byte[] data = "二进制数据".getBytes(StandardCharsets.UTF_8);
out.write(data);
// 客户端读取字节流示例
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int bytesRead = in.read(buffer);
String receivedData = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);
3.2 异常处理与资源管理
直接关闭流和 Socket 可能引发资源泄漏。推荐使用 try-with-resources 自动管理:
try (
ServerSocket serverSocket = new ServerSocket(8888);
Socket clientSocket = serverSocket.accept();
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()))
) {
// 通信逻辑
} catch (IOException e) {
e.printStackTrace();
}
四、扩展应用:多线程服务器
4.1 问题:单线程服务器的局限性
当前代码中,服务器每次只能处理一个客户端请求。若第二个客户端在第一个连接未关闭前尝试连接,将被阻塞在 accept()
方法中。
4.2 解决方案:多线程化
通过为每个连接创建独立线程,服务器可同时处理多个客户端。修改后的代码如下:
public class MultiThreadServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("多线程服务器启动...");
while (true) {
Socket clientSocket = serverSocket.accept();
new ClientHandler(clientSocket).start();
}
}
static class ClientHandler extends Thread {
private final Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()))
) {
String clientMessage = in.readLine();
System.out.println("客户端消息:" + clientMessage);
out.println("服务器已收到!");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4.3 运行效果
启动 MultiThreadServer 后,多个客户端可同时连接并收发消息,彼此之间互不干扰。
五、常见问题与解决方案
5.1 端口被占用
若启动服务器时提示端口被占用,可尝试:
- 更改端口号(如
new ServerSocket(9999)
)。 - 关闭占用端口的进程(如通过
netstat -ano
查找并终止)。
5.2 数据粘包问题
当数据分批次发送时,可能因网络延迟导致接收端读取到的数据“粘连”。解决方法包括:
- 固定长度协议:发送前先发送数据长度。
- 分隔符协议:用
\n
或自定义符号分隔消息。
// 发送端
String message = "Hello\nWorld";
out.write(message.getBytes());
// 接收端
StringBuilder buffer = new StringBuilder();
while (true) {
int data = in.read();
if (data == '\n') {
break;
}
buffer.append((char) data);
}
String received = buffer.toString();
六、结论
通过本文的实例,我们掌握了 ServerSocket 和 Socket 的核心用法,从单线程通信到多线程扩展,逐步构建了一个完整的网络通信系统。理解以下关键点至关重要:
- 主动与被动:客户端主动发起连接,服务器被动监听。
- 资源管理:使用 try-with-resources 避免泄漏。
- 扩展方向:通过多线程或协议设计提升系统能力。
建议读者尝试修改示例代码,例如添加文件传输功能,或使用 NIO 框架优化性能。掌握这些基础后,可进一步探索 HTTP 协议实现、WebSocket 或分布式系统开发,为构建更复杂的网络应用打下坚实基础。