WebSocket in Tomcat 7 (part 2)
接着上一篇,来分析一下 Tomcat 7 中 WebSocket 的实现
WsFilter
WebSocket 的实现入口处为 WsFilter,该 Filter 检查 HTTP header 中是否包含下述字段
1 | Upgrade: websocket |
且为 HTTP GET 请求。若符合 WebSocket Handshake 要求,则从 WebSocketContainer 中查找请求路径是否有 filter 拦截,若没有则继续后续的 filter,若有则进入 UpgradeUtil.doUpgrade 方法
WsServerContainer
回头来看 WsServerContainer 的初始化,当然最为重要的是注册了 WsFilter
1 | FilterRegistration.Dynamic fr = servletContext.addFilter( |
可见 WsFilter 拦截所有请求,当遇到 HTTP Upgrade to websocket 协议的请求时执行 doUpgrade 逻辑
UpgradeUtil.doUpgrade
doUpgrade 在各种检查后,可以接受 Client Upgrade 请求时,需向 Client 端返回的 HTTP 报文头中添加如下字段(Sec-WebSocket-Protocol / Sec-WebSocket-Extensions 可选)
1 | Upgrade: websocket |
当然还需要初始化 ServerEndpoint 实例(@ServerEndpoint 注解)
1 | Endpoint ep; |
最后向 Client 返回 HTTP Response
1 | WsHttpUpgradeHandler wsHandler = |
而 WsHttpUpgradeHandler 则会用于处理
1 | The handler for all further incoming data on the current connection. |
WsHttpUpgradeHandler
WsHttpUpgradeHandler init 方法中干了很多事情
- 首先从 WebConnection 中获取输入流/输出流
1 | this.connection = connection; |
- 实例化 WsSession,注意 SessionId 使用一个 static AtomicLong 维护,每次增加 1
- 实例化 WsFrameServer,用于读写 Message
- 在 sos 上注册 WsWriteListener
- 调用 ServerEndpoint onOpen 方法
- 在 sis 上注册 WsReadListener
summary
所以综上所述,每一次 HTTP Upgrade 请求均会创建一个新的 ServerEndpoint 实例,因此定义于 ServerEndpoint 中的 static 变量需注意确保线程安全
另外 ServerEndpoint 中 onOpen 和 onMessage 的执行顺序为 onOpen 必然首先执行,若 onOpen 执行时间过长,则就算 sis 中有数据等待处理,也不会触发 onMessage,因为从 WsHttpUpgradeHandler init 方法中可以看出 onOpen 调用结束后,才会在 sis 上注册 WsReadListener。接下来继续分析,如何触发 WsReadListener
Connector
AbstractServiceInputStream.onDataAvailable() 方法中调用 listener.onDataAvailable(); 即 WsReadListener.onDataAvailable()
而 AbstractServiceInputStream.onDataAvailable() 又由 AbstractProcessor.upgradeDispatch(SocketStatus status) 调用
1 |
|
Tomcat 7 默认使用 BIO,Http11Protocol.createUpgradeProcessor,其中将 socket 超时时间设置为不超时,并返回一个 Processor
因此 Tomcat 处理 WebSocket 请求的大致流程为
- JIOEndpoint accept socket and new a thread to handle it
- Worker wait for the next socket to be assigned and call handler to process socket
- Http11ConnectionHandler
- Http11Processor
客户端首先打开一个 connection,connection 建立后,向服务器端发起 WebSocket Handshake 请求,服务器接受后,返回 101 status code,双方可在当前 connection 上双工通信
当 SocketStatus 为 OPEN_READ 时,回调 readListener 的 onDataAvailable 方法,此处逻辑有 trick 的地方,值得注意的是如果 SocketStatus.OPEN_READ 时,仍未完成注册 readListener,则不会触发 listener.onDataAvailable() … 显然,因为 listener 为 null
1 | protected final void onDataAvailable() throws IOException { |
在 WsFrameServer 的 onDataAvailable 方法中首先尝试获取对象锁,获取成功后,for loop 监听 Servlet 输入流,当有数据时读取数据供 WsFrameServer 处理,处理 okay 后,回调 ServerEndpoint 的 onMessage 方法,业务层即感知到从 ws 连接中获取到数据
另外 ServerEndpoint onOpen 是在 WsHttpUpgradeHandler init 方法中被回调,看看官方文档对 handler 的 init 方法的描述
This method is called once the request/response pair where the upgrade is initiated has completed processing and is the point where control of the connection passes from the container to the HttpUpgradeHandler.
so 理论上要想使得 Tomcat 7 WebSocket 能正常工作的前提为
- WsHttpUpgradeHandler init 方法被调用 —— WsHttpUpgradeHandler
- 在 sos 上注册 WsWriteListener 方法结束 —— WsHttpUpgradeHandler
- SocketStatus.OPEN_WRITE —— AbstractProcessor
- onOpen 方法回调结束 —— ServerEndpoint
- 在 sis 上注册 WsReadListener 方法结束 —— WsHttpUpgradeHandler
- SocketStatus.OPEN_READ —— AbstractProcessor
接下来需要搞清楚一个问题,上述这些逻辑是单线程在跑,还是多线程,单线程的话,时序问题不大,但是多线程的情况下,就很有讲究了
To be cont. 下一遍回答上述时序问题