OkHttp WebSocket 实现详解:数据传输、帧结构与组件关系
1. 整体架构与数据流
WebSocket通信在OkHttp中由多个组件协同完成,下图展示了这些组件之间的关系和数据流向:
flowchart TD
A["应用层\n(用户代码)"] <-->|发送/接收消息| B[RealWebSocket]
B <-->|控制| D[Socket]
B -->|编码/解码| C
subgraph 编码解码层
C[["WebSocketWriter\nWebSocketReader"]]
end
C -->|写入帧| D
D -->|读取帧| C
C -->|使用| E[Okio\nSource/Sink/Buffer]
D -->|字节流| E
F[WebSocket服务器] --> D
数据发送流程
- 应用层调用 → 用户调用
WebSocket.send(String/ByteString)
- RealWebSocket处理 → 将消息排队并安排写入
- WebSocketWriter编码 → 将消息转换为WebSocket帧
- Okio写入 → 通过BufferedSink将帧写入Socket
- 网络传输 → 数据通过网络发送到服务器
数据接收流程
- 网络接收 → Socket接收来自服务器的数据
- Okio读取 → 通过BufferedSource从Socket读取数据
- WebSocketReader解码 → 将字节流解析为WebSocket帧
- RealWebSocket处理 → 处理帧并触发相应回调
- 应用层接收 → 用户通过WebSocketListener接收消息
2. 核心数据结构
2.1 WebSocket帧结构
WebSocket协议使用帧(Frame)作为通信的基本单位,每个帧的结构如下:
lua
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
字段说明:
- FIN (1 bit): 标识这是消息的最后一个片段
- RSV1-3 (3 bits): 保留位,通常为0,RSV1用于压缩标志
- Opcode (4 bits) : 操作码,定义帧的类型
- 0x0: 继续帧
- 0x1: 文本帧
- 0x2: 二进制帧
- 0x8: 关闭帧
- 0x9: Ping帧
- 0xA: Pong帧
- MASK (1 bit): 标识是否使用掩码
- Payload length (7+16/64 bits): 有效载荷长度
- Masking-key (32 bits): 掩码密钥(如果MASK=1)
- Payload Data: 实际数据内容
2.2 Okio核心组件
Buffer类
java
public final class Buffer implements BufferedSource, BufferedSink {
// 链表头节点
Segment head;
// 当前缓冲区大小
long size;
// 主要方法
public Buffer write(byte[] source) { ... }
public Buffer writeUtf8(String string) { ... }
public byte readByte() { ... }
public String readUtf8() { ... }
// ...更多读写方法
}
Buffer使用分段链表结构存储数据:
flowchart LR
subgraph Segment1["Segment"]
S1D["data[8KB]"]
S1P["pos: 0"]
S1L["limit: 8K"]
end
subgraph Segment2["Segment"]
S2D["data[8KB]"]
S2P["pos: 0"]
S2L["limit: 4K"]
end
subgraph Segment3["Segment"]
S3D["data[8KB]"]
S3P["pos: 0"]
S3L["limit: 2K"]
end
Segment1 --> Segment2
Segment2 --> Segment3
Segment类
java
final class Segment {
// 每个段的大小(8KB)
static final int SIZE = 8192;
// 数据数组
final byte[] data;
// 有效数据起始位置
int pos;
// 有效数据结束位置
int limit;
// 是否共享(用于写时复制)
boolean shared;
// 是否可被其他Buffer所有
boolean owner;
// 链表指针
Segment next;
Segment prev;
}
Source和Sink接口
java
public interface Source extends Closeable {
// 从此源读取字节到sink
long read(Buffer sink, long byteCount) throws IOException;
// 设置超时
Timeout timeout();
// 关闭源
void close() throws IOException;
}
public interface Sink extends Closeable, Flushable {
// 从source写入字节到此接收器
void write(Buffer source, long byteCount) throws IOException;
// 刷新缓冲数据
void flush() throws IOException;
// 设置超时
Timeout timeout();
// 关闭接收器
void close() throws IOException;
}
2.3 WebSocketReader/Writer类
WebSocketReader
java
final class WebSocketReader {
// 用于读取数据的缓冲源
private final BufferedSource source;
// 是否是客户端模式
private final boolean isClient;
// 消息回调接口
private final FrameCallback frameCallback;
// WebSocket扩展
private final WebSocketExtensions extensions;
// 消息解压缩器
private final MessageInflater messageInflater;
// 帧状态变量
private boolean closed;
private int opcode;
private long frameLength;
private boolean isFinalFrame;
private boolean isMasked;
private boolean isCompressed;
// 缓冲区
private final Buffer controlFrameBuffer = new Buffer();
private final Buffer messageFrameBuffer = new Buffer();
// 主要方法
void processNextFrame() throws IOException { ... }
private void readHeader() throws IOException { ... }
private void readControlFrame() throws IOException { ... }
private void readMessageFrame() throws IOException { ... }
}
WebSocketWriter
java
final class WebSocketWriter {
// 用于写入数据的缓冲接收器
private final BufferedSink sink;
// 是否是客户端模式
private final boolean isClient;
// 随机数生成器(用于掩码)
private final Random random;
// 写入锁
private final Object writerLock = new Object();
// 是否已关闭
private boolean closed;
// 消息压缩器
private final MessageDeflater messageDeflater;
// 缓冲区
private final Buffer buffer = new Buffer();
private final Buffer messageBuffer = new Buffer();
private final byte[] maskKey;
private final Buffer maskBuffer;
// 主要方法
void writeMessageFrame(int formatOpcode, ByteString data) throws IOException { ... }
void writeControlFrame(int opcode, ByteString payload) throws IOException { ... }
void writePing(ByteString payload) throws IOException { ... }
void writePong(ByteString payload) throws IOException { ... }
void writeClose(int code, ByteString reason) throws IOException { ... }
}
3. 详细数据传输流程
3.1 发送消息流程
sequenceDiagram
participant App as 应用代码
participant RWS as RealWebSocket
participant WSW as WebSocketWriter
participant Socket
App->>RWS: send(message)
RWS->>WSW: writeMessageFrame()
Note over WSW: 1. 准备帧头
Note over WSW: 2. 可选压缩
Note over WSW: 3. 应用掩码(如果是客户端)
WSW->>Socket: 4. 写入Socket
Note over Socket: 5. 网络传输
详细步骤:
-
应用调用发送方法
javawebSocket.send("Hello, WebSocket!"); // 或 webSocket.send(ByteString.of("binary data".getBytes()));
-
RealWebSocket处理发送请求
javapublic boolean send(String text) { if (text == null) throw new NullPointerException("text == null"); return send(ByteString.encodeUtf8(text), OPCODE_TEXT); } private boolean send(ByteString data, int formatOpcode) { // 检查状态和大小限制 synchronized (this) { if (closed) return false; // 将消息加入队列 messageQueue.add(new Message(formatOpcode, data)); // 检查队列大小 long queueSize = queueSize + data.size(); if (queueSize > MAX_QUEUE_SIZE) { close(CLOSE_CLIENT_GOING_AWAY, null); return false; } queueSize = queueSize; } // 安排写入操作 runWriter(); return true; }
-
WebSocketWriter编码消息
javavoid writeMessageFrame(int formatOpcode, ByteString data) throws IOException { synchronized (writerLock) { // 准备缓冲区 messageBuffer.write(data); // 可选压缩 boolean compress = false; if (messageDeflater != null && data.size() >= MIN_COMPRESS_SIZE) { Buffer compressedMessage = new Buffer(); messageDeflater.deflate(messageBuffer, compressedMessage); if (compressedMessage.size() < data.size()) { compress = true; messageBuffer = compressedMessage; } } // 计算帧大小 long frameSize = messageBuffer.size(); // 写入帧头 byte b0 = (byte) (FIN_FLAG | formatOpcode); if (compress) { b0 |= RSV1_FLAG; // 设置压缩标志 } buffer.writeByte(b0); // 写入长度和掩码标志 byte b1 = isClient ? (byte) MASK_FLAG : 0; // 根据长度选择合适的编码方式 if (frameSize <= PAYLOAD_SHORT_MAX) { buffer.writeByte(b1 | (byte) frameSize); } else if (frameSize <= PAYLOAD_MEDIUM_MAX) { buffer.writeByte(b1 | PAYLOAD_SHORT); buffer.writeShort((int) frameSize); } else { buffer.writeByte(b1 | PAYLOAD_LONG); buffer.writeLong(frameSize); } // 如果是客户端,生成并写入掩码密钥 if (isClient) { random.nextBytes(maskKey); buffer.write(maskKey); // 应用掩码 WebSocketProtocol.toggleMask(messageBuffer.data(), 0, frameSize, maskKey, 0); } // 写入消息内容 buffer.write(messageBuffer, frameSize); // 刷新到底层Socket sink.write(buffer, buffer.size()); sink.flush(); } }
-
数据通过Socket发送
- 编码后的WebSocket帧通过BufferedSink写入Socket
- Socket将数据发送到网络层
- 数据通过网络传输到服务器
3.2 接收消息流程
sequenceDiagram
participant App as 应用代码
participant RWS as RealWebSocket
participant WSR as WebSocketReader
participant Socket
Note over Socket: 1. 接收数据
Socket->>WSR: 2. 读取数据
Note over WSR: 3. 解析帧头
Note over WSR: 4. 读取帧内容
Note over WSR: 5. 移除掩码(如果有)
Note over WSR: 6. 可选解压缩
WSR->>RWS: 7. 回调消息
RWS->>App: 8. 通知应用
详细步骤:
-
Socket接收数据
- 服务器发送WebSocket帧
- Socket接收网络数据
-
WebSocketReader读取数据
javavoid processNextFrame() throws IOException { // 读取帧头 readHeader(); if (isControlFrame()) { // 处理控制帧(PING, PONG, CLOSE) readControlFrame(); } else { // 处理数据帧(TEXT, BINARY) readMessageFrame(); } }
-
解析帧头
javaprivate void readHeader() throws IOException { if (closed) throw new IOException("closed"); // 读取第一个字节,包含FIN标志和操作码 int b0 = source.readByte() & 0xff; isFinalFrame = (b0 & 0x80) != 0; isCompressed = (b0 & 0x40) != 0; // 检查保留位 if ((b0 & 0x20) != 0 || (b0 & 0x10) != 0) { throw new ProtocolException("Reserved flags are unsupported."); } // 提取操作码 opcode = b0 & 0xf; // 读取第二个字节,包含MASK标志和有效载荷长度 int b1 = source.readByte() & 0xff; isMasked = (b1 & 0x80) != 0; // 提取有效载荷长度 frameLength = b1 & 0x7f; // 处理扩展长度 if (frameLength == 126) { frameLength = source.readShort() & 0xffffL; // 2字节长度 } else if (frameLength == 127) { frameLength = source.readLong(); // 8字节长度 } // 如果帧使用掩码,读取掩码密钥 if (isMasked) { maskKey = source.readByteArray(4); } }
-
读取帧内容
javaprivate void readMessageFrame() throws IOException { // 准备缓冲区 Buffer message = messageFrameBuffer; // 读取帧内容 if (frameLength > 0) { source.readFully(message, frameLength); // 如果使用掩码,应用掩码解码 if (isMasked) { WebSocketProtocol.toggleMask(message.data(), message.size() - frameLength, frameLength, maskKey, 0); } } // 处理最终帧 if (isFinalFrame) { // 如果消息被压缩,进行解压缩 if (isCompressed) { Buffer uncompressedMessage = new Buffer(); messageInflater.inflate(message, uncompressedMessage); message = uncompressedMessage; } // 根据操作码回调不同类型的消息 if (opcode == OPCODE_TEXT) { frameCallback.onReadMessage(message.readUtf8()); } else { frameCallback.onReadMessage(message.readByteString()); } } }
-
RealWebSocket处理消息
java// 在RealWebSocket中实现的FrameCallback接口 @Override public void onReadMessage(String text) throws IOException { listener.onMessage(this, text); } @Override public void onReadMessage(ByteString bytes) throws IOException { listener.onMessage(this, bytes); }
-
应用层接收消息
- 用户通过WebSocketListener接收消息
javawebSocket.listener().onMessage(webSocket, message);
4. 掩码机制详解
WebSocket协议要求客户端发送的所有帧必须使用掩码,而服务器发送的帧不能使用掩码。这是一种安全措施,目的是:
- 防止缓存投毒攻击:掩码可以防止恶意代理或中间人预测和缓存WebSocket流量
- 防止协议混淆:掩码使WebSocket流量看起来是随机的,减少与其他协议混淆的可能性
掩码算法
java
// 应用掩码的代码示例
static void toggleMask(byte[] buffer, long byteCount, byte[] key) {
for (int i = 0; i < byteCount; i++) {
buffer[i] = (byte) (buffer[i] ^ key[i % 4]);
}
}
掩码过程图解:
flowchart LR
subgraph 原始数据
A[A] --- B[B] --- C[C] --- D[D] --- E[E] --- F[F] --- G[G] --- H[H]
end
subgraph 掩码密钥
K1[K1] --- K2[K2] --- K3[K3] --- K4[K4] --- K1'[K1] --- K2'[K2] --- K3'[K3] --- K4'[K4]
end
A -- XOR --> M1[A^K1]
B -- XOR --> M2[B^K2]
C -- XOR --> M3[C^K3]
D -- XOR --> M4[D^K4]
E -- XOR --> M5[E^K1]
F -- XOR --> M6[F^K2]
G -- XOR --> M7[G^K3]
H -- XOR --> M8[H^K4]
subgraph 掩码后数据
M1 --- M2 --- M3 --- M4 --- M5 --- M6 --- M7 --- M8
end
5. 压缩扩展机制
WebSocket协议支持通过扩展机制来压缩消息,OkHttp实现了permessage-deflate扩展(RFC 7692)。
压缩协商过程
-
握手阶段:
- 客户端在HTTP升级请求中包含
Sec-WebSocket-Extensions: permessage-deflate
头 - 服务器在响应中确认并可能修改参数
- 客户端在HTTP升级请求中包含
-
压缩参数:
client_max_window_bits
:客户端压缩窗口大小server_max_window_bits
:服务器压缩窗口大小client_no_context_takeover
:每条消息后重置客户端压缩上下文server_no_context_takeover
:每条消息后重置服务器压缩上下文
压缩流程
flowchart LR
A[原始消息] --> B[DEFLATE压缩]
B --> C[设置RSV1=1]
C --> D[发送WebSocket帧]
解压缩流程
flowchart LR
A[接收帧] --> B[检查RSV1=1]
B --> C[INFLATE解压]
C --> D[处理解压后的消息]
6. Socket与Okio的关系
OkHttp使用Okio库来处理底层I/O操作,它在Java标准Socket API之上提供了更高效的抽象。
Socket包装过程
java
// 在RealConnection中
private void connectSocket(int connectTimeout, int readTimeout, Call call,
EventListener eventListener) throws IOException {
// 创建原始Socket
Socket rawSocket = socketFactory.createSocket();
// 连接到服务器
rawSocket.connect(address, connectTimeout);
// 设置超时
rawSocket.setSoTimeout(readTimeout);
// 获取输入输出流
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
}
Okio与Socket的关系图
┌───────────────────┐
│ 输入流 │
├───────────────────┤
│ Socket InputStream → Okio Source → BufferedSource
│
└───────────────────┘
┌───────────────────┐
│ 输出流 │
├───────────────────┤
│ Socket OutputStream → Okio Sink → BufferedSink
└───────────────────┘
7. 完整的WebSocket生命周期
1. 建立连接
java
// 创建OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
.pingInterval(30, TimeUnit.SECONDS) // 设置心跳间隔
.build();
// 创建请求
Request request = new Request.Builder()
.url("ws://example.com/websocket")
.build();
// 创建WebSocket监听器
WebSocketListener listener = new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
// 连接已建立
}
@Override
public void onMessage(WebSocket webSocket, String text) {
// 接收到文本消息
}
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
// 接收到二进制消息
}
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
// 连接正在关闭
webSocket.close(code, reason); // 确认关闭
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
// 连接已关闭
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
// 连接失败
}
};
// 建立WebSocket连接
WebSocket webSocket = client.newWebSocket(request, listener);
2. 内部连接流程
sequenceDiagram
participant App as 应用代码
participant Client as OkHttpClient
participant RWS as RealWebSocket
participant Server as 服务器
App->>Client: newWebSocket()
Client->>RWS: 创建RealWebSocket
RWS->>Server: HTTP升级请求
Note over Server: 处理请求
Server->>RWS: 101 Switching
Note over RWS: 创建读写器
Note over RWS: 启动读取线程
RWS->>App: onOpen回调
3. 发送和接收消息
java
// 发送文本消息
webSocket.send("Hello, WebSocket!");
// 发送二进制消息
webSocket.send(ByteString.of("binary data".getBytes()));
// 接收消息通过WebSocketListener回调
4. 心跳机制
OkHttp的WebSocket实现包含自动心跳机制,通过pingInterval配置:
java
// 设置30秒的心跳间隔
OkHttpClient client = new OkHttpClient.Builder()
.pingInterval(30, TimeUnit.SECONDS)
.build();
心跳流程:
sequenceDiagram
participant RWS as RealWebSocket
participant WSW as WebSocketWriter
participant Server as 服务器
Note over RWS: 定时器触发
RWS->>WSW: writePing()
WSW->>Server: PING帧
Note over Server: 处理PING
Server->>WSW: PONG帧
Note over RWS: 更新最后活动时间
5. 关闭连接
java
// 正常关闭
webSocket.close(1000, "Goodbye!");
// 异常关闭
webSocket.cancel();
关闭流程:
sequenceDiagram
participant RWS as RealWebSocket
participant WSW as WebSocketWriter
participant Server as 服务器
RWS->>WSW: writeClose()
WSW->>Server: CLOSE帧
Note over Server: 处理CLOSE
Server->>WSW: CLOSE帧
Note over RWS: 关闭连接
Note over RWS: onClosed回调
8. 总结
OkHttp的WebSocket实现是一个复杂而高效的系统,它通过以下关键组件协同工作:
- RealWebSocket:核心协调类,管理WebSocket的生命周期和消息队列
- WebSocketReader/Writer:处理WebSocket协议的编码和解码
- Okio:提供高效的I/O抽象,处理底层数据传输
- Socket:提供与网络的实际连接
数据流通过这些组件在不同层次之间传递:
- 应用层 ↔ 协议层 ↔ I/O抽象层 ↔ 网络层
这种分层设计使得OkHttp的WebSocket实现既高效又可靠,能够处理各种网络条件和边缘情况,同时为开发者提供简洁的API。