无需轮询的双向通信:http_study完整实战WebSocket服务端与客户端
【免费下载链接】http_studyfollow me to study http项目地址: https://gitcode.com/gh_mirrors/ht/http_study
本文带你用http_study(follow me to study http)开源项目,在 OpenResty + Nginx 环境中从零实战WebSocket 服务端与客户端:一个 Lua 服务端脚本、一个 Lua 客户端脚本、一个 Python 客户端脚本,三步跑通无需轮询的双向通信。
为什么需要 WebSocket:告别轮询 🔄
传统的 HTTP 通信是"请求-响应"模式:客户端问一句,服务器答一句。想要服务器主动推送消息?只能靠轮询(客户端每隔几秒问一次"有新消息吗?"),既浪费带宽又延迟高。
WebSocket(RFC 6455)解决的就是这个问题:
- 客户端与服务器通过一次 HTTP 握手建立长连接
- 连接建立后,双方都可以主动推送数据,无需反复发起请求
- 典型场景:即时聊天、股票行情、在线协作、游戏状态同步
http_study项目正是围绕《透视 HTTP 协议》编写的学习仓库,用 Nginx/OpenResty + Lua 把每个知识点做成可运行的实验,第 38 课就是 WebSocket 实战。
环境准备:一键克隆并启动
如果本地还没有项目代码,克隆仓库:
git clone https://gitcode.com/gh_mirrors/ht/http_study进入www/目录后,只需执行启动脚本即可拉起整套 OpenResty 环境:
cd http_study/www ./run.sh start💡 也支持 Docker 方式:
docker pull chronolaw/http_study,详见项目根目录的 docker-compose.yml 与 Dockerfile。
启动后无需手工配置任何路由——www/conf/http/servers/locations.inc中有一个非常巧妙的正则 location:
# curl 127.1/07-1 location ~ ^/([\d|\-]+) { default_type text/plain; content_by_lua_file lua/$1.lua; }见 locations.inc:访问/38-0就会自动执行lua/38-0.lua,访问/38-1执行lua/38-1.lua。这是 http_study 最省心的设计——每个实验一个脚本,URL 即入口。
第一步:用 20 行 Lua 写一个 WebSocket 服务端 🔌
打开 38-0.lua,整个服务端核心逻辑不到 50 行:
local server = require "resty.websocket.server" local wb, err = server:new{ timeout = 5000, max_payload_len = 1024 * 64, }OpenResty 自带的resty.websocket.server库帮我们屏蔽了握手帧的编解码细节,server:new()就完成了一次 WebSocket 握手(见 38-0.lua)。
之后就是一个while true循环不断收帧(38-0.lua):
| 帧类型 | 服务端行为 | 说明 |
|---|---|---|
ping | wb:send_pong() | 心跳保活,防止连接被中间设备掐断 |
text | wb:send_text(data..','..data) | 收到什么文本,就回显拼接后的文本 |
close | wb:send_close() | 协商关闭连接 |
| 超时 | 继续等待 | 区分 timeout 错误与真实错误,避免误关连接 |
这个小服务端的规则很简单:你发monado,我回monado,monado。麻雀虽小,却完整覆盖了 WebSocket 的收发、心跳、关闭三大生命周期。
第二步:Lua 客户端与服务端对练 🤝
同一个 nginx worker 里,还能直接用resty.websocket.client写客户端——见 38-1.lua,流程一目了然:
local wb, err = client:new{ timeout = 5000, max_payload_len = 1024 * 64, } local uri = "ws://127.0.0.1/38-0" local ok, err = wb:connect(uri)- 连接:
wb:connect(uri)发起握手,连接ws://127.0.0.1/38-0 - 心跳:
send_ping()后收一帧pong,打印recv pong - 业务:
send_text("hello websocket"),收到回显hello websocket,hello websocket - 收尾:
send_close()正常断开(见 38-1.lua)
在终端执行:
curl 127.0.0.1/38-1预期输出:
connect ok recv pong recv: hello websocket,hello websocket一行命令看到完整的"连接→心跳→收发消息→关闭"闭环,这就是 WebSocket 双向通信的最小演示。
第三步:Python 客户端跨语言验证 🐍
更贴近真实场景的是用独立语言写客户端。python/websocket.py 只有 20 多行:
async with websockets.connect(uri) as ws: name = 'monado' await ws.send(name) print(f'> {name}') greeting = await ws.recv() print(f'< {greeting}')它连接同一个地址ws://127.0.0.1/38-0,发送monado并打印服务端的回显。安装依赖后运行:
pip install websockets python python/websocket.py输出:
> monado < monado,monado一个 Lua 服务端 + 一个 Lua 客户端 + 一个 Python 客户端,三种视角验证同一条 WebSocket 连接,非常适合理解"协议是双方共同遵守的约定"这一本质。
抓包验证:看清 WebSocket 握手过程 🔍
想亲眼看看 WebSocket 的 HTTP 升级握手吗?项目贴心地附好了现成的抓包文件 38-1.pcapng。用 Wireshark 打开,用过滤条件http.request.method == "GET" && websocket即可定位握手请求。
握手阶段你会看到经典的三个头:
Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: <客户端随机值>服务器返回101 Switching Protocols后,TCP 连接从"HTTP 语义"切换为"WebSocket 帧语义",此后所有数据都以二进制帧传输——这就是"一次握手、双向推送"的底层原理。
配置细节:两个值得学习的小技巧 ⚙️
- 超时与缓冲区:resty.conf 中统一配置了
lua_socket_read_timeout等 cosocket 超时与缓冲参数,避免长连接因超时设置不当而异常断开。 - 拒绝普通 HTTP 访问:38-0.lua 里当握手失败(即直接用 HTTP 访问
/38-0)时返回444状态码直接断连,防止 WebSocket 端口被普通 HTTP 流量"打杂"。
小结:一条连接,双向世界 🚀
本文用 http_study 的 3 个脚本完成了 WebSocket 的完整实战:
| 角色 | 文件 | 关键 API |
|---|---|---|
| WebSocket 服务端 | www/lua/38-0.lua | resty.websocket.server/recv_frame |
| Lua 客户端 | www/lua/38-1.lua | resty.websocket.client/send_text |
| Python 客户端 | python/websocket.py | websockets.connect/ws.recv |
| 抓包验证 | wireshark/38-1.pcapng | RFC 6455 升级握手 |
相比轮询,WebSocket 用一次握手换来了一条全双工通道:消息实时、开销更低、代码更直观。跑通这三个脚本后,你就已经掌握了 WebSocket 服务端与客户端开发的核心骨架,接下来只需把它接入聊天室、行情推送等真实业务即可。
【免费下载链接】http_studyfollow me to study http项目地址: https://gitcode.com/gh_mirrors/ht/http_study
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考