在Nginx Location上下文使用ngx.socket.tcp()遇阻塞问题求助
Nginx Lua模块
ngx.socket.tcp()使用问题排查与解决 问题背景
我是Ubuntu Noble系统上Nginx Lua模块(libnginx-mod-http-lua包)的新手,在使用local sock = ngx.socket.tcp()时遇到了问题:已成功打开套接字并写入数据,但tcpdump显示sock:send()操作成功却收不到返回数据,推测Nginx可能阻止了Lua块接收sock:connect()的ACK包。
为简化问题,我尝试复现一个简单示例验证操作正确性,但自行实现的版本在tcp:receive()调用时挂起。
环境配置
- 系统:Ubuntu Noble
- 相关Lua/Nginx包版本:
libluajit2-5.1-2: 2.1-20230410-1build1libluajit2-5.1-common: 2.1-20230410-1build1libnginx-mod-http-lua: 1:0.10.26-2libnginx-mod-http-ndk: 1:0.3.3-1build1libnginx-mod-stream-js: 0.8.2-1ubuntu1lua-resty-core: 0.1.28-2lua-resty-lrucache: 0.13-10
- Nginx版本:1.24.0-2ubuntu7.1
- 部署环境:Nginx与
nc运行在VirtualBox虚拟机中,curl在宿主机执行。
代码与输出
Nginx测试location配置
location /foo { content_by_lua_block { local tcp=ngx.socket.tcp() ngx.say(ngx.now()) ngx.flush() tcp:connect("127.0.0.1", 8000) ngx.say("connect() did not block") ngx.flush() local data, err = tcp:receive(1) ngx.say("receive() did not block") ngx.say(err) ngx.flush() ngx.update_time() ngx.say(ngx.now()) ngx.flush() } }
curl调用输出(因挂起执行Ctrl+C终止)
rlott@1022rdnote04:~/testing$ curl -k https://192.168.56.220/foo 1740693455.69 connect() did not block ^C rlott@1022rdnote04:~/testing$
8000端口运行的nc状态(无操作)
etservice@noble:~$ nc -l 8000 -k
Nginx错误日志(/var/log/nginx/error.log)
2025/02/27 16:58:35 [error] 608728#608728: *101 lua tcp socket read timed out, client: 192.168.56.1, server: , request: "GET /foo HTTP/1.1", host: "192.168.56.220" 2025/02/27 16:58:35 [info] 608728#608728: *101 client 192.168.56.1 closed keepalive connection
核心疑问
- 如何在该场景下成功使用
ngx.socket.tcp()? - 测试发送JSON-RPC消息时,
tcpdump显示数据已送达,但仍在sock:receive()处挂起。Wireshark分析发现两点差异:我的消息未设置TCP FIN标志;合法客户端消息数据量更大。这两点对问题有什么影响?
解决方案与技术分析
1. 基础问题:tcp:receive()挂起的直接原因
你当前的测试中,nc -l 8000 -k只是监听端口但没有发送任何数据,tcp:receive(1)会一直等待接收1字节数据,直到超时(默认超时时间60秒),这就是挂起的直接原因。
要验证套接字通信正常,只需在nc终端输入任意字符并回车,tcp:receive()就能获取到数据并继续执行。
2. 关于ACK包与Nginx的误解
Nginx不会阻止Lua块接收sock:connect()的ACK包。ngx.socket.tcp()基于Nginx事件循环实现异步非阻塞逻辑,connect()调用返回成功就说明TCP三次握手已完成,ACK包已被正常接收处理。你看到tcpdump中无后续数据,是因为对方未发送数据,而非ACK包被拦截。
3. JSON-RPC场景的问题分析
针对你提到的JSON-RPC场景:
- TCP FIN标志:FIN标志用于主动关闭TCP连接,你的消息没有FIN标志是正常状态——合法客户端可能在发送完数据后主动关闭连接(发送FIN),而你的代码仅发送数据却保持连接打开。
sock:receive()会一直等待对方发送的数据,直到对方关闭连接(此时receive()返回closed错误)或超时。 - 数据量差异:合法客户端数据量更大,可能是因为它发送了完整的响应数据,而你的服务端未正确返回响应,或响应格式不符合预期(比如缺少换行符、长度标识等),导致
receive()一直等待完整数据。
4. 正确使用ngx.socket.tcp()的建议
(1)设置合理的超时时间
在connect()或receive()前设置超时,避免长时间挂起:
local tcp = ngx.socket.tcp() tcp:settimeout(5000) -- 设置5秒超时
(2)处理connect()的错误
不要忽略connect()的返回值,必须检查连接是否成功:
local ok, err = tcp:connect("127.0.0.1", 8000) if not ok then ngx.say("连接失败: ", err) return end
(3)根据协议确定接收方式
- 基于行的协议(如HTTP、部分JSON-RPC实现):使用
tcp:receive("*l")接收一行数据; - 固定长度/带长度前缀的协议:使用
tcp:receive(n)接收指定长度; - 需要接收所有数据直到连接关闭:使用
tcp:receive("*a")。
示例:接收JSON-RPC响应直到连接关闭
local data, err = tcp:receive("*a") if err then ngx.say("接收错误: ", err) end
(4)主动关闭连接
使用完套接字后,主动释放资源:
tcp:close()
内容的提问来源于stack exchange,提问作者Rodney Lott
相关产品推荐
相关产品推荐

