【安卓开发系列 -- 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);
        }
    }    

    ...

}