0%

编解码器

WebSocket 编解码器:Java 对象与二进制流的转换桥梁

在 WebSocket 通信中,客户端与服务器通常需要传递复杂的 Java 对象(如自定义消息、业务数据),但网络传输的本质是二进制流。编解码器(Encoder/Decoder)的作用就是实现Java 对象与 WebSocket 消息(文本或二进制)之间的转换,是 WebSocket 处理复杂数据的核心组件。本文将详细介绍编解码器的实现方式、注册方法及应用场景。

编解码器的核心作用

WebSocket 协议本身仅支持文本(Text)二进制(Binary) 两种消息格式,而实际开发中需要传输的是结构化数据(如 MessageUser 等 Java 对象)。编解码器解决了这一矛盾:

  • 编码器(Encoder):将 Java 对象转换为 WebSocket 可传输的文本(String)或二进制(byte[])消息;
  • 解码器(Decoder):将 WebSocket 接收的文本或二进制消息转换为 Java 对象。

通过编解码器,开发者可直接在 WebSocket 端点中使用 Java 对象进行通信,无需手动处理字符串拼接或二进制解析,简化开发流程。

编码器(Encoder):Java 对象 → WebSocket 消息

编码器需根据消息类型(文本或二进制)实现对应的接口,核心方法是将 Java 对象转换为传输格式。

1. 文本编码器(Encoder.Text<T>

用于将 Java 对象转换为文本消息(通常为 JSON 或 XML 格式,推荐 JSON 因其轻量性)。

实现示例(基于 JSON):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import javax.websocket.Encoder;
import javax.websocket.EncodeException;
import javax.websocket.EndpointConfig;
import com.google.gson.Gson; // 使用 Gson 库实现 JSON 序列化

// 泛型 T 为需要转换的 Java 类(如 Message)
public class MessageEncoder implements Encoder.Text<Message> {
private static Gson gson = new Gson(); // Gson 实例(线程安全)

// 核心方法:将 Message 对象转换为 JSON 字符串
@Override
public String encode(Message message) throws EncodeException {
return gson.toJson(message); // 序列化 Java 对象为 JSON
}

// 初始化方法(可选,如初始化 Gson 配置)
@Override
public void init(EndpointConfig endpointConfig) {
// 初始化逻辑(如设置日期格式化)
gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.create();
}

// 销毁方法(可选,释放资源)
@Override
public void destroy() {
// 无需特殊处理,Gson 无资源需释放
}
}

// 自定义消息类(示例)
class Message {
private String sender; // 发送者
private String content; // 内容
private long timestamp; // 时间戳

// 构造器、getter、setter 省略
}

2. 二进制编码器(Encoder.Binary<T>

用于将 Java 对象转换为二进制消息(如序列化后的字节数组),适合传输二进制数据(如图片、文件片段)。

实现示例(基于 Java 序列化):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import javax.websocket.Encoder;
import javax.websocket.EncodeException;
import javax.websocket.EndpointConfig;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;

public class FileDataEncoder implements Encoder.Binary<FileData> {

// 核心方法:将 FileData 对象转换为字节数组
@Override
public byte[] encode(FileData fileData) throws EncodeException {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(fileData); // 序列化对象为字节数组
return bos.toByteArray();
} catch (Exception e) {
throw new EncodeException(fileData, "序列化失败", e);
}
}

@Override
public void init(EndpointConfig endpointConfig) {}

@Override
public void destroy() {}
}

// 自定义二进制数据类(需实现 Serializable)
class FileData implements java.io.Serializable {
private String fileName;
private byte[] content; // 文件内容字节数组

// 构造器、getter、setter 省略
}

解码器(Decoder):WebSocket 消息 → Java 对象

解码器是编码器的逆过程,将接收的文本或二进制消息转换为 Java 对象,需实现对应的解码接口。

1. 文本解码器(Decoder.Text<T>

将文本消息(如 JSON 字符串)转换为 Java 对象。

实现示例(基于 JSON):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import javax.websocket.Decoder;
import javax.websocket.DecodeException;
import javax.websocket.EndpointConfig;
import com.google.gson.Gson;

public class MessageDecoder implements Decoder.Text<Message> {
private static Gson gson = new Gson();

// 核心方法:将 JSON 字符串转换为 Message 对象
@Override
public Message decode(String json) throws DecodeException {
return gson.fromJson(json, Message.class); // 反序列化 JSON 为 Java 对象
}

// 预检查:判断消息是否可被当前解码器处理(如验证 JSON 格式)
@Override
public boolean willDecode(String json) {
try {
// 简单验证:尝试解析 JSON 结构(非严格校验)
gson.fromJson(json, Message.class);
return true;
} catch (Exception e) {
return false;
}
}

@Override
public void init(EndpointConfig endpointConfig) {}

@Override
public void destroy() {}
}

2. 二进制解码器(Decoder.Binary<T>

将二进制消息(如字节数组)转换为 Java 对象。

实现示例(基于 Java 反序列化):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import javax.websocket.Decoder;
import javax.websocket.DecodeException;
import javax.websocket.EndpointConfig;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;

public class FileDataDecoder implements Decoder.Binary<FileData> {

// 核心方法:将字节数组转换为 FileData 对象
@Override
public FileData decode(byte[] bytes) throws DecodeException {
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis)) {
return (FileData) ois.readObject(); // 反序列化字节数组为对象
} catch (Exception e) {
throw new DecodeException(bytes, "反序列化失败", e);
}
}

// 预检查:判断二进制数据是否可被处理(如验证魔术数)
@Override
public boolean willDecode(byte[] bytes) {
// 示例:验证字节数组长度是否大于 0(简单校验)
return bytes != null && bytes.length > 0;
}

@Override
public void init(EndpointConfig endpointConfig) {}

@Override
public void destroy() {}
}

注册编解码器到 WebSocket 端点

编解码器需注册到 WebSocket 端点后才能生效,通过 @ServerEndpoint 注解的 encodersdecoders 属性指定。

示例:注册编解码器的端点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

// 注册编码器和解码器
@ServerEndpoint(
value = "/chat",
encoders = { MessageEncoder.class, FileDataEncoder.class }, // 文本+二进制编码器
decoders = { MessageDecoder.class, FileDataDecoder.class } // 文本+二进制解码器
)
public class ChatEndpoint {

// 接收客户端发送的 Message 对象(自动通过 MessageDecoder 解码)
@OnMessage
public void onMessage(Message message, Session session) throws IOException, EncodeException {
System.out.println("收到消息:" + message.getContent());

// 向客户端发送 Message 对象(自动通过 MessageEncoder 编码为 JSON)
session.getBasicRemote().sendObject(new Message("server", "已收到消息", System.currentTimeMillis()));
}

// 接收二进制 FileData 对象(自动通过 FileDataDecoder 解码)
@OnMessage
public void onFileData(FileData fileData, Session session) throws IOException, EncodeException {
System.out.println("收到文件:" + fileData.getFileName());

// 向客户端发送 FileData 对象(自动通过 FileDataEncoder 编码为字节数组)
session.getBasicRemote().sendObject(new FileData("response.txt", "处理完成".getBytes()));
}
}

关键说明

  • 端点方法的参数类型(如 MessageFileData)会触发对应的解码器;
  • 调用 sendObject() 方法发送对象时,会自动使用注册的编码器转换为 WebSocket 消息。

编解码器的注意事项

  1. 线程安全:编码器和解码器的实例会被多个 WebSocket 会话共享,需确保其内部状态(如 Gson 实例)是线程安全的。
  2. 异常处理:编码 / 解码失败时需抛出 EncodeExceptionDecodeException,WebSocket 容器会自动关闭连接并通知客户端。
  3. 数据格式选择:
    • 文本消息优先使用 JSON(轻量、易读、跨语言支持好),推荐使用 Gson、Jackson 等库;
    • 二进制消息适合传输文件、图片等二进制数据,需注意序列化效率(如使用 Protocol Buffers 替代 Java 原生序列化)。
  4. 预检查有效性willDecode() 方法应尽可能验证消息格式(如 JSON 结构、二进制魔术数),避免无效消息进入解码流程

欢迎关注我的其它发布渠道