1. 概述

在网络通信中,我们经常使用 Socket 来收发数据。Socket 本质上是 IP 地址和端口号的组合,可以唯一标识某台机器上运行的程序。

本文将介绍如何通过 Java 的 ServerSocket 读取通过 Socket 发送过来的数据。

2. 从 Socket 读取数据

假设你已经了解 Java 的 Socket 编程基础(如果不了解,可以先阅读这篇 Java Socket 编程指南)。

接下来,我们深入探讨如何从服务器监听的端口读取数据。

首先,我们需要声明并初始化 ServerSocketSocketDataInputStream

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 指定

figure1-1-1

在客户端和服务端建立连接后,每条消息都遵循这个格式。我们可以根据这个格式编写代码来解析每条消息,并读取指定长度的字节。

示例:读取字符串

我们先用 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() 就能读完所有数据,务必循环读取直到满足长度要求。

如果你正在开发网络通信模块,这套协议结构简单实用,可以作为基础参考。


原始标题:Read an InputStream using the Java Server Socket | Baeldung