WebSocket 编解码器:Java 对象与二进制流的转换桥梁
在 WebSocket 通信中,客户端与服务器通常需要传递复杂的 Java 对象(如自定义消息、业务数据),但网络传输的本质是二进制流。编解码器(Encoder/Decoder)的作用就是实现Java 对象与 WebSocket 消息(文本或二进制)之间的转换,是 WebSocket 处理复杂数据的核心组件。本文将详细介绍编解码器的实现方式、注册方法及应用场景。
编解码器的核心作用
WebSocket 协议本身仅支持文本(Text) 和二进制(Binary) 两种消息格式,而实际开发中需要传输的是结构化数据(如 Message、User 等 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;
public class MessageEncoder implements Encoder.Text<Message> { private static Gson gson = new Gson();
@Override public String encode(Message message) throws EncodeException { return gson.toJson(message); }
@Override public void init(EndpointConfig endpointConfig) { gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd HH:mm:ss") .create(); }
@Override public void destroy() { } }
class Message { private String sender; private String content; private long timestamp;
}
|
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> {
@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() {} }
class FileData implements java.io.Serializable { private String fileName; private byte[] content;
}
|
解码器(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();
@Override public Message decode(String json) throws DecodeException { return gson.fromJson(json, Message.class); }
@Override public boolean willDecode(String json) { try { 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> {
@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) { return bytes != null && bytes.length > 0; }
@Override public void init(EndpointConfig endpointConfig) {}
@Override public void destroy() {} }
|
注册编解码器到 WebSocket 端点
编解码器需注册到 WebSocket 端点后才能生效,通过 @ServerEndpoint 注解的 encoders 和 decoders 属性指定。
示例:注册编解码器的端点
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 {
@OnMessage public void onMessage(Message message, Session session) throws IOException, EncodeException { System.out.println("收到消息:" + message.getContent()); session.getBasicRemote().sendObject(new Message("server", "已收到消息", System.currentTimeMillis())); }
@OnMessage public void onFileData(FileData fileData, Session session) throws IOException, EncodeException { System.out.println("收到文件:" + fileData.getFileName()); session.getBasicRemote().sendObject(new FileData("response.txt", "处理完成".getBytes())); } }
|
关键说明:
- 端点方法的参数类型(如
Message、FileData)会触发对应的解码器;
- 调用
sendObject() 方法发送对象时,会自动使用注册的编码器转换为 WebSocket 消息。
编解码器的注意事项
- 线程安全:编码器和解码器的实例会被多个 WebSocket 会话共享,需确保其内部状态(如
Gson 实例)是线程安全的。
- 异常处理:编码 / 解码失败时需抛出
EncodeException 或 DecodeException,WebSocket 容器会自动关闭连接并通知客户端。
- 数据格式选择:
- 文本消息优先使用 JSON(轻量、易读、跨语言支持好),推荐使用 Gson、Jackson 等库;
- 二进制消息适合传输文件、图片等二进制数据,需注意序列化效率(如使用 Protocol Buffers 替代 Java 原生序列化)。
- 预检查有效性:
willDecode() 方法应尽可能验证消息格式(如 JSON 结构、二进制魔术数),避免无效消息进入解码流程