OkHttp WebSocket 实现详解

224 阅读6分钟

OkHttp WebSocket 实现详解

1. WebSocket 概述

WebSocket 是一种在单个 TCP 连接上提供全双工通信的协议,它在客户端和服务器之间建立持久连接,允许双向数据传输。与传统的 HTTP 请求-响应模式不同,WebSocket 连接建立后,客户端和服务器可以随时向对方发送数据,无需等待对方的请求。

WebSocket 协议由 RFC 6455 定义,主要特点包括:

  • 基于 TCP 的全双工通信
  • 低延迟通信
  • 支持文本和二进制数据传输
  • 内置心跳机制保持连接活跃

2. OkHttp WebSocket 架构

OkHttp 提供了对 WebSocket 协议的完整实现,其架构设计清晰,主要包括以下组件:

2.1 核心接口和类

  • WebSocket:定义 WebSocket 客户端的基本操作接口
  • WebSocketListener:定义 WebSocket 事件的回调接口
  • RealWebSocket:WebSocket 接口的具体实现
  • WebSocketReader:负责读取和解析 WebSocket 帧
  • WebSocketWriter:负责构造和发送 WebSocket 帧
  • WebSocketProtocol:定义 WebSocket 协议的常量和工具方法
  • WebSocketExtensions:处理 WebSocket 扩展,如压缩

2.2 架构图

┌─────────────┐      ┌───────────────┐      ┌─────────────────┐
│ Application │◄────►│   WebSocket   │◄────►│ WebSocketListener│
└─────────────┘      └───────┬───────┘      └─────────────────┘
                             │
                     ┌───────▼───────┐
                     │ RealWebSocket │
                     └───────┬───────┘
                             │
              ┌──────────────┴──────────────┐
              │                             │
      ┌───────▼───────┐           ┌────────▼────────┐
      │WebSocketReader│           │ WebSocketWriter │
      └───────────────┘           └─────────────────┘

2.3 组件关系图

classDiagram
    class WebSocket {
        <<interface>>
        +send(text: String): Boolean
        +send(bytes: ByteString): Boolean
        +close(code: Int, reason: String?): Boolean
        +cancel()
        +queueSize: Long
        +request(): Request
    }

    class WebSocketListener {
        <<abstract>>
        +onOpen(webSocket: WebSocket, response: Response)
        +onMessage(webSocket: WebSocket, text: String)
        +onMessage(webSocket: WebSocket, bytes: ByteString)
        +onClosing(webSocket: WebSocket, code: Int, reason: String)
        +onClosed(webSocket: WebSocket, code: Int, reason: String)
        +onFailure(webSocket: WebSocket, t: Throwable, response: Response?)
    }

    class RealWebSocket {
        -writer: WebSocketWriter
        -reader: WebSocketReader
        -messageQueue: Queue
        -listener: WebSocketListener
        +connect(client: OkHttpClient)
        -processNextFrame()
        -writePingFrame()
        -writeCloseFrame()
    }

    class WebSocketReader {
        +nextFrame()
        -readHeader()
        -readControlFrame()
        -readMessageFrame()
    }

    class WebSocketWriter {
        +writeMessageFrame(opcode: Int, payload: ByteString)
        +writePing(payload: ByteString)
        +writePong(payload: ByteString)
        +writeClose(code: Int, reason: ByteString)
    }

    class WebSocketProtocol {
        <<static>>
        +OPCODE_CONTINUATION: Int
        +OPCODE_TEXT: Int
        +OPCODE_BINARY: Int
        +OPCODE_CONTROL_CLOSE: Int
        +OPCODE_CONTROL_PING: Int
        +OPCODE_CONTROL_PONG: Int
        +validateCloseCode(code: Int): Boolean
    }

    class WebSocketExtensions {
        +perMessageDeflate: Boolean
        +clientMaxWindowBits: Int
        +clientNoContextTakeover: Boolean
        +serverMaxWindowBits: Int
        +serverNoContextTakeover: Boolean
        +parse(extensionsHeader: String): WebSocketExtensions
    }

    class MessageDeflater {
        +deflate(data: ByteString): ByteString
        +close()
    }

    class MessageInflater {
        +inflate(data: ByteString): ByteString
        +close()
    }

    WebSocket <|.. RealWebSocket
    RealWebSocket --> WebSocketListener
    RealWebSocket --> WebSocketReader
    RealWebSocket --> WebSocketWriter
    RealWebSocket --> WebSocketProtocol
    WebSocketReader --> WebSocketProtocol
    WebSocketWriter --> WebSocketProtocol
    RealWebSocket --> WebSocketExtensions
    WebSocketExtensions --> MessageDeflater
    WebSocketExtensions --> MessageInflater

3. 核心接口和类详解

3.1 WebSocket 接口

WebSocket 接口定义了客户端可以执行的基本操作:

interface WebSocket {
  // 发送文本消息
  fun send(text: String): Boolean

  // 发送二进制消息
  fun send(bytes: ByteString): Boolean

  // 关闭连接
  fun close(code: Int, reason: String?): Boolean

  // 取消连接
  fun cancel()

  // 查询连接状态
  val queueSize: Long

  // 请求标签
  fun request(): Request
}

3.2 WebSocketListener 接口

WebSocketListener 定义了 WebSocket 生命周期的回调方法:

abstract class WebSocketListener {
  // 连接打开时调用
  open fun onOpen(webSocket: WebSocket, response: Response) {}

  // 接收到文本消息时调用
  open fun onMessage(webSocket: WebSocket, text: String) {}

  // 接收到二进制消息时调用
  open fun onMessage(webSocket: WebSocket, bytes: ByteString) {}

  // 连接关闭时调用
  open fun onClosing(webSocket: WebSocket, code: Int, reason: String) {}

  // 连接已关闭时调用
  open fun onClosed(webSocket: WebSocket, code: Int, reason: String) {}

  // 发生错误时调用
  open fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {}
}

3.3 RealWebSocket 类

RealWebSocket 是 WebSocket 接口的具体实现,负责管理 WebSocket 连接的整个生命周期:

  • 连接建立和握手
  • 消息的发送和接收
  • 心跳机制
  • 连接关闭
  • 错误处理

RealWebSocket 内部维护了一个消息队列,用于缓存待发送的消息,并通过 WebSocketWriter 发送消息。

3.4 WebSocketReader 和 WebSocketWriter

这两个类负责 WebSocket 帧的读写操作:

  • WebSocketReader:从底层 Socket 读取数据,解析 WebSocket 帧,并调用相应的回调方法
  • WebSocketWriter:将消息编码为 WebSocket 帧,并写入底层 Socket

4. WebSocket 连接建立流程

OkHttp 中 WebSocket 连接的建立过程如下:

  1. 客户端创建 WebSocket.Factory(通常是 OkHttpClient)
  2. 客户端调用 webSocket(Request, WebSocketListener) 方法
  3. OkHttpClient 创建 RealWebSocket 实例
  4. RealWebSocket 发起 HTTP 握手请求,包含必要的 WebSocket 升级头
  5. 服务器响应 101 状态码,表示协议切换成功
  6. RealWebSocket 创建 WebSocketReader 和 WebSocketWriter
  7. 调用 WebSocketListener 的 onOpen 方法
  8. 开始消息读取循环
// 创建 WebSocket 连接
val client = OkHttpClient()
val request = Request.Builder()
    .url("ws://example.com/ws")
    .build()

client.newWebSocket(request, object : WebSocketListener() {
    override fun onOpen(webSocket: WebSocket, response: Response) {
        // 连接已建立
    }
})

4.1 连接建立流程图

sequenceDiagram
    participant App as 应用程序
    participant Client as OkHttpClient
    participant RWS as RealWebSocket
    participant Server as WebSocket服务器
    participant Reader as WebSocketReader
    participant Writer as WebSocketWriter
    participant Listener as WebSocketListener

    App->>Client: newWebSocket(request, listener)
    Client->>RWS: 创建RealWebSocket实例
    RWS->>Server: 发送HTTP握手请求(包含Upgrade头)
    Note over RWS,Server: GET /path HTTP/1.1<br>Upgrade: websocket<br>Connection: Upgrade<br>Sec-WebSocket-Key: [base64key]<br>Sec-WebSocket-Version: 13
    Server->>RWS: 返回101响应(协议切换)
    Note over Server,RWS: HTTP/1.1 101 Switching Protocols<br>Upgrade: websocket<br>Connection: Upgrade<br>Sec-WebSocket-Accept: [base64key]
    RWS->>Writer: 创建WebSocketWriter
    RWS->>Reader: 创建WebSocketReader
    RWS->>Listener: onOpen(webSocket, response)
    RWS->>Reader: 启动读取循环(新线程)
    Note over RWS: 连接建立完成

5. 消息收发机制

5.1 消息发送

当调用 WebSocket 的 send 方法时:

  1. 消息被添加到发送队列
  2. RealWebSocket 检查队列大小是否超过限制
  3. 消息通过 WebSocketWriter 编码为 WebSocket 帧
  4. 帧被写入底层 Socket

5.1.1 消息发送流程图

sequenceDiagram
    participant App as 应用程序
    participant WS as WebSocket接口
    participant RWS as RealWebSocket
    participant Queue as 消息队列
    participant Writer as WebSocketWriter
    participant Socket as 底层Socket

    App->>WS: send(text)
    WS->>RWS: send(text)
    RWS->>RWS: 检查连接状态
    alt 连接已关闭
        RWS-->>App: 返回false
    else 连接正常
        RWS->>Queue: 添加消息到队列
        RWS->>RWS: 检查队列大小是否超过限制
        alt 队列过大
            RWS->>RWS: 关闭连接(1001, "消息队列过大")
            RWS-->>App: 返回false
        else 队列正常
            RWS->>Writer: writeMessageFrame(OPCODE_TEXT, message)
            Writer->>Writer: 构造WebSocket帧
            Note over Writer: 添加帧头(FIN, RSV, OPCODE, MASK, LEN)
            alt 启用压缩
                Writer->>Writer: 压缩消息内容
            end
            Writer->>Socket: 写入帧数据
            RWS-->>App: 返回true
        end
    end

5.2 消息接收

WebSocketReader 在单独的线程中运行,不断从 Socket 读取数据:

  1. 读取并解析 WebSocket 帧头
  2. 根据帧类型进行不同处理:
    • 文本帧:调用 WebSocketListener.onMessage(String)
    • 二进制帧:调用 WebSocketListener.onMessage(ByteString)
    • PING 帧:自动回复 PONG
    • PONG 帧:更新心跳状态
    • CLOSE 帧:开始关闭流程

5.2.1 消息接收流程图

sequenceDiagram
    participant Socket as 底层Socket
    participant Reader as WebSocketReader
    participant RWS as RealWebSocket
    participant Writer as WebSocketWriter
    participant Listener as WebSocketListener

    loop 读取循环
        Reader->>Socket: 读取帧头
        Socket-->>Reader: 返回帧头数据
        Reader->>Reader: 解析帧头(FIN, RSV, OPCODE, MASK, LEN)
        Reader->>Socket: 读取帧内容
        Socket-->>Reader: 返回帧内容数据

        alt OPCODE = TEXT
            Reader->>Reader: 解码UTF-8文本
            alt 启用压缩
                Reader->>Reader: 解压缩消息内容
            end
            Reader->>RWS: 处理文本消息
            RWS->>Listener: onMessage(webSocket, text)
        else OPCODE = BINARY
            alt 启用压缩
                Reader->>Reader: 解压缩消息内容
            end
            Reader->>RWS: 处理二进制消息
            RWS->>Listener: onMessage(webSocket, bytes)
        else OPCODE = PING
            Reader->>RWS: 处理PING帧
            RWS->>Writer: writePong(payload)
            Writer->>Socket: 写入PONG帧
        else OPCODE = PONG
            Reader->>RWS: 处理PONG帧
            RWS->>RWS: 更新心跳状态
        else OPCODE = CLOSE
            Reader->>RWS: 处理CLOSE帧
            RWS->>Listener: onClosing(webSocket, code, reason)
            RWS->>Writer: writeClose(code, reason)
            RWS->>RWS: 开始关闭流程
        end
    end

6. 心跳和保活机制

RealWebSocket 实现了自动的心跳机制,用于保持连接活跃:

  1. 定期发送 PING 帧(默认间隔为 30 秒)
  2. 如果在超时时间内未收到 PONG 响应,连接被视为失效
  3. 失效的连接会被自动关闭,并触发 onFailure 回调

心跳机制由 RealWebSocket 内部的 pinger 线程池管理,可以通过 OkHttpClient.Builder 的 pingInterval 方法配置心跳间隔。

6.1 心跳机制流程图

sequenceDiagram
    participant Client as OkHttpClient
    participant RWS as RealWebSocket
    participant Writer as WebSocketWriter
    participant Server as WebSocket服务器
    participant Reader as WebSocketReader
    participant Listener as WebSocketListener

    Note over Client,RWS: 配置pingInterval(默认30秒)
    Client->>RWS: 创建RealWebSocket实例

    loop 心跳循环(每pingInterval秒)
        RWS->>RWS: 检查连接状态
        alt 连接正常
            RWS->>Writer: writePing(随机payload)
            Writer->>Server: 发送PING帧

            alt 服务器响应
                Server->>Reader: 发送PONG帧
                Reader->>RWS: 处理PONG帧
                RWS->>RWS: 更新最后一次PONG时间
            else 服务器未响应(超时)
                RWS->>RWS: 检测到PONG超时
                RWS->>RWS: 标记连接失效
                RWS->>Listener: onFailure(webSocket, 超时异常, null)
                RWS->>RWS: 关闭连接
            end
        end
    end

7. 错误处理和连接关闭

7.1 正常关闭

当调用 WebSocket.close 方法时:

  1. 发送 CLOSE 帧,包含关闭码和原因
  2. 等待接收对方的 CLOSE 帧
  3. 关闭底层连接
  4. 调用 WebSocketListener.onClosed 方法

7.1.1 正常关闭流程图

sequenceDiagram
    participant App as 应用程序
    participant WS as WebSocket接口
    participant RWS as RealWebSocket
    participant Writer as WebSocketWriter
    participant Server as WebSocket服务器
    participant Reader as WebSocketReader
    participant Listener as WebSocketListener

    App->>WS: close(1000, "正常关闭")
    WS->>RWS: close(1000, "正常关闭")
    RWS->>RWS: 检查连接状态
    alt 已经关闭
        RWS-->>App: 返回false
    else 连接正常
        RWS->>RWS: 设置关闭状态为CLOSING
        RWS->>Writer: writeClose(1000, "正常关闭")
        Writer->>Server: 发送CLOSE帧

        alt 服务器响应
            Server->>Reader: 发送CLOSE帧
            Reader->>RWS: 处理CLOSE帧
            RWS->>RWS: 设置关闭状态为CLOSED
            RWS->>RWS: 关闭底层连接
            RWS->>Listener: onClosed(webSocket, 1000, "正常关闭")
        else 超时未响应
            RWS->>RWS: 超时后强制关闭
            RWS->>RWS: 关闭底层连接
            RWS->>Listener: onClosed(webSocket, 1000, "正常关闭")
        end

        RWS-->>App: 返回true
    end

7.2 异常关闭

当发生异常时:

  1. 调用 WebSocketListener.onFailure 方法
  2. 关闭底层连接
  3. 释放相关资源

7.2.1 异常关闭流程图

sequenceDiagram
    participant Socket as 底层Socket
    participant Reader as WebSocketReader
    participant RWS as RealWebSocket
    participant Listener as WebSocketListener

    alt 网络异常
        Socket->>Reader: 抛出IOException
        Reader->>RWS: 传递异常
    else 协议错误
        Reader->>Reader: 检测到协议违规
        Reader->>RWS: 传递ProtocolException
    else 应用取消
        RWS->>RWS: cancel()被调用
    end

    RWS->>RWS: 标记连接失效
    RWS->>Listener: onFailure(webSocket, exception, response)
    RWS->>RWS: 关闭底层连接
    RWS->>RWS: 释放资源

7.3 关闭码

WebSocketProtocol 定义了标准的关闭码:

  • 1000:正常关闭
  • 1001:服务端关闭
  • 1002:协议错误
  • 1003:数据类型不支持
  • 1007:数据类型不一致
  • 1008:策略违规
  • 1009:消息过大
  • 1010:扩展协商失败
  • 1011:服务器意外情况

8. WebSocket 扩展支持

OkHttp 支持 WebSocket 的 permessage-deflate 扩展,提供消息压缩功能:

8.1 WebSocketExtensions 类

WebSocketExtensions 类解析和处理 Sec-WebSocket-Extensions 响应头,支持以下参数:

  • client_max_window_bits
  • client_no_context_takeover
  • server_max_window_bits
  • server_no_context_takeover

8.2 压缩实现

MessageDeflater 和 MessageInflater 类实现了消息的压缩和解压缩:

  • 使用 DEFLATE 算法压缩消息
  • 支持上下文接管控制
  • 支持窗口大小配置

8.3 扩展协商流程图

+---------------+      +---------------+      +---------------+      +---------------+
|               |      |               |      |               |      |               |
|  OkHttpClient +----->+ RealWebSocket +----->+ WebSocket服务器+----->+ WebSocketExt. |
|               |      |               |      |               |      |               |
+---------------+      +-------+-------+      +-------+-------+      +-------+-------+
                               |                      |                      |
                               |                      |                      |
                               |   握手请求(扩展请求)   |                      |
                               +--------------------->+                      |
                               |                      |                      |
                               |                      |                      |
                               |   101响应(扩展协商)   |                      |
                               +<---------------------+                      |
                               |                      |                      |
                               |                      |                      |
                               |     解析扩展头        |                      |
                               +--------------------------------------------->
                               |                      |                      |
                               |                      |                      |
                               |     返回扩展配置      |                      |
                               <---------------------------------------------+
                               |                      |                      |
                               |                      |                      |
                               |  创建压缩/解压组件    |                      |
                               +-----+                |                      |
                               |     |                |                      |
                               |     v                |                      |
                               |  启用消息压缩        |                      |
                               |                      |                      |
                               |                      |                      |

WebSocket扩展协商流程:

  1. 初始化阶段

    • 应用程序通过OkHttpClient创建WebSocket连接
    • OkHttpClient创建RealWebSocket实例
    • RealWebSocket初始化扩展支持
  2. 握手阶段

    • RealWebSocket发送握手请求,包含扩展请求头:
      GET /path HTTP/1.1
      ...
      Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
      
  3. 协商阶段

    • 如果服务器支持扩展,返回101响应,包含协商结果:
      HTTP/1.1 101 Switching Protocols
      ...
      Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=14; server_max_window_bits=14
      
    • RealWebSocket将扩展头传递给WebSocketExtensions进行解析
    • WebSocketExtensions验证扩展参数
  4. 配置阶段

    • 如果扩展参数有效:
      • WebSocketExtensions返回扩展配置
      • RealWebSocket创建MessageDeflater和MessageInflater
      • 配置WebSocketWriter启用压缩
      • 配置WebSocketReader启用解压
      • 后续消息发送时应用压缩,接收时应用解压
    • 如果扩展参数无效或服务器不支持扩展:
      • 不启用压缩功能
      • 继续正常的WebSocket通信
  5. 完成连接建立

9. 使用示例

9.1 基本使用

val client = OkHttpClient.Builder()
    .pingInterval(30, TimeUnit.SECONDS)
    .build()

val request = Request.Builder()
    .url("ws://example.com/ws")
    .build()

val webSocket = client.newWebSocket(request, object : WebSocketListener() {
    override fun onOpen(webSocket: WebSocket, response: Response) {
        webSocket.send("Hello, WebSocket!")
    }

    override fun onMessage(webSocket: WebSocket, text: String) {
        println("Received message: $text")
    }

    override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
        println("Received bytes: ${bytes.hex()}")
    }

    override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
        println("Closing: $code / $reason")
        webSocket.close(1000, null)
    }

    override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
        println("Closed: $code / $reason")
    }

    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
        println("Error: ${t.message}")
    }
})

9.2 高级配置

val client = OkHttpClient.Builder()
    .pingInterval(15, TimeUnit.SECONDS)  // 设置心跳间隔
    .connectTimeout(10, TimeUnit.SECONDS)  // 连接超时
    .readTimeout(30, TimeUnit.SECONDS)  // 读取超时
    .writeTimeout(30, TimeUnit.SECONDS)  // 写入超时
    .build()

9.3 完整的聊天客户端示例

sequenceDiagram
    participant User as 用户
    participant App as 聊天应用
    participant WS as WebSocket
    participant Server as 聊天服务器

    User->>App: 打开聊天
    App->>WS: 创建WebSocket连接
    WS->>Server: 建立连接
    Server->>WS: 连接确认
    WS->>App: onOpen回调
    App->>User: 显示"已连接"状态

    User->>App: 输入消息
    App->>WS: send(消息)
    WS->>Server: 发送消息

    Server->>WS: 发送新消息
    WS->>App: onMessage回调
    App->>User: 显示新消息

    User->>App: 关闭聊天
    App->>WS: close(1000, "用户关闭")
    WS->>Server: 发送CLOSE帧
    Server->>WS: 响应CLOSE帧
    WS->>App: onClosed回调
    App->>User: 显示"已断开连接"状态
class ChatClient(private val serverUrl: String) {
    private var webSocket: WebSocket? = null
    private val client = OkHttpClient.Builder()
        .pingInterval(15, TimeUnit.SECONDS)
        .build()

    fun connect(messageCallback: (String) -> Unit,
                statusCallback: (String) -> Unit) {
        val request = Request.Builder()
            .url(serverUrl)
            .build()

        webSocket = client.newWebSocket(request, object : WebSocketListener() {
            override fun onOpen(webSocket: WebSocket, response: Response) {
                statusCallback("已连接到服务器")
            }

            override fun onMessage(webSocket: WebSocket, text: String) {
                messageCallback(text)
            }

            override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
                webSocket.close(1000, null)
                statusCallback("连接关闭中: $reason")
            }

            override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
                statusCallback("连接已关闭: $reason")
            }

            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                statusCallback("连接错误: ${t.message}")
            }
        })
    }

    fun sendMessage(message: String): Boolean {
        return webSocket?.send(message) ?: false
    }

    fun disconnect() {
        webSocket?.close(1000, "用户主动断开连接")
        webSocket = null
    }
}

10. 总结

OkHttp 提供了一个功能完善、设计良好的 WebSocket 实现,具有以下特点:

  • 符合 RFC 6455 规范
  • 自动心跳机制
  • 支持文本和二进制消息
  • 支持消息压缩
  • 优雅的错误处理
  • 简洁易用的 API

通过 OkHttp 的 WebSocket 实现,开发者可以轻松地在 Android 和 Java 应用中添加实时通信功能,如聊天、实时数据更新等。

11. 参考资料