You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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-1build1
    • libluajit2-5.1-common: 2.1-20230410-1build1
    • libnginx-mod-http-lua: 1:0.10.26-2
    • libnginx-mod-http-ndk: 1:0.3.3-1build1
    • libnginx-mod-stream-js: 0.8.2-1ubuntu1
    • lua-resty-core: 0.1.28-2
    • lua-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

核心疑问

  1. 如何在该场景下成功使用ngx.socket.tcp()?
  2. 测试发送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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 04:50:10