浅析nginx实现websocket原理
前言:
传统的HTTP协议是一种无状态的请求/响应协议,每次请求都需要重新建立连接。在一些特殊的业务场景下,服务端需要主动发送数据到客户端,例如行情推送、监控告警推送等。然而,HTTP协议不支持双向通信,因此需要将HTTP协议“升级”为WebSocket协议。WebSocket协议可以在建立连接后保持连接状态,双方可以通过一个持久的连接通道进行实时通信。WebSocket连接在建立时通过HTTP协议进行握手,之后的数据传输就可以使用WebSocket协议进行。
Nginx作为中间层的Web服务器,支持使用多种协议与上下游进行通信,包括TCP、HTTP、WebSocket等协议,如下图所示。
1、nginx升级http为websocket的过程
HTTP/1.1提供了一种特殊的机制,这一机制允许将一个已建立的连接升级成新的、不相容的协议。具体过程如下:
1.客户端发起 WebSocket 连接请求到 Nginx,Nginx 作为反向代理服务器,将请求转发给上游 WebSocket 服务器。客户端发送的请求类似于下图所示:
1 |
|
2.上游 WebSocket 服务器响应连接请求并完成握手协议,如果允许升级,将响应状态码101返回给 Nginx。服务端的响应类似于下图所示:
1 |
|
3.Nginx 收到上游 WebSocket 服务器的响应结果后,将其转发给客户端,建立起客户端与上游 WebSocket 服务器的连接。
4.客户端和上游 WebSocket 服务器之间开始进行实时数据传输。
5.当客户端或上游 WebSocket 服务器需要发送数据时,数据将通过 WebSocket 协议封装成帧(frame)并发送到对方。
6.数据通过 Nginx 进行转发时,Nginx 会根据实际情况选择合适的负载均衡算法,将数据传输到适当的上游 WebSocket 服务器。
7.上游 WebSocket 服务器收到数据后,解析数据帧并处理数据,然后将响应结果封装成帧并发送回客户端。
8.客户端收到上游 WebSocket 服务器的响应结果后,解析数据帧并处理数据,完成一次数据交互。
2、nginx核心代码实现
2.1、连接升级
当服务端同意升级为 WebSocket 时,会将响应状态码设置为 “101 Switching Protocols”,表示服务器正在切换协议。如下代码所示,在处理 HTTP 协议时,首先会检查连接是否已升级到另一个协议。其中,NGX_HTTP_SWITCHING_PROTOCOLS 是一个宏,值为 101。然后,会检查客户端的请求是否携带 Upgrade 头部。如果请求携带了该头部,就会将 u->upgrade 的值设为 1,表示该连接是一个升级连接。
1 |
|
2.2、上下游数据处理
nginx基于事件驱动模型,当有事件触发时,就会调用对应的回调函数。
下游:
1 |
|
上游:
1 |
|
每个连接都根据类型(http、websocket等)的不同,设置不同的回调,下面的代码展示了当连接的u->upgrade = 1,即为websocket协议时,设置的上下游读写回调函数。当此连接再有读写事件时,就会回调下面设置的函数
1 |
|
实际上,这四个回调函数都调用了同一个处理函数。但是,它们分别传入了不同的参数,因此函数的处理方式也不同,对应四种不同的类型。
1 |
|
此处的设计非常巧妙(部分代码),使用了不同参数复用了同一个函数。下面的核心代码展示了 Nginx 如何处理事件。
当数据来自于服务端时,from_upstream 为真,do_write 表示需要发送数据。根据下面的代码,可以看出 src 表示服务端的连接,dst 表示客户端的连接。当 do_write 为 1 且 dst 准备好写入操作(并且需要发送的数据长度不为 0)时,dst 就会发送数据;当 src 准备好读取操作时,src 就会读取数据。
比较有意思的是,当数据来自于客户端时,只需要将 dst 和 src 交换即可。
1 |
|
3、使用实例
本节将通过 Nginx 和 WebSocket 客户端/服务端的实例,展示如何在实际业务中使用 WebSocket 进行消息推送。
首先,需要对 Nginx进行路由配置,如下图所示。即,所有以 uri 前缀为 wss 的 HTTP 客户端请求都会被升级为 WebSocket。在代理过程中,HSIAR 还需要将 Connection 和 Upgrade 头部携带给后端服务,告知后端需要将 Nginx 与后端的连接升级为 WebSocket。
2、建立连接的过程如下图所示,可以看到与2.1节所述过程一致
3、连接建立成功后,点击订阅,即接收消息的推送
4、后端推送消息
可以看到消息由后端成功推送到了客户端
4、总结
在实际的生产中,消息推送是一种常用的技术。然而,如果在 WebSocket 客户端和服务端之间的链路中加入代理,尤其是多级代理后,情况就会变得更加复杂。为了确保链路上的每一条连接都是 WebSocket 长连接,需要避免中间出现 HTTP 短链接。否则,推送就可能因连接提前断开而失败。了解该技术的原理和细节,可以帮助快速排查问题并进行修复。同时,研究 Nginx 对 WebSocket 的支持技术实现,不仅能够提高对该技术的理解,也能够为今后开发相关系统提供有益的借鉴。