【安卓开发系列 -- APP 开源框架】网络请求框架 OKHTTP -- WebSocket
【1】基于OkHttp框架的WebSocket代码示例
// 构造OkHttp客户端
OkHttpClient client = new OkHttpClient.Builder().build();
// 构造Request对象
Request request = new Request.Builder()
.url(url)
.build();
// 建立连接
client.newWebSocket(request, new WebSocketListener() {
// 当远程对等方接受Web套接字并可能开始传输消息时调用
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
webSocket.send("发送消息");
}
// 当收到文本(类型{@code 0x1})消息时调用
@Override
public void onMessage(WebSocket webSocket, String text) {
super.onMessage(webSocket, text);
}
// 当收到二进制(类型为{@code 0x2})消息时调用
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
super.onMessage(webSocket, bytes);
}
// 当远程对等方指示不再有传入的消息将被传输时调用
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
super.onClosing(webSocket, code, reason);
}
// 当两个对等方都表示不再传输消息并且连接已成功释放时调用
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
super.onClosed(webSocket, code, reason);
}
// 由于从网络读取或向网络写入错误而关闭Web套接字时调用
// 传出和传入的消息都可能丢失
@Override
public void onFailure(WebSocket webSocket, Throwable t, @Nullable Response response) {
super.onFailure(webSocket, t, response);
}
});
【2】客户端与服务端建立连接
【2.1】新建WebSocket实例
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
...
final int pingInterval;
@Override public WebSocket newWebSocket(Request request, WebSocketListener listener) {
// 建立一个RealWebSocket实例
RealWebSocket webSocket = new RealWebSocket(request, listener, new Random(), pingInterval);
// 执行RealWebSocket实例的connect方法以建立连接
webSocket.connect(this);
return webSocket;
}
...
}
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
...
/** The application's original request unadulterated by web socket headers. */
// 应用程序的原始请求不受web套接字标头的影响
private final Request originalRequest;
// WebSocket监听器
final WebSocketListener listener;
// 生成随机数
private final Random random;
private final long pingIntervalMillis;
private final String key;
/** This runnable processes the outgoing queues. Call {@link #runWriter()} to after enqueueing. */
// 该可执行程序用于处理出站队列
private final Runnable writerRunnable;
// RealWebSocket的构造方法
public RealWebSocket(Request request, WebSocketListener listener, Random random,
long pingIntervalMillis) {
// 使用GET的方式进行握手
if (!"GET".equals(request.method())) {
throw new IllegalArgumentException("Request must be GET: " + request.method());
}
this.originalRequest = request;
this.listener = listener;
this.random = random;
this.pingIntervalMillis = pingIntervalMillis;
// 初始化key以备后续连接建立及握手使用
// Key是一个16字节长的随机数经过Base64编码得到
byte[] nonce = new byte[16];
random.nextBytes(nonce);
this.key = ByteString.of(nonce).base64();
// 初始化writerRunnable
this.writerRunnable = new Runnable() {
@Override public void run() {
try {
// writeOneFrame()尝试从队列中删除单个帧并发送该帧
// 优先发送紧急的PONGS
while (writeOneFrame()) {
}
} catch (IOException e) {
// 处理异常关闭WebSocket
failWebSocket(e, null);
}
}
};
}
...
}
【2.2】建立连接
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
...
// Upgrade和Connection向服务器表明,
// 请求的目的就是要将客户端和服务器端的通讯协议从HTTP协议升级到WebSocket协议,
// 同时在请求处理完成之后,连接不要断开;
//
// Sec-WebSocket-Key值正是创建RealWebSocket实例时生成的key,
// 其为WebSocket客户端发送的一个base64编码的密文,
// 要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept” 应答,
// 否则客户端会抛出“Error during WebSocket handshake”错误,并关闭连接;
//
// 当来自于HTTP服务器的响应到达时,便建立了连接;
public void connect(OkHttpClient client) {
// 创建OkHttpClient客户端实例
client = client.newBuilder()
.eventListener(EventListener.NONE)
.protocols(ONLY_HTTP1)
.build();
// 创建适用于WebSocket升级的HTTP请求
final Request request = originalRequest.newBuilder()
.header("Upgrade", "websocket")
.header("Connection", "Upgrade")
.header("Sec-WebSocket-Key", key)
.header("Sec-WebSocket-Version", "13")
.build();
// 创建一个OkHttpClient请求Call
call = Internal.instance.newWebSocketCall(client, request);
// WebSocket协议首先会通过发送一个http请求来完成一个握手的过程
// 客户端发送一个请求协议升级的get请求给服务端
// 服务端如果支持的话会返回状态码101,表示可以切换到对应的协议
//
// OKHTTP异步请求
call.enqueue(new Callback() {
@Override public void onResponse(Call call, Response response) {
try {
// 1. 检查HTTP响应
checkResponse(response);
} catch (ProtocolException e) {
failWebSocket(e, response);
closeQuietly(response);
return;
}
// Promote the HTTP streams into web socket streams.
// 将HTTP流升级到Web套接字流
StreamAllocation streamAllocation = Internal.instance.streamAllocation(call);
streamAllocation.noNewStreams(); // Prevent connection pooling!,阻止连接池
// 2. 初始化用于输入输出的Source和Sink
// Source和Sink创建于之前发送HTTP请求的时候,此处会阻止在这个连接上再创建新的流
Streams streams = streamAllocation.connection().newWebSocketStreams(streamAllocation);
// Process all web socket messages.
// 处理所有的WebSocket消息
try {
// 3. 调用回调onOpen()方法
listener.onOpen(RealWebSocket.this, response);
// 4. 初始化Reader和Writer
String name = "OkHttp WebSocket " + request.url().redact();
initReaderAndWriter(name, streams);
// 5. 配置socket的超时时间为0,即阻塞IO
streamAllocation.connection().socket().setSoTimeout(0);
// 6. 执行loopReader(),实际上进入了消息读取循环,即数据接收的逻辑
loopReader();
} catch (Exception e) {
failWebSocket(e, null);
}
}
@Override public void onFailure(Call call, IOException e) {
failWebSocket(e, null);
}
});
}
// 根据WebSocket协议,服务器端接受建立WebSocket连接的请求的判断标准
// 1. 响应码是101;
// 2. "Connection"的值为"Upgrade",表明服务器并没有在处理完请求之后把连接个断开;
// 3. "Upgrade"的值为"websocket",表明服务器接受使用WebSocket方式通信;
// 4. "Sec-WebSocket-Accept"的值为key+WebSocketProtocol.ACCEPT_MAGIC做SHA1 hash,
// 然后做base64编码,来做服务器接受连接的验证;
void checkResponse(Response response) throws ProtocolException {
// 响应码是101
if (response.code() != 101) {
throw new ProtocolException("Expected HTTP 101 response but was '"
+ response.code() + " " + response.message() + "'");
}
// "Connection"的值为"Upgrade",表明服务器并没有在处理完请求之后把连接个断开
String headerConnection = response.header("Connection");
if (!"Upgrade".equalsIgnoreCase(headerConnection)) {
throw new ProtocolException("Expected 'Connection' header value 'Upgrade' but was '"
+ headerConnection + "'");
}
// "Upgrade"的值为"websocket",表明服务器接受使用WebSocket方式通信
String headerUpgrade = response.header("Upgrade");
if (!"websocket".equalsIgnoreCase(headerUpgrade)) {
throw new ProtocolException(
"Expected 'Upgrade' header value 'websocket' but was '" + headerUpgrade + "'");
}
// "Sec-WebSocket-Accept"的值为key+WebSocketProtocol.ACCEPT_MAGIC做SHA1 hash,
// 然后做base64编码,来做服务器接受连接的验证;
String headerAccept = response.header("Sec-WebSocket-Accept");
String acceptExpected = ByteString.encodeUtf8(key + WebSocketProtocol.ACCEPT_MAGIC)
.sha1().base64();
if (!acceptExpected.equals(headerAccept)) {
throw new ProtocolException("Expected 'Sec-WebSocket-Accept' header value '"
+ acceptExpected + "' but was '" + headerAccept + "'");
}
}
// 初始化Reader和Writer
//
// 1. OkHttp使用WebSocketReader和WebSocketWriter处理数据的收发;
// 2. 在发送数据时将数据组织成帧,在接收数据时则进行反向操作,同时处理WebSocket的控制消息;
// 3. WebSocket的所有数据发送动作,都会在单线程线程池的线程中通过WebSocketWriter处理,
// 此处会创建ScheduledThreadPoolExecutor用于发送数据;
// 4. WebSocket协议中主要会传输两种类型的帧,
// 一是控制帧,主要是用于连接保活的Ping帧等;
// 二是用户数据载荷帧;
// 此处会根据用户的配置,调度PingRunnable周期性地发送Ping帧;
// 5. 在调用WebSocket的接口发送数据时,数据并不是同步发送的,而是被放在了一个消息队列中;
// 发送消息的Runnable从消息队列中读取数据并发送,
// 此处会检查消息队列中是否有数据,如果有的话,会调度发送消息的Runnable发送数据;
public void initReaderAndWriter(String name, Streams streams) throws IOException {
synchronized (this) {
this.streams = streams;
this.writer = new WebSocketWriter(streams.client, streams.sink, random);
this.executor = new ScheduledThreadPoolExecutor(1, Util.threadFactory(name, false));
if (pingIntervalMillis != 0) {
executor.scheduleAtFixedRate(
new PingRunnable(), pingIntervalMillis, pingIntervalMillis, MILLISECONDS);
}
if (!messageAndCloseQueue.isEmpty()) {
runWriter(); // Send messages that were enqueued before we were connected.
}
}
reader = new WebSocketReader(streams.client, streams.source, this);
}
/** 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();
}
}
...
}
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
...
static {
Internal.instance = new Internal() {
...
// 流分配方法
@Override public StreamAllocation streamAllocation(Call call) {
return ((RealCall) call).streamAllocation();
}
// 新建一个WebSocket请求
@Override public Call newWebSocketCall(OkHttpClient client, Request originalRequest) {
return RealCall.newRealCall(client, originalRequest, true);
}
}
}
...
}
public final class RealConnection extends Http2Connection.Listener implements Connection {
...
// Source和Sink创建于之前发送HTTP请求的时候,此处会阻止在这个连接上再创建新的流
public RealWebSocket.Streams newWebSocketStreams(final StreamAllocation streamAllocation) {
return new RealWebSocket.Streams(true, source, sink) {
@Override public void close() throws IOException {
streamAllocation.streamFinished(true, streamAllocation.codec(), -1L, null);
}
};
}
...
}
【3】WebSocket数据发送与接收
【3.1】WebSocket协议中帧的类型
1. PING帧,在PingRunnable中发送,用于连接保活,在初始化Reader和Writer的时候会根据设置调度执行或不执行;
2. PONG帧,在writeOneFrame()中发送,PONG帧是对服务器发过来的PING帧的响应,用于保活连接,PONG 帧具有最高的发送优先级;
3. CLOSE帧,在writeOneFrame()中发送,用于关闭连接;
4. MESSAGE帧,承载用户数据;
【3.2】WebSocket数据发送
【3.2.1】WebSocket数据发送 -- send 方法 / writerRunnable
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
...
// 通过WebSocket的send(String text)和send(ByteString bytes)分别发送文本的和二进制格式的消息;
// 调用发送数据的方法时,主要任务是构造消息并放进一个消息队列,然后调度writerRunnable执行;
// 当消息队列中的未发送数据超出最大大小限制,WebSocket连接会被直接关闭,
// 对于发送失败过或被关闭了的WebSocket将无法再发送信息;
@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) {
// Don't send new frames after we've failed or enqueued a close frame.
// 处于发送失败的WebSocket或者关闭帧入队的情况下不再发送
if (failed || enqueuedClose) return false;
// If this frame overflows the buffer, reject it and close the web socket.
// 如果该帧溢出缓冲区,则拒绝该帧并关闭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));
// 调度writerRunnable
runWriter();
return true;
}
private void runWriter() {
assert (Thread.holdsLock(this));
if (executor != null) {
executor.execute(writerRunnable);
}
}
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);
}
}
};
}
}
【3.2.2】WebSocket数据发送 -- writeOneFrame方法
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
...
/** The close code from the peer, or -1 if this web socket has not yet read a close frame. */
// 来自对等端的关闭代码,如果此web套接字尚未读取关闭帧则返回-1
private int receivedCloseCode = -1;
/** The close reason from the peer, or null if this web socket has not yet read a close frame. */
// 来自对等端的关闭原因,如果此web套接字尚未读取关闭帧则返回null
private String receivedCloseReason;
/** Null until this web socket is connected. Used for writes, pings, and close timeouts. */
// 周期性的线程池,该线程池在建立连接时创建,用于发送数据帧、PING帧、超时的CLOSE帧
private ScheduledExecutorService executor;
/**
* The streams held by this web socket. This is non-null until all incoming messages have been
* read and all outgoing messages have been written. It is closed when both reader and writer are
* exhausted, or if there is any failure.
*
* 此web套接字包含的流;
* 在读取所有传入消息和写入所有传出消息之前,此值为非空;
* 当写与读处理器已用尽或者出现故障时,流将关闭;
*/
private Streams streams;
// 在没有PONG帧需要发送时,writeOneFrame()从消息队列中取出一条消息,
// 如果消息不是CLOSE帧,发送过程如下
// 1. 队列中取出消息;
// 2. 创建一个BufferedSink用于数据发送;
// 3. 将数据写入先前创建的BufferedSink中;
// 4. 关闭BufferedSink;
// 5. 更新queueSize以正确地指示未发送数据的长度;
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) {
// websocket连接失败则跳出循环
// Failed web socket.
return false;
}
writer = this.writer;
// PONG队列中获取PONG消息
pong = pongQueue.poll();
// 判断是否有PONG消息
if (pong == null) {
// 在没有PONG消息时获取Message与Close帧
messageOrClose = messageAndCloseQueue.poll();
// 判断是否为Close帧
//
// 发送CLOSE帧分为主动关闭与被动关闭
// 已读取关闭帧的情况,在发送完CLOSE帧之后,连接被最终关闭,
// 因此发送CLOSE帧之前,会停掉发送消息用的executor并在发送之后通过onClosed()通知用户;
// 未读取关闭帧的情况,在发送前会调度CancelRunnable并且发送后不会通过onClosed()通知用户;
if (messageOrClose instanceof Close) {
// Close帧的处理
// 如果此Web套接字尚未读取关闭帧则receivedCloseCode为-1
receivedCloseCode = this.receivedCloseCode;
receivedCloseReason = this.receivedCloseReason;
if (receivedCloseCode != -1) {
// 被动关闭的发送
// 停止发送消息用的executor
// 已经读取到关闭帧则再向服务端发一个关闭帧
// 最后会通过onClosed()通知用户
streamsToClose = this.streams;
this.streams = null;
this.executor.shutdown();
} else {
// 主动关闭的发送
// 不会通过onClosed()通知用户
// 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) {
// 队列耗尽则退出
// The queue is exhausted.
return false;
}
}
}
try {
if (pong != null) {
// 将PONG帧不为空则发送PONG帧
writer.writePong(pong);
} else if (messageOrClose instanceof Message) {
// 发送MESSAGE帧
ByteString data = ((Message) messageOrClose).data;
// 此处将数据转化为可供websocket交互的格式
BufferedSink sink = Okio.buffer(writer.newMessageSink(
((Message) messageOrClose).formatOpcode, data.size()));
// 使用sink发送数据
sink.write(data);
// 关闭sink
sink.close();
synchronized (this) {
// 调整队列大小
queueSize -= data.size();
}
} else if (messageOrClose instanceof Close) {
// 发送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);
}
}
...
}
【3.2.3】WebSocket数据发送 -- newMessageSink方法 / FrameSink类
final class WebSocketWriter {
...
boolean activeWriter;
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状态
frameSink.formatOpcode = formatOpcode;
frameSink.contentLength = contentLength;
frameSink.isFirstFrame = true;
frameSink.closed = false;
return frameSink;
}
...
}
final class WebSocketWriter {
...
final class FrameSink implements Sink {
int formatOpcode;
long contentLength;
boolean isFirstFrame;
boolean closed;
// FrameSink的write()会先将数据写入一个Buffer中,然后再从该Buffer中读取数据并发送;
// 如果是第一次发送数据,同时剩余要发送的数据小于8192字节时,会延迟执行实际的数据发送,等close()时刷新;
// 在write()时,总是写入整个消息的所有数据,在FrameSink的write()中总是不会发送数据的(交给close);
@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.
// 确定这是否是一个缓冲写入,若是则可以推迟到close()时刷新;
// 判断标准,1.第一次发送数据;2.剩余待发送的数据小于8192字节;
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;
}
}
...
}
final class WebSocketWriter {
...
// WebSocket数据(帧)格式
//
// 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 ... |
// +---------------------------------------------------------------+
//
// 格式化并发送用户数据
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();
}
...
}
【3.3】WebSocket数据接收
【3.3.1】WebSocket数据接收 -- 循环读取数据
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
...
// 在握手的HTTP请求返回之后,会在HTTP请求的回调里,启动消息读取循环loopReader();
// 不断通过WebSocketReader的processNextFrame()方法读取消息,直到收到了关闭连接的消息;
// processNextFrame()方法先读取Header的两个字节,然后根据Header的信息,读取数据内容;
//
/** Receive frames until there are no more. Invoked only by the reader thread. */
// 接收WebSocket帧直到没有新的帧到达,仅仅由读线程触发
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();
}
}
...
}
【3.3.2】WebSocket数据接收 -- processNextFrame 方法
final class WebSocketReader {
...
boolean closed;
final BufferedSource source;
// Stateful data about the current frame
// 当前帧的状态信息
int opcode;
long frameLength;
boolean isFinalFrame;
boolean isControlFrame;
// maskKey = isClient ? null : new byte[4];
// 在WebSocketReader构造方法中初始化
private final byte[] maskKey;
void processNextFrame() throws IOException {
// 读取Header
readHeader();
if (isControlFrame) {
// 读取控制帧
readControlFrame();
} else {
// 读取消息帧
readMessageFrame();
}
}
...
}
【3.3.3】WebSocket数据接收 -- readHeader 方法
final class WebSocketReader {
...
// WebSocketReader从Header中,可以获取该帧是不是消息的最后一帧,
// 消息的类型,是否有掩码字节,保留位,帧的长度以及掩码字节等信息;
// WebSocket通过掩码位和掩码字节来区分数据是
// 从客户端发送给服务器的,还是服务器发送给客户端的;
// 若通过帧的Header确定了是数据帧,则会执行readMessageFrame()读取消息帧,
// 该方法会读取一条消息包含的所有数据帧;
private void readHeader() throws IOException {
if (closed) throw new IOException("closed");
// Disable the timeout to read the first byte of a new frame.
// 当读取新帧的第一个字节时,关闭超时
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;
// Control frames must be final frames (cannot contain continuations).
// 控制帧必须是最终帧(不能包含延续)
if (isControlFrame && !isFinalFrame) {
throw new ProtocolException("Control frames must be final.");
}
// 获取保留字段,目前OKHTTP不支持保留字段的扩展
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) {
// Reserved flags are for extensions which we currently do not support.
throw new ProtocolException("Reserved flags are unsupported.");
}
// 读取第二个字节
int b1 = source.readByte() & 0xff;
// 获取掩码标志位
// “MASK”即掩码标志位,表示帧内容是否使用异或操作(xor)做简单的加密,
// 目前的WebSocket标准规定,客户端发送数据必须使用掩码,而服务器发送则必须不使用掩码;
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.
// 获取帧长度,如果由特殊值指示则可选择从后续字节读取
// “Payload len”,表示帧内容的长度,是一种变长编码,最少7位,最多是7+64位,
// 即额外增加8个字节,所以一个WebSocket帧最大是2^64;
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);
}
}
...
}
【3.3.4】WebSocket数据接收 -- readMessageFrame 方法
final class WebSocketReader {
...
// readHeader()方法中初始化
// isFinalFrame = (b0 & B0_FLAG_FIN) != 0;
boolean isFinalFrame;
// 消息数据帧缓冲区
private final Buffer messageFrameBuffer = new Buffer();
// 读取信息帧方法
private void readMessageFrame() throws IOException {
int opcode = this.opcode;
// 消息的类型是字符串或二进制
if (opcode != OPCODE_TEXT && opcode != OPCODE_BINARY) {
throw new ProtocolException("Unknown opcode: " + toHexString(opcode));
}
// 读取消息
readMessage();
// 回调读取的信息
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();
}
}
// 在没有接续帧时结束读取操作
// We are exhausted and have no continuations.
if (isFinalFrame) break;
// Read headers and process any control frames until we reach a non-control frame
// 读取帧头信息并处理控制类型的帧直到遇到非控制类型的帧
readUntilNonControlFrame();
if (opcode != OPCODE_CONTINUATION) {
throw new ProtocolException("Expected continuation opcode. Got: " + toHexString(opcode));
}
}
}
...
}
【4】WebSocket连接保活
【4.1】WebSocket连接保活 -- 发送PING帧
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
...
/** True if this web socket failed and the listener has been notified. */
// 若WebSocket失败并且监听器通知了失败消息时取true
// 在failWebSocket方法中被设置为true
private boolean failed;
/** Total number of pings sent by this web socket. */
// 该WebSocket发送的PING帧的总次数
private int sentPingCount;
/** True if we have sent a ping that is still awaiting a reply. */
// 在发送了PING帧等待PONG帧时取值为true
private boolean awaitingPong;
1. @Override public WebSocket newWebSocket(Request request, WebSocketListener listener)
2. public void connect(OkHttpClient client)
3. public void initReaderAndWriter(String name, Streams streams) throws IOException {
...
// 开启周期性的向服务器发送PING帧任务
if (pingIntervalMillis != 0) {
// 周期性地执行PingRunnable任务,在PingRunnable中执行writePingFrame方法
executor.scheduleAtFixedRate(
new PingRunnable(), pingIntervalMillis, pingIntervalMillis, MILLISECONDS);
}
...
}
// PingRunnable类
private final class PingRunnable implements Runnable {
PingRunnable() {
}
@Override public void run() {
writePingFrame();
}
}
// 发送PING帧的方法
// PING帧是一个不包含载荷的控制帧,
// 其掩码位和掩码字节的设置与消息的数据帧相同,
// 即客户端发送的帧,设置掩码位,帧中包含掩码字节;
// 服务器发送的帧,不设置掩码位,帧中不包含掩码字节;
void writePingFrame() {
WebSocketWriter writer;
int failedPing;
synchronized (this) {
if (failed) return;
writer = this.writer;
failedPing = awaitingPong ? sentPingCount : -1;
sentPingCount++;
awaitingPong = true;
}
if (failedPing != -1) {
failWebSocket(new SocketTimeoutException("sent ping but didn't receive pong within "
+ pingIntervalMillis + "ms (after " + (failedPing - 1) + " successful ping/pongs)"),
null);
return;
}
try {
// 发送PING帧
writer.writePing(ByteString.EMPTY);
} catch (IOException e) {
failWebSocket(e, null);
}
}
...
}
final class WebSocketWriter {
/** Send a ping with the supplied {@code payload}. */
// 使用提供的有效负载发送PING帧
void writePing(ByteString payload) throws IOException {
// 构造并发送PING帧
writeControlFrame(OPCODE_CONTROL_PING, payload);
}
private void writeControlFrame(int opcode, ByteString payload) throws IOException {
if (writerClosed) throw new IOException("closed");
// 获取指定载荷的长度
int length = payload.size();
if (length > PAYLOAD_BYTE_MAX) {
throw new IllegalArgumentException(
"Payload size must be less than or equal to " + PAYLOAD_BYTE_MAX);
}
// 设置操作码,PING帧的操作码为9
int b0 = B0_FLAG_FIN | opcode;
sinkBuffer.writeByte(b0);
// 设置载荷长度
int b1 = length;
// 若是客户端则需要加入掩码标志位
if (isClient) {
b1 |= B1_FLAG_MASK;
sinkBuffer.writeByte(b1);
// 随机生成并写入MASK-KEY
random.nextBytes(maskKey);
sinkBuffer.write(maskKey);
// 写入载荷
if (length > 0) {
long payloadStart = sinkBuffer.size();
sinkBuffer.write(payload);
sinkBuffer.readAndWriteUnsafe(maskCursor);
maskCursor.seek(payloadStart);
toggleMask(maskCursor, maskKey);
maskCursor.close();
}
} else {
// 非客户端的情况
sinkBuffer.writeByte(b1);
sinkBuffer.write(payload);
}
sink.flush();
}
}
【4.2】WebSocket连接保活 -- 接收PING帧并发送PONG帧
final class WebSocketReader {
...
// readHeader()中初始化
long frameLength;
private void readControlFrame() throws IOException {
if (frameLength > 0) {
// 读取帧
source.readFully(controlFrameBuffer, frameLength);
// 若不是客户端则需要对读取的帧解除MASK
if (!isClient) {
controlFrameBuffer.readAndWriteUnsafe(maskCursor);
maskCursor.seek(0);
toggleMask(maskCursor, maskKey);
maskCursor.close();
}
}
switch (opcode) {
case OPCODE_CONTROL_PING:
// 读取到PING帧时回调
frameCallback.onReadPing(controlFrameBuffer.readByteString());
break;
case OPCODE_CONTROL_PONG:
// 读取到PONG帧时回调
frameCallback.onReadPong(controlFrameBuffer.readByteString());
break;
case OPCODE_CONTROL_CLOSE:
...
break;
default:
throw new ProtocolException("Unknown control opcode: " + toHexString(opcode));
}
}
...
}
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
...
// 接收到PING帧时的回调
@Override public synchronized void onReadPing(ByteString payload) {
// Don't respond to pings after we've failed or sent the close frame.
// 当WebSocket失败或发送了Close帧之后便不发送响应
if (failed || (enqueuedClose && messageAndCloseQueue.isEmpty())) return;
// 向PONG帧队列中加入载荷
pongQueue.add(payload);
// 运行writerRunnable发送PONG帧
runWriter();
// 记录
receivedPingCount++;
}
// 接收到PONG帧时的回调
@Override public synchronized void onReadPong(ByteString buffer) {
// This API doesn't expose pings.
// 对于PONG帧不做响应只是做简单的记录并恢复awaitingPong标记
receivedPongCount++;
awaitingPong = false;
}
...
}
final class WebSocketWriter {
...
/** Send a pong with the supplied {@code payload}. */
// 发送指定载荷的PONG帧
void writePong(ByteString payload) throws IOException {
// 构造控制帧,PONG的操作码是10
writeControlFrame(OPCODE_CONTROL_PONG, payload);
}
...
}
【5】WebSocket关闭
【5.1】WebSocket关闭并发送CLOSE帧
【5.1.1】WebSocket关闭并发送CLOSE帧 -- close 方法
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
...
@Override public boolean close(int code, String reason) {
return close(code, reason, CANCEL_AFTER_CLOSE_MILLIS);
}
synchronized boolean close(int code, String reason, long cancelAfterCloseMillis) {
// 验证关闭操作码
validateCloseCode(code);
// 关闭理由相关的缓冲区
ByteString reasonBytes = null;
if (reason != null) {
reasonBytes = ByteString.encodeUtf8(reason);
if (reasonBytes.size() > CLOSE_MESSAGE_MAX) {
throw new IllegalArgumentException("reason.size() > " + CLOSE_MESSAGE_MAX + ": " + reason);
}
}
if (failed || enqueuedClose) return false;
// Immediately prevent further frames from being enqueued.
// 设置enqueuedClose标志,防止后续的帧入队
enqueuedClose = true;
// Enqueue the close frame.
// CLOSE帧入队
messageAndCloseQueue.add(new Close(code, reasonBytes, cancelAfterCloseMillis));
// 调用writerRunnable发送CLOSE帧
// 主动关闭时会将CancelRunnable调度到线程池运行
//
// 发送CLOSE帧分为主动关闭与被动关闭
// 已读取关闭帧的情况,在发送完CLOSE帧之后,连接被最终关闭,
// 因此发送CLOSE帧之前,会停掉发送消息用的executor并在发送之后通过onClosed()通知用户;
// 未读取关闭帧的情况,在发送前会调度CancelRunnable并且发送后不会通过onClosed()通知用户;
runWriter();
return true;
}
...
}
【5.1.2】WebSocket关闭并发送CLOSE帧 -- CancelRunnable调度
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
...
/** Non-null for client web sockets. These can be canceled. */
// OKHTTP的请求Call
private Call call;
final class CancelRunnable implements Runnable {
@Override public void run() {
// 执行关闭操作
cancel();
}
}
@Override public void cancel() {
call.cancel();
}
...
}
final class RealCall implements Call {
...
final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;
@Override public void cancel() {
// 调用RetryAndFollowUpInterceptor拦截器的cancel()方法
retryAndFollowUpInterceptor.cancel();
}
...
}
public final class RetryAndFollowUpInterceptor implements Interceptor {
...
private volatile boolean canceled;
private volatile StreamAllocation streamAllocation;
/**
* Immediately closes the socket connection if it's currently held. Use this to interrupt an
* in-flight request from any thread. It's the caller's responsibility to close the request body
* and response body streams; otherwise resources may be leaked.
*
* <p>This method is safe to be called concurrently, but provides limited guarantees. If a
* transport layer connection has been established (such as a HTTP/2 stream) that is terminated.
* Otherwise if a socket connection is being established, that is terminated.
*
* 如果当前持有连接则立即关闭套接字连接
* 使用它来中断来自任何线程的正在进行的请求
* 关闭请求主体和响应主体流是调用者的责任,否则资源可能会泄露
*
*/
public void cancel() {
canceled = true;
StreamAllocation streamAllocation = this.streamAllocation;
if (streamAllocation != null) streamAllocation.cancel();
}
...
}
public final class StreamAllocation {
...
private HttpCodec codec;
private RealConnection connection;
public void cancel() {
HttpCodec codecToCancel;
RealConnection connectionToCancel;
synchronized (connectionPool) {
canceled = true;
codecToCancel = codec;
connectionToCancel = connection;
}
if (codecToCancel != null) {
codecToCancel.cancel();
} else if (connectionToCancel != null) {
connectionToCancel.cancel();
}
}
...
}
【5.1.3】WebSocket关闭并发送CLOSE帧 -- 发送CLOSE帧
final class WebSocketWriter {
...
// 发送CLOSE帧
void writeClose(int code, ByteString reason) throws IOException {
ByteString payload = ByteString.EMPTY;
if (code != 0 || reason != null) {
if (code != 0) {
// 验证关闭操作码
validateCloseCode(code);
}
Buffer buffer = new Buffer();
// 写入关闭操作到缓冲区
buffer.writeShort(code);
if (reason != null) {
// 写入关闭理由
buffer.write(reason);
}
// 获取关闭帧载荷
payload = buffer.readByteString();
}
try {
// 构造并发送关闭帧
writeControlFrame(OPCODE_CONTROL_CLOSE, payload);
} finally {
writerClosed = true;
}
}
...
}
【5.2】WebSocket关闭接收CLOSE帧
final class WebSocketReader {
...
private void readControlFrame() throws IOException {
// 读取帧并解除MASK
if (frameLength > 0) {
source.readFully(controlFrameBuffer, frameLength);
if (!isClient) {
controlFrameBuffer.readAndWriteUnsafe(maskCursor);
maskCursor.seek(0);
toggleMask(maskCursor, maskKey);
maskCursor.close();
}
}
switch (opcode) {
...
case OPCODE_CONTROL_CLOSE:
int code = CLOSE_NO_STATUS_CODE;
String reason = "";
long bufferSize = controlFrameBuffer.size();
if (bufferSize == 1) {
throw new ProtocolException("Malformed close payload length of 1.");
} else if (bufferSize != 0) {
// 读取CLOSE帧的操作码与理由
code = controlFrameBuffer.readShort();
reason = controlFrameBuffer.readUtf8();
String codeExceptionMessage = WebSocketProtocol.closeCodeExceptionMessage(code);
if (codeExceptionMessage != null) throw new ProtocolException(codeExceptionMessage);
}
// 回调CLOSE帧的操作码与理由
frameCallback.onReadClose(code, reason);
closed = true;
break;
default:
throw new ProtocolException("Unknown control opcode: " + toHexString(opcode));
}
}
...
}
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
...
/** True if we've enqueued a close frame. No further message frames will be enqueued. */
// 当CLOSE帧入队后置为true,以防止此后有新的帧入队
private boolean enqueuedClose;
@Override public void onReadClose(int code, String reason) {
if (code == -1) throw new IllegalArgumentException();
Streams toClose = null;
synchronized (this) {
if (receivedCloseCode != -1) throw new IllegalStateException("already closed");
// 更新receivedCloseCode与receivedCloseReason
receivedCloseCode = code;
receivedCloseReason = reason;
// CLOSE帧入队并且消息队列为空时则可以关闭
if (enqueuedClose && messageAndCloseQueue.isEmpty()) {
toClose = this.streams;
this.streams = null;
if (cancelFuture != null) cancelFuture.cancel(false);
this.executor.shutdown();
}
}
try {
// 回调关闭事件信息
listener.onClosing(this, code, reason);
if (toClose != null) {
listener.onClosed(this, code, reason);
}
} finally {
// 关闭流,释放资源
closeQuietly(toClose);
}
}
...
}