WebSocket 读写是通过 Tomcat NIO Connector,但封装后行为是半阻塞式 每个 WebSocket 会话需要绑定一个线程
抛开一堆的异常处理和过滤类这边找到相关的代码是这个。
百度了一下
Tomcat 内部通过:
- WsServerContainer
- WsWebSocketContainer
- NIO Connector + Selector
实现了对 WebSocket 的绑定。
每个 Session(一个 WebSocket 会话)最终会绑定到一个 WsRemoteEndpointImplBase,使用 BlockingQueue 来实现消息的写入
WebSocketContainer 是一个接口
WsWebSocketContainer 是这个接口的一个实现
connectToServer(...) 用于发起 WebSocket 客户端连接(重载了多个版本)
get/setDefaultAsyncSendTimeout() 设置异步发送消息的最大等待时间(毫秒)
get/setDefaultMaxSessionIdleTimeout() 设置 WebSocket 会话的最大空闲时间(毫秒)
get/setDefaultMaxBinaryMessageBufferSize() 设置默认二进制消息的缓冲区大小(字节)
get/setDefaultMaxTextMessageBufferSize() 设置默认文本消息的缓冲区大小(字符)
getInstalledExtensions() 获取支持的 WebSocket 扩展(如 permessage-deflate)
这个接口定义了一些关于任务 后台周期性处理任务
void backgroundProcess();
“在某个时间间隔内自动触发一次的后台任务逻辑”。
setProcessPeriod(int):设置多长时间执行一次。
getProcessPeriod():获取当前设定的周期时间。
我们来看些具体方法的实现
先看这两个方法
从数据结构来说
链接的核心目的在于这个instance的发现和被发现。然后这个 instance 的类型是一个endpoint。
前者用于连接到一个已经构造好的 WebSocket 实例(POJO 对象)。
后者用于连接到一个 Class 类型的 (这个class类型是一个annotatedEndpointClass 这个标识使用注解和接口继承注册的客户端实例)WebSocket POJO,交由容器(Tomcat)自己去实例化它。
可以看到endpoint 又是session控制的单元
再看这两段就比较直接都是传入一个 Endpoint 相关的参数
EndpointHolder 传入已实例化的 Endpoint 对象
EndpointClassHolder 传入 Endpoint 类
这一段的两个方法
ClientEndpointHolder 对象传入 connectToServerRecursive 这个方法返回一个session对象
这个方法总共有150多行这边就不做截图了
概况一下connectToServerRecursive 方法负责:
- 解析并检查连接的 URI(ws:// 或 wss://)
- 处理代理连接(HTTP CONNECT 方式)
- 建立底层异步Socket通道连接(AsynchronousSocketChannel)
- 处理 SSL/TLS 握手(如果是 wss)
- 发送 WebSocket 握手请求(HTTP Upgrade)
- 处理服务器响应,包括重定向、认证(401)、协议协商、扩展协商
- 递归处理 HTTP 重定向(最多 20 次)
- 最终完成 WebSocket 会话的创建并返回
我们关注几个点什么叫做代理链接
在java net包中实现了一个这样的网络代理类。里面有两个成员变量 type 和地址
然后这个过程就是简单的把这个代理对象的值设置为传进来的地址和类型
从这段逻辑中我们可以明确知道整个代理过程不过是将合法的url给写入到代理对象中的地址中去。
我们发现有意思的是 一个目标地址返回的是一个代理列表?这是为什么?
百度了一下
因为系统代理机制允许为一个目标地址返回多个候选代理,按优先级排序。这是为了实现灵活的网络路由策略。
这里我们先不管总之我们只用知道tomcat只关注第一个能被循环访问到的可用合法的代理对象。
关注这里我们从http中拿到或者是没有拿到地址(sa)。还是从方法传参中去拿这个地址(ppst)。这里告诉我们基于http升级成websocket协议的握手发起是在tomcat的这一层去做的。
然后后面很长一段代码都是这种链接相关的逻辑
包含
为 WebSocket 客户端连接做准备,包括代理处理、请求头构建、连接创建、超时配置、扩展协商等。
我们再关注这里这里就意味着这里的二次通信的过程就是一个channel(客户端)连接(注册)的起点。
通过对这个channel的观察。我们发现这里整个 WebSocket 的连接建立和协议升级过程确实是异步的,通过 Future 来实现异步操作的结果等待(阻塞等待),从而兼顾了非阻塞 I/O 和流程控制的需求。也就是说协议升级过程是基于异步回调机制实现的。
这里再解释下
HTTP 重定向(HTTP Redirect)是指服务器在接收到客户端请求后,告诉客户端去访问另一个 URL 地址的机制。
比如说
GET http://example.com/oldpage
服务器响应:
HTTP/1.1 301 Moved Permanently
Location: http://example.com/newpage
客户端收到这个响应后,会自动去请求:
GET http://example.com/newpage
我们这里再来说明一个概念
创建相应的 Transformation 对象链,用于后续 WebSocket 数据的处理。
关注这一长串方法
先来看一下session的数据结构和构造器
WsSession wsSession = new WsSession(
clientEndpointHolder, // Endpoint 持有者
wsRemoteEndpointClient, // RemoteEndpoint 发送器
this, // WebSocket 容器(WsWebSocketContainer)
extensionsAgreed, // 协商成功的扩展
subProtocol, // 协商成功的子协议
Collections.emptyMap(), // 路径参数(客户端一般为空)
secure, // 是否 wss
clientEndpointConfiguration // 配置对象
);
WsSession 是一个典型的会话模型,抽象了 WebSocket 在 Java 客户端中的所有核心职责。也标志着从报文交换进入到全双工的标志
然后我们关注方法
我们回忆
关注
总体流程上来说
Channel = AsynchronousSocketChannel.open(...);完成 TCP(或 TLS)连接创建。而后连接的生命周期由 AsyncChannelWrapper 封装后交给 WsSession 与 WsFrameClient 管理。
然后我们发现这个连接的封装对象作为入参传入到session中做管理。
然后我们不妨再探讨一下这个channel的上层对象
所以说tomcat的连接和本地读写这样的行为都是和session这么一个对象关联上的。
而我的怀疑是因为在上层可能要做到切换session 所以这里就不如netty灵活。