okhttp之旅(十三)--websocket数据发送与接收

系统学习详见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

你可能感兴趣的:(okhttp之旅(十三)--websocket数据发送与接收)