系统学习详见OKhttp源码解析详解系列
1 数据发送
1.1 四种类型帧
在 WebSocket 协议中,客户端需要发送 四种类型 的帧:
1.PING 帧
PING帧用于连接保活,它的发送是在 PingRunnable 中执行的,在初始化 Reader 和 Writer 的时候,就会根据设置调度执行或不执行。
除PING 帧外的其它 三种 帧,都在 writeOneFrame() 中发送。
2.PONG 帧
PONG 帧是对服务器发过来的 PING 帧的响应,同样用于保活连接。
PONG 帧具有最高的发送优先级
3.CLOSE 帧
CLOSE 帧用于关闭连接
4.MESSAGE 帧
1.2 send()
- 通过 WebSocket 接口的 send(String text) 和 send(ByteString bytes) 分别发送文本的和二进制格式的消息。
- 调用发送数据的接口时,做的事情主要是构造消息,放进一个消息队列,然后调度 writerRunnable 执行。
- 当消息队列中的未发送数据超出最大大小限制,WebSocket 连接会被直接关闭。对于发送失败过或被关闭了的 WebSocket,将无法再发送信息。
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
@Override
public boolean send(String text) {
if (text == null) throw new NullPointerException("text == null");
return send(ByteString.encodeUtf8(text), OPCODE_TEXT);
}
@Override
public boolean send(ByteString bytes) {
if (bytes == null) throw new NullPointerException("bytes == null");
return send(bytes, OPCODE_BINARY);
}
private synchronized boolean send(ByteString data, int formatOpcode) {
//对于发送失败过或被关闭了的 WebSocket,将无法再发送信息。
// Don't send new frames after we've failed or enqueued a close frame.
if (failed || enqueuedClose) return false;
//当消息队列中的未发送数据超出最大大小限制,WebSocket 连接会被直接关闭。
if (queueSize + data.size() > MAX_QUEUE_SIZE) {
close(CLOSE_CLIENT_GOING_AWAY, null);
return false;
}
// Enqueue the message frame.
queueSize += data.size();
messageAndCloseQueue.add(new Message(formatOpcode, data));
runWriter();
return true;
}
private void runWriter() {
assert (Thread.holdsLock(this));
if (executor != null) {
executor.execute(writerRunnable);
}
}
}
1.3 writerRunnable
在 writerRunnable 中会循环调用 writeOneFrame() 逐帧发送数据,直到数据发完,或发送失败。
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
public RealWebSocket(Request request, WebSocketListener listener, Random random,
long pingIntervalMillis) {
...
this.writerRunnable = new Runnable() {
@Override
public void run() {
try {
while (writeOneFrame()) {
}
} catch (IOException e) {
failWebSocket(e, null);
}
}
};
}
}
1.4 writeOneFrame()
- 在没有PONG 帧需要发送时,writeOneFrame() 从消息队列中取出一条消息,如果消息不是 CLOSE 帧,则主要通过如下的过程进行发送:
- 1.队列中取出消息
- 2.创建一个 BufferedSink 用于数据发送。
- 3.将数据写入前面创建的 BufferedSink 中。
- 4.关闭 BufferedSink。
- 5.更新 queueSize 以正确地指示未发送数据的长度。
PONG 帧具有最高的发送优先级
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
boolean writeOneFrame() throws IOException {
WebSocketWriter writer;
ByteString pong;
Object messageOrClose = null;
int receivedCloseCode = -1;
String receivedCloseReason = null;
Streams streamsToClose = null;
synchronized (RealWebSocket.this) {
if (failed) {
return false; // websocket连接失败,跳出循环
}
writer = this.writer;
pong = pongQueue.poll();
//判断是否有pong消息
if (pong == null) {
messageOrClose = messageAndCloseQueue.poll();
//判断是否为关闭帧
if (messageOrClose instanceof Close) {
receivedCloseCode = this.receivedCloseCode;
receivedCloseReason = this.receivedCloseReason;
if (receivedCloseCode != -1) {
streamsToClose = this.streams;
this.streams = null;
this.executor.shutdown();
} else {
// When we request a graceful close also schedule a cancel of the websocket.
cancelFuture = executor.schedule(new CancelRunnable(),
((Close) messageOrClose).cancelAfterCloseMillis, MILLISECONDS);
}
} else if (messageOrClose == null) {
return false; //消息队列为空,跳出循环
}
}
}
try {
if (pong != null) {
writer.writePong(pong);
} else if (messageOrClose instanceof Message) {
ByteString data = ((Message) messageOrClose).data;
//这里将数据转化为可供websocket交互的格式
BufferedSink sink = Okio.buffer(writer.newMessageSink(
((Message) messageOrClose).formatOpcode, data.size()));
sink.write(data);
sink.close();
synchronized (this) {
queueSize -= data.size();
}
} else if (messageOrClose instanceof Close) {
Close close = (Close) messageOrClose;
writer.writeClose(close.code, close.reason);
// We closed the writer: now both reader and writer are closed.
if (streamsToClose != null) {
listener.onClosed(this, receivedCloseCode, receivedCloseReason);
}
} else {
throw new AssertionError();
}
return true;
} finally {
//释放资源
closeQuietly(streamsToClose);
}
}
}
1.5 数据格式化及发送
if (messageOrClose instanceof Message) {
ByteString data = ((Message) messageOrClose).data;
//这里将数据转化为可供websocket交互的格式
BufferedSink sink = Okio.buffer(writer.newMessageSink(
((Message) messageOrClose).formatOpcode, data.size()));
sink.write(data);
sink.close();
synchronized (this) {
queueSize -= data.size();
}
}
newMessageSink-返回的是FrameSink
final class WebSocketWriter {
final FrameSink frameSink = new FrameSink();
Sink newMessageSink(int formatOpcode, long contentLength) {
if (activeWriter) {
throw new IllegalStateException("Another message writer is active. Did you call close()?");
}
activeWriter = true;
// Reset FrameSink state for a new writer.
frameSink.formatOpcode = formatOpcode;
frameSink.contentLength = contentLength;
frameSink.isFirstFrame = true;
frameSink.closed = false;
return frameSink;
}
}
sink.write(data)
- FrameSink 的 write() 会先将数据写如一个 Buffer 中,然后再从这个 Buffer 中读取数据来发送。
- 如果是第一次发送数据,同时剩余要发送的数据小于 8192 字节时,会延迟执行实际的数据发送,等 close() 时刷新。
- 在 write() 时,总是写入整个消息的所有数据,在 FrameSink 的 write() 中总是不会发送数据的(交给close)。
final class WebSocketWriter {
final class FrameSink implements Sink {
@Override public void write(Buffer source, long byteCount) throws IOException {
if (closed) throw new IOException("closed");
buffer.write(source, byteCount);
// Determine if this is a buffered write which we can defer until close() flushes.
boolean deferWrite = isFirstFrame
&& contentLength != -1
&& buffer.size() > contentLength - 8192 /* segment size */;
long emitCount = buffer.completeSegmentByteCount();
if (emitCount > 0 && !deferWrite) {
writeMessageFrame(formatOpcode, emitCount, isFirstFrame, false /* final */);
isFirstFrame = false;
}
}
@Override public void flush() throws IOException {
if (closed) throw new IOException("closed");
writeMessageFrame(formatOpcode, buffer.size(), isFirstFrame, false /* final */);
isFirstFrame = false;
}
@Override public Timeout timeout() {
return sink.timeout();
}
@SuppressWarnings("PointlessBitwiseExpression")
@Override public void close() throws IOException {
if (closed) throw new IOException("closed");
writeMessageFrame(formatOpcode, buffer.size(), isFirstFrame, true /* final */);
closed = true;
activeWriter = false;
}
}
}
writeMessageFrame()将用户数据格式化并发送出去。
final class WebSocketWriter {
void writeMessageFrame(int formatOpcode, long byteCount, boolean isFirstFrame,
boolean isFinal) throws IOException {
if (writerClosed) throw new IOException("closed");
int b0 = isFirstFrame ? formatOpcode : OPCODE_CONTINUATION;
if (isFinal) {
b0 |= B0_FLAG_FIN;
}
sinkBuffer.writeByte(b0);
int b1 = 0;
if (isClient) {
b1 |= B1_FLAG_MASK;
}
if (byteCount <= PAYLOAD_BYTE_MAX) {
b1 |= (int) byteCount;
sinkBuffer.writeByte(b1);
} else if (byteCount <= PAYLOAD_SHORT_MAX) {
b1 |= PAYLOAD_SHORT;
sinkBuffer.writeByte(b1);
sinkBuffer.writeShort((int) byteCount);
} else {
b1 |= PAYLOAD_LONG;
sinkBuffer.writeByte(b1);
sinkBuffer.writeLong(byteCount);
}
if (isClient) {
random.nextBytes(maskKey);
sinkBuffer.write(maskKey);
if (byteCount > 0) {
long bufferStart = sinkBuffer.size();
sinkBuffer.write(buffer, byteCount);
sinkBuffer.readAndWriteUnsafe(maskCursor);
maskCursor.seek(bufferStart);
toggleMask(maskCursor, maskKey);
maskCursor.close();
}
} else {
sinkBuffer.write(buffer, byteCount);
}
sink.emit();
}
}
规范中定义的数据格式如下:
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 ... |
+---------------------------------------------------------------+
2 数据接收
2.1 读取内容
- 在握手的HTTP请求返回之后,会在HTTP请求的回调里,启动消息读取循环 loopReader():
- 不断通过 WebSocketReader 的 processNextFrame() 读取消息,直到收到了关闭连接的消息。
- processNextFrame() 先读取 Header 的两个字节,然后根据 Header 的信息,读取数据内容。
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
/**
* Receive frames until there are no more. Invoked only by the reader thread.
* 接收帧,直到没有更多。 仅由读者线程调用。
*/
public void loopReader() throws IOException {
while (receivedCloseCode == -1) {
// This method call results in one or more onRead* methods being called on this thread.
//此方法调用将导致在此线程上调用一个或多个onRead *方法
reader.processNextFrame();
}
}
}
final class WebSocketReader {
void processNextFrame() throws IOException {
//先读取Header
readHeader();
//是控制帧要先读取
if (isControlFrame) {
readControlFrame();
} else {
readMessageFrame();
}
}
}
2.2 读取header
- WebSocketReader 从 Header 中,获取到这个帧是不是消息的最后一帧,消息的类型,是否有掩码字节,保留位,帧的长度,以及掩码字节等信息。
- WebSocket 通过掩码位和掩码字节来区分数据是从客户端发送给服务器的,还是服务器发送给客户端的。
通过帧的 Header 确定了是数据帧,则会执行 readMessageFrame() 读取消息帧:会读取一条消息包含的所有数据帧。
final class WebSocketReader {
private void readHeader() throws IOException {
if (closed) throw new IOException("closed");
//读的第一个字节是同步的不计超时时间的
int b0;
long timeoutBefore = source.timeout().timeoutNanos();
source.timeout().clearTimeout();
try {
b0 = source.readByte() & 0xff;
} finally {
source.timeout().timeout(timeoutBefore, TimeUnit.NANOSECONDS);
}
opcode = b0 & B0_MASK_OPCODE;
isFinalFrame = (b0 & B0_FLAG_FIN) != 0;
isControlFrame = (b0 & OPCODE_FLAG_CONTROL) != 0;
// 控制帧必须是最终帧(不能包含延续)。
if (isControlFrame && !isFinalFrame) {
throw new ProtocolException("Control frames must be final.");
}
boolean reservedFlag1 = (b0 & B0_FLAG_RSV1) != 0;
boolean reservedFlag2 = (b0 & B0_FLAG_RSV2) != 0;
boolean reservedFlag3 = (b0 & B0_FLAG_RSV3) != 0;
if (reservedFlag1 || reservedFlag2 || reservedFlag3) {
// 保留标志用于我们目前不支持的扩展。
throw new ProtocolException("Reserved flags are unsupported.");
}
int b1 = source.readByte() & 0xff;
boolean isMasked = (b1 & B1_FLAG_MASK) != 0;
if (isMasked == isClient) {
// Masked payloads must be read on the server. Unmasked payloads must be read on the client.
throw new ProtocolException(isClient
? "Server-sent frames must not be masked."
: "Client-sent frames must be masked.");
}
// Get frame length, optionally reading from follow-up bytes if indicated by special values.
//获取帧长度,如果由特殊值指示,则可选择从后续字节读取。
frameLength = b1 & B1_MASK_LENGTH;
if (frameLength == PAYLOAD_SHORT) {
frameLength = source.readShort() & 0xffffL; // Value is unsigned.
} else if (frameLength == PAYLOAD_LONG) {
frameLength = source.readLong();
if (frameLength < 0) {
throw new ProtocolException(
"Frame length 0x" + Long.toHexString(frameLength) + " > 0x7FFFFFFFFFFFFFFF");
}
}
if (isControlFrame && frameLength > PAYLOAD_BYTE_MAX) {
throw new ProtocolException("Control frame must be less than " + PAYLOAD_BYTE_MAX + "B.");
}
if (isMasked) {
// Read the masking key as bytes so that they can be used directly for unmasking.
//以字节读取屏蔽键,以便它们可以直接用于取消屏蔽。
source.readFully(maskKey);
}
}
}
2.3 readMessageFrame() 读取消息帧并回调 FrameCallback 将读取的内容通知出去
- 按照 WebSocket 的标准,包含用户数据的消息数据帧可以和控制帧交替发送;但消息之间的数据帧不可以。因而在这个过程中,若遇到了控制帧,则会先读取控制帧进行处理,然后继续读取消息的数据帧,直到读取了消息的所有数据帧。
final class WebSocketReader {
private void readMessageFrame() throws IOException {
int opcode = this.opcode;
if (opcode != OPCODE_TEXT && opcode != OPCODE_BINARY) {
throw new ProtocolException("Unknown opcode: " + toHexString(opcode));
}
readMessage();
//回调 FrameCallback 将读取的内容通知出去
if (opcode == OPCODE_TEXT) {
frameCallback.onReadMessage(messageFrameBuffer.readUtf8());
} else {
frameCallback.onReadMessage(messageFrameBuffer.readByteString());
}
}
/**
* Reads a message body into across one or more frames. Control frames that occur between
* fragments will be processed. If the message payload is masked this will unmask as it's being
* processed.
*
* 通过一个或多个帧读取消息正文。 处理片段之间发生的控制帧。
* 如果消息有效负载被屏蔽,则在处理它时将取消屏蔽。
*/
private void readMessage() throws IOException {
while (true) {
if (closed) throw new IOException("closed");
if (frameLength > 0) {
source.readFully(messageFrameBuffer, frameLength);
if (!isClient) {
messageFrameBuffer.readAndWriteUnsafe(maskCursor);
maskCursor.seek(messageFrameBuffer.size() - frameLength);
toggleMask(maskCursor, maskKey);
maskCursor.close();
}
}
if (isFinalFrame) break; // We are exhausted and have no continuations.
readUntilNonControlFrame();
if (opcode != OPCODE_CONTINUATION) {
throw new ProtocolException("Expected continuation opcode. Got: " + toHexString(opcode));
}
}
}
}
参考
https://www.jianshu.com/p/13ceb541ade9