主题
网络工具例程 4:手写 TCP 服务器(支持多个客户端)
这一页只讲这一个例程。同组其余例程、以及这个库的函数说明都在 05-05-net。
适用:需要自定义协议的 TCP 服务端 | 依赖模块:net timer | 难度:★★★
场景:tcp_server 模块够用时优先用它;这里演示用 net 从零搭一个能同时带多个客户端的回显服务器。
完整代码(可直接下载运行):
lua
local PORT = 8000
local srv, e = net.socket("TCP")
if not srv then
print("建不出来:", net.errstr(e))
return
end
srv:setoption("reuseaddr") -- 重启后端口能立刻再绑
if srv:bind("0.0.0.0", PORT) ~= 0 then -- 服务器必须给端口
print("端口绑定失败", PORT)
return
end
srv:listen(4)
print("TCP 服务器已启动:", PORT)
local clients = {} -- 在线客户端集合
timer.start(200, function()
-- 1) 收新连接:给 10ms 超时,没有新连接就立刻返回
local c = srv:accept(10)
if c then
clients[c] = true
local ip = c:peer()
print("新客户端:", net.unpackip(ip, "*s"))
end
-- 2) 收数据:只对"有话要说"的客户端 recv,不会被沉默的连接堵住
local list = {}
for s in pairs(clients) do list[#list + 1] = s end
if #list == 0 then return end
for _, s in ipairs(net.select(list, 0)) do
local data, rerr = s:recv(256)
if rerr == 0 and #data > 0 then
s:send("echo:" .. data)
elseif rerr == 2 then -- 2 = 对端已断开
s:close()
clients[s] = nil -- 不剔除就一直占着那 16 个名额
end
end
end)要点:
- 顺序是
socket→setoption("reuseaddr")→bind(端口必填)→listen→ 周期accept。 accept只在真的等到连接时返回句柄,超时返回nil+1(超时),所以放在定时器里反复调用就行,一个服务器能接多个客户端。- 多客户端必须配
net.select:直接对每个客户端调recv会被第一个没数据的连接卡到超时。 recv返回2(连接已关闭)说明对端断开,要close并从集合里剔除;忘了 close 也会在句柄被回收时关掉,但主动关更可控。
验证方法:
- 电脑上用
nc 192.168.1.x 8000(或串口工具的 TCP 客户端)连上,发一段文字,会收到echo:前缀的回包。 - 同时开两个客户端,两边都能收到各自的回包(验证
select没被其中一个堵死)。 - 关掉一个客户端,服务器日志不再报错,另一个照常工作。
注意事项:
- 忘记
listen直接accept会报「socket 还没 listen」。 accept()不传超时表示一直等,放在定时器里会把整个 Lua 卡死;要轮询就传一个小超时(如 10)。- 忘了
reuseaddr,重启脚本后bind会失败(端口还被上次的连接占着)。