skynet.socket 完整通信流程
目录
- 一、服务端(Server)通信流程
- 1. 启动监听
- 2. 接受客户端连接
- 3. 接收客户端数据
- 4. 主动关闭连接
- 二、客户端(Client)通信流程
- 1. 连接服务端
- 2. 接收服务端数据
- 3. 主动发送数据
- 4. 断开连接
- 三、完整交互时序图
- 四、关键注意事项
- 1. 非阻塞与协程
- 2. 粘包/分包处理
- 3. 错误处理
- 五、性能优化建议
- 六、示例代码整合
- 服务端完整代码
- 客户端完整代码
以下是基于 Skynet 框架的 Socket 通信流程详解,涵盖服务端与客户端的交互全过程,包括核心 API 使用、数据收发逻辑及示例代码:
一、服务端(Server)通信流程
1. 启动监听
- API:
socket.listen(host, port, backlog)
- 功能: 绑定 IP 和端口,开启监听。
- 参数:
host
: 监听地址(如"0.0.0.0"
表示所有网卡)。port
: 监听端口。backlog
: 等待连接队列的最大长度。
- 返回值: 监听句柄
id
,用于后续操作。
local skynet = require "skynet"
local socket = require "skynet.socket"
-- 启动服务端
skynet.start(function()
local listen_id = socket.listen("0.0.0.0", 8888, 128)
skynet.error("Server listening on port 8888")
socket.start(listen_id, accept_handler) -- 注册 accept_handler 处理新连接
end)
2. 接受客户端连接
- API:
socket.start(fd, accept_cb)
- 功能: 将监听句柄
fd
托管给 Skynet,当有新连接时触发accept_cb
。
- 功能: 将监听句柄
- 回调函数:
accept_handler(fd, addr)
- 参数:
fd
: 新连接的客户端 Socket 句柄。addr
: 客户端地址(如"127.0.0.1:12345"
)。
- 参数:
local function accept_handler(client_fd, addr)
skynet.error("New client connected:", addr)
socket.start(client_fd, receive_handler) -- 注册 receive_handler 处理数据
end
3. 接收客户端数据
- 回调函数:
receive_handler(fd, data)
- 触发条件: 当客户端发送数据时,Skynet 自动调用。
- 参数:
fd
: 客户端 Socket 句柄。data
: 收到的二进制数据(可能不完整)。
- 注意: 需自行处理粘包/分包(参考前文的分包策略)。
local function receive_handler(client_fd, data)
skynet.error("Received data from client", client_fd, ":", data)
-- 示例:回显数据
socket.write(client_fd, "Echo: " .. data)
end
4. 主动关闭连接
- API:
socket.close(fd)
- 功能: 关闭指定 Socket 连接。
-- 在适当位置调用
socket.close(client_fd)
二、客户端(Client)通信流程
1. 连接服务端
- API:
socket.open(host, port)
- 功能: 发起 TCP 连接到服务端。
- 参数: 同
socket.listen
。 - 返回值: 客户端 Socket 句柄
fd
。
local skynet = require "skynet"
local socket = require "skynet.socket"
skynet.start(function()
local server_fd = socket.open("127.0.0.1", 8888)
if server_fd then
skynet.error("Connected to server")
socket.start(server_fd, client_receive_handler)
-- 发送数据示例
socket.write(server_fd, "Hello Server!")
else
skynet.error("Connection failed")
end
end)
2. 接收服务端数据
- 回调函数:
client_receive_handler(fd, data)
- 逻辑与服务端
receive_handler
类似。
- 逻辑与服务端
local function client_receive_handler(fd, data)
skynet.error("Received from server:", data)
end
3. 主动发送数据
- API:
socket.write(fd, data)
- 功能: 向指定 Socket 写入数据。
- 注意: 数据需为字符串格式。
socket.write(server_fd, "Another message")
4. 断开连接
- 与服务端关闭方式相同:
socket.close(server_fd)
三、完整交互时序图
Client Server
| |
|--- socket.open() ---->| 建立连接
| |--触发 accept_handler
| |
|--- socket.write() ---->| 发送数据
| |--触发 receive_handler
|<--- socket.write() ----| 服务端响应
| |
|--- socket.close() ---->| 断开连接
四、关键注意事项
1. 非阻塞与协程
- Skynet 的 Socket 操作是非阻塞的,所有收发操作通过回调异步处理。
- 若需同步等待数据,可在协程中使用
socket.read
:local data = socket.read(fd, len) -- 阻塞协程直到读取 len 字节
2. 粘包/分包处理
- 必须设计应用层协议(如长度前缀、分隔符)确保数据完整性。
- 示例协议解析(长度前缀):
-- 服务端 receive_handler 中 local buffer = {} -- 按 fd 存储缓冲区 function receive_handler(fd, data) if not buffer[fd] then buffer[fd] = "" end buffer[fd] = buffer[fd] .. data while true do if #buffer[fd] < 2 then break end -- 假设长度字段为 2 字节 local len = string.unpack(">I2", buffer[fd]) if #buffer[fd] >= len + 2 then local packet = string.sub(buffer[fd], 3, len + 2) process_packet(fd, packet) buffer[fd] = string.sub(buffer[fd], len + 3) else break end end end
3. 错误处理
- 监听或连接失败时返回
nil
。 - 连接异常关闭时,回调函数的
data
参数为nil
:function receive_handler(fd, data) if not data then skynet.error("Connection closed by peer:", fd) socket.close(fd) return end -- 正常处理数据 end
五、性能优化建议
-
减少内存拷贝:
- 使用
table
存储分片数据,最后用table.concat
合并。
local buffer_tbl = {} function receive_handler(fd, data) buffer_tbl[fd] = buffer_tbl[fd] or {} table.insert(buffer_tbl[fd], data) -- ...解析协议后清理 buffer_tbl end
- 使用
-
连接池管理:
- 对高频短连接场景,复用 Socket 句柄减少开销。
-
协议设计:
- 优先使用二进制协议(如
string.pack
/string.unpack
)减少传输体积。
- 优先使用二进制协议(如
六、示例代码整合
服务端完整代码
local skynet = require "skynet"
local socket = require "skynet.socket"
local clients = {} -- 存储客户端 fd
local function process_packet(fd, packet)
skynet.error("Process packet from", fd, ":", packet)
socket.write(fd, "ACK: " .. packet)
end
local function receive_handler(fd, data)
if not data then
skynet.error("Client disconnected:", fd)
socket.close(fd)
clients[fd] = nil
return
end
-- 假设协议为长度前缀(2字节)
if not clients[fd] then
clients[fd] = { buffer = "" }
end
clients[fd].buffer = clients[fd].buffer .. data
while true do
if #clients[fd].buffer < 2 then break end
local len = string.unpack(">I2", clients[fd].buffer)
if #clients[fd].buffer >= len + 2 then
local packet = string.sub(clients[fd].buffer, 3, len + 2)
process_packet(fd, packet)
clients[fd].buffer = string.sub(clients[fd].buffer, len + 3)
else
break
end
end
end
local function accept_handler(fd, addr)
skynet.error("New client:", addr)
clients[fd] = { buffer = "" }
socket.start(fd, receive_handler)
end
skynet.start(function()
local listen_fd = socket.listen("0.0.0.0", 8888, 128)
socket.start(listen_fd, accept_handler)
skynet.error("Server started on 8888")
end)
客户端完整代码
local skynet = require "skynet"
local socket = require "skynet.socket"
skynet.start(function()
local fd = socket.open("127.0.0.1", 8888)
if not fd then
skynet.error("Connect failed")
return
end
socket.start(fd, function(fd, data)
if not data then
skynet.error("Server closed")
return
end
skynet.error("Received:", data)
end)
-- 发送带长度前缀的数据包
local msg = "Hello World"
local len = #msg
local packed = string.pack(">I2", len) .. msg
socket.write(fd, packed)
end)
通过以上流程,可清晰理解 Skynet 中 Socket 通信的完整生命周期。实际开发中需根据业务需求调整协议解析和错误处理逻辑。