- 新增 lua-embed-analysis.md 技术分析文档 - 新增 lua-nginx-module 文档目录 - 更新 gitignore 允许跟踪 docs/lua-nginx-module/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.7 KiB
7.7 KiB
lua-nginx-module Cosocket 非阻塞 Socket API
本文档详细说明 lua-nginx-module 的非阻塞 Socket API (Cosocket)。
核心文件
| 文件路径 | 描述 |
|---|---|
src/ngx_http_lua_socket_tcp.c |
TCP socket 实现 (~7000 行) |
src/ngx_http_lua_socket_tcp.h |
TCP socket 头文件 |
src/ngx_http_lua_socket_udp.c |
UDP socket 实现 (~1700 行) |
src/ngx_http_lua_socket_udp.h |
UDP socket 头文件 |
一、TCP Socket API
1.1 创建 Socket
ngx.socket.tcp() / ngx.socket.stream()
创建 TCP socket 对象。
- 返回值: TCP socket 对象
- 示例:
local sock = ngx.socket.tcp()
1.2 连接
sock:connect(host, port, options?)
建立 TCP 连接。
- 参数:
host(string): 主机名或 IP 地址port(number): 端口号options(table, 可选): 连接选项
- 参数格式:
-- IP + 端口 sock:connect("127.0.0.1", 80) -- Unix Domain Socket sock:connect("unix:/path/to/socket") -- 带连接池选项 sock:connect("127.0.0.1", 80, { pool_size = 10, -- 连接池大小 backlog = 5, -- 等待队列长度 pool = "my_pool" -- 自定义池名称 }) - 返回值: 1 (成功) 或 nil, err
- 适用阶段: rewrite, access, content
1.3 发送数据
sock:send(data)
发送数据。
- 参数:
data(string/number/boolean/table) - 返回值: bytes_sent (成功) 或 nil, err
- 示例:
local bytes, err = sock:send("GET / HTTP/1.1\r\n\r\n") local bytes, err = sock:send({ "line1", "line2" })
1.4 接收数据
sock:receive(pattern?)
接收数据。
- 参数:
pattern(string/number, 可选) - 接收模式:
| 模式 | 说明 |
|---|---|
nil 或 "*l" |
读取一行 (默认) |
"*a" |
读取所有数据直到 EOF |
number |
读取指定字节数 |
- 返回值: data (成功) 或 nil, err, partial
- 示例:
local line = sock:receive() -- 读取一行 local data = sock:receive("*a") -- 读取全部 local data = sock:receive(1024) -- 读取 1024 字节
sock:receiveany(max_bytes)
接收最多指定字节数的数据。
- 参数:
max_bytes(number) - 返回值: data 或 nil, err
sock:receiveuntil(pattern, options?)
创建迭代器读取直到匹配模式。
- 参数:
pattern(string): 结束模式options(table):{inclusive = true/false}
- 返回值: iterator 函数
- 示例:
local reader = sock:receiveuntil("\r\n\r\n") local data, err, partial = reader() local data4 = reader(4) -- 读取 4 字节
1.5 超时设置
sock:settimeout(ms)
设置所有操作的超时时间。
sock:settimeouts(connect_timeout, send_timeout, read_timeout)
分别设置连接、发送、读取超时。
- 参数: 毫秒数
- 示例:
sock:settimeout(5000) -- 5 秒超时 sock:settimeouts(1000, 2000, 5000) -- 连接 1s, 发送 2s, 读取 5s
1.6 连接池管理
sock:setkeepalive(timeout?, pool_size?)
将连接放回连接池。
- 参数:
timeout(number, 可选): 最大空闲时间,毫秒pool_size(number, 可选): 池大小
- 返回值: 1 或 nil, err
- 示例:
local ok, err = sock:setkeepalive(60000, 100)
sock:getreusedtimes()
获取连接被复用的次数。
- 返回值: number
- 示例:
local times = sock:getreusedtimes() if times > 100 then -- 连接复用次数过多,关闭重建 sock:close() end
1.7 关闭
sock:close()
关闭连接。
- 返回值: 1 或 nil, err
1.8 SSL 握手
sock:sslhandshake(session?, server_name?, verify?, ...)
执行 SSL 握手。
- 参数:
session(userdata, 可选): SSL 会话对象server_name(string, 可选): SNI 主机名verify(boolean, 可选): 是否验证证书
- 返回值: session 或 nil, err
- 示例:
local session, err = sock:sslhandshake(nil, "example.com", true)
1.9 Socket 选项
sock:setoption(option, value)
设置 socket 选项 (FFI 接口)。
| 选项 | 说明 |
|---|---|
ngx.HTTP_LUA_SOCKOPT_KEEPALIVE |
SO_KEEPALIVE |
ngx.HTTP_LUA_SOCKOPT_TCP_NODELAY |
TCP_NODELAY |
ngx.HTTP_LUA_SOCKOPT_SNDBUF |
SO_SNDBUF |
ngx.HTTP_LUA_SOCKOPT_RCVBUF |
SO_RCVBUF |
ngx.HTTP_LUA_SOCKOPT_REUSEADDR |
SO_REUSEADDR |
二、UDP Socket API
2.1 创建 Socket
ngx.socket.udp()
创建 UDP socket 对象。
2.2 设置对端
sock:setpeername(host, port)
设置目标地址。
- 参数:
host(string): 主机名或 IPport(number): 端口号
- 示例:
local sock = ngx.socket.udp() sock:setpeername("127.0.0.1", 53)
2.3 发送
sock:send(data)
发送数据报。
- 参数:
data(string) - 返回值: 1 或 nil, err
2.4 接收
sock:receive(size?)
接收数据报。
- 参数:
size(number, 可选) - 最大 65536 字节 - 返回值: data 或 nil, err
2.5 绑定本地地址
sock:bind(address)
绑定本地地址。
三、内部实现机制
3.1 非阻塞原理
Cosocket 通过以下机制实现非阻塞:
- 协程挂起: 调用
lua_yield()挂起 Lua 协程 - 事件注册: 向 Nginx 事件循环注册读写事件
- 恢复执行: 事件触发时通过
resume_handler恢复协程
3.2 连接池实现
连接池结构:
┌─────────────────────────────────────┐
│ pool = "host:port" │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐│
│ │conn 1 │→│conn 2 │→│conn N ││
│ └─────────┘ └─────────┘ └─────────┘│
│ free_queue │
└─────────────────────────────────────┘
关键数据结构:
typedef struct {
ngx_queue_t queue; // 空闲队列链接
ngx_connection_t *connection; // 缓存的连接
ngx_str_t host;
ngx_uint_t port;
// ...
} ngx_http_lua_socket_pool_item_t;
3.3 请求 Socket
ngx.req.socket(raw?)
获取请求的 raw socket。
- 参数:
raw(boolean) - 是否原始模式 - 返回值: cosocket 对象
- 示例:
local sock, err = ngx.req.socket() sock:receive() -- 接收客户端数据 sock:send(data) -- 发送响应
四、最佳实践
4.1 连接池使用
local function query_redis()
local sock = ngx.socket.tcp()
sock:settimeout(1000)
-- 使用连接池
local ok, err = sock:connect("127.0.0.1", 6379, {pool = "redis"})
if not ok then
return nil, err
end
local bytes, err = sock:send("PING\r\n")
local data, err = sock:receive()
-- 放回连接池而非关闭
sock:setkeepalive(60000, 100)
return data
end
4.2 错误处理
local function safe_request()
local sock = ngx.socket.tcp()
local ok, err = sock:connect("127.0.0.1", 80)
if not ok then
ngx.log(ngx.ERR, "connect failed: ", err)
return nil, err
end
local bytes, err = sock:send("GET / HTTP/1.0\r\n\r\n")
if not bytes then
sock:close()
return nil, err
end
local reader = sock:receiveuntil("\r\n\r\n")
local headers, err = reader()
if not headers then
sock:close()
return nil, err
end
sock:setkeepalive()
return headers
end