1. 概述
在网络通信中,我们经常使用 Socket 来收发数据。Socket 本质上是 IP 地址和端口号的组合,可以唯一标识某台机器上运行的程序。
本文将介绍如何通过 Java 的 ServerSocket 读取通过 Socket 发送过来的数据。
2. 从 Socket 读取数据
假设你已经了解 Java 的 Socket 编程基础(如果不了解,可以先阅读这篇 Java Socket 编程指南)。
接下来,我们深入探讨如何从服务器监听的端口读取数据。
首先,我们需要声明并初始化 ServerSocket
、Socket
和 DataInputStream
:
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();
DataInputStream in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
注意我们把 InputStream
包装成了 DataInputStream
,这样我们就可以用更方便的方法读取文本行和 Java 基本数据类型。
✅ 优势:如果我们知道接收的数据类型,可以使用 readChar()
、readInt()
、readDouble()
、readLine()
这些方法直接读取。
❌ 问题:但如果我们不知道数据类型和长度,这种方式就不太适用了。
这时候,我们只能以字节流的形式读取数据,使用底层的 read()
方法。但这就引出了一个关键问题:
⚠️ 如何判断接收到的数据类型和长度?
我们将在下一节讨论这个问题的解决方案。
3. 从 Socket 读取二进制数据
当我们以字节流形式读取数据时,需要在客户端和服务端之间定义一套通信协议。最简单的协议之一是 TLV(Type-Length-Value)格式。
也就是说,每条消息都由三部分组成:
- T(Type):1 字节,表示数据类型,例如
's'
表示字符串 - L(Length):4 字节,表示数据长度
- V(Value):实际数据内容,长度由 Length 指定
在客户端和服务端建立连接后,每条消息都遵循这个格式。我们可以根据这个格式编写代码来解析每条消息,并读取指定长度的字节。
示例:读取字符串
我们先用 readChar()
读取数据类型,再用 readInt()
读取长度:
char dataType = in.readChar();
int length = in.readInt();
然后,开始读取实际数据内容:
⚠️ 注意:read()
方法可能不会一次性读取全部数据,因此需要用 while
循环多次读取:
if(dataType == 's') {
byte[] messageByte = new byte[length];
boolean end = false;
StringBuilder dataString = new StringBuilder(length);
int totalBytesRead = 0;
while(!end) {
int currentBytesRead = in.read(messageByte);
totalBytesRead += currentBytesRead;
if(totalBytesRead <= length) {
dataString.append(new String(messageByte, 0, currentBytesRead, StandardCharsets.UTF_8));
} else {
dataString.append(new String(messageByte, 0, length - totalBytesRead + currentBytesRead, StandardCharsets.UTF_8));
}
if(dataString.length() >= length) {
end = true;
}
}
}
这样,我们就完整地读取了一条字符串类型的消息。
4. 客户端发送数据的代码
那客户端这边该怎么写呢?其实非常简单:
char type = 's'; // 's' 表示字符串类型
String data = "This is a string of length 29";
byte[] dataInBytes = data.getBytes(StandardCharsets.UTF_8);
out.writeChar(type);
out.writeInt(dataInBytes.length);
out.write(dataInBytes);
这就是客户端发送一条字符串数据的完整逻辑。
5. 小结
本文介绍了如何通过 Java 的 ServerSocket 读取来自网络的数据流。
我们讨论了以下几种方式:
- 使用
DataInputStream
读取已知类型的数据(如readInt()
、readChar()
) - 当数据类型和长度未知时,采用 TLV 协议(Type-Length-Value)来解析字节流
- 客户端如何按照协议格式发送数据
✅ 踩坑提示:读取字节流时,不要以为一次 read()
就能读完所有数据,务必循环读取直到满足长度要求。
如果你正在开发网络通信模块,这套协议结构简单实用,可以作为基础参考。