Skynet.socket 函数族使用详解

目录

  • [Skynet.socket 函数族使用详解](#Skynet.socket 函数族使用详解)
  • 核心功能分类
  • [一、TCP 连接管理](#一、TCP 连接管理)
    • [1. 监听端口](#1. 监听端口)
    • [2. 建立连接](#2. 建立连接)
    • [3. 关闭连接](#3. 关闭连接)
  • 二、数据读写操作
    • [1. 阻塞式读取](#1. 阻塞式读取)
    • [2. 写入数据](#2. 写入数据)
      • [2.1 `socket.write(fd, data)` 的返回值](#2.1 socket.write(fd, data) 的返回值)
      • [2.2 示例代码](#2.2 示例代码)
      • [2.3 关键注意事项](#2.3 关键注意事项)
      • [2.4 与其他函数的区别](#2.4 与其他函数的区别)
      • [2.5 底层原理](#2.5 底层原理)
      • [2.6 总结](#2.6 总结)
  • [三、UDP 处理](#三、UDP 处理)
    • [1. 创建 UDP 句柄](#1. 创建 UDP 句柄)
    • [2. 发送 UDP 数据](#2. 发送 UDP 数据)
  • 四、高级控制与监控
    • [1. 缓冲区过载警告](#1. 缓冲区过载警告)
    • [2. 域名解析](#2. 域名解析)
  • [五、SocketChannel 封装](#五、SocketChannel 封装)
    • [1. 创建 Channel 对象](#1. 创建 Channel 对象)
    • [2. 发送请求](#2. 发送请求)
  • 六、最佳实践与注意事项
  • 总结

Skynet.socket 函数族使用详解

Skynet 的 skynet.socket 模块提供了 TCP/UDP 网络通信的核心 API,结合协程机制实现了阻塞式调用模型,简化了异步网络编程。本文详细解析其核心函数、使用场景及最佳实践。


核心功能分类

  1. TCP 连接管理(监听、连接、关闭)
  2. 数据读写(阻塞式读写、分包处理)
  3. UDP 支持(数据包收发、地址管理)
  4. 高级控制(缓冲区警告、域名解析、过载处理)

一、TCP 连接管理

1. 监听端口

lua 复制代码
local socket = require "skynet.socket"

-- 启动 TCP 服务器
skynet.start(function()
    local listen_fd = socket.listen("0.0.0.0", 8888) -- 监听 8888 端口
    socket.start(listen_fd, function(client_fd, addr)
        -- 新连接回调,处理客户端请求
        socket.start(client_fd)
        -- ... 处理数据逻辑
    end)
end)
  • socket.listen(host, port [, backlog])
    返回监听套接字的文件描述符 listen_fd
    backlog:等待连接队列的最大长度(可选,默认 SOMAXCONN)。

2. 建立连接

lua 复制代码
local client_fd = socket.open("127.0.0.1", 6379) -- 连接 Redis
if client_fd then
    socket.start(client_fd)
    socket.write(client_fd, "PING\r\n")
end
  • socket.open(host, port)
    同步阻塞连接目标地址,返回客户端套接字 client_fd

3. 关闭连接

lua 复制代码
socket.close(client_fd)  -- 安全关闭,等待未完成读写
socket.close_fd(client_fd) -- 强制立即关闭(慎用)
socket.shutdown(client_fd) -- 强制关闭(适用于 __gc 元方法)
  • 区别
    • close:等待其他协程完成读写后关闭。
    • close_fd/shutdown:直接关闭,可能导致未处理数据丢失。

二、数据读写操作

1. 阻塞式读取

lua 复制代码
-- 读取固定字节
local data, partial = socket.read(client_fd, 1024) -- 读 1024 字节
if data then
    print("完整数据:", data)
else
    print("部分数据:", partial) -- 连接已关闭
end

-- 读取一行(默认以 \n 分割)
local line = socket.readline(client_fd, "\r\n") -- 自定义分隔符
  • socket.read(fd, sz)
    • sznil 时读取尽可能多的数据(至少 1 字节)。
    • 返回完整数据或 false + 已读部分数据(连接关闭时)。

2. 写入数据

lua 复制代码
socket.write(client_fd, "Hello Skynet!\r\n") -- 高优先级写入
socket.lwrite(client_fd, "Low priority data\r\n") -- 低优先级写入
  • 优先级区别
    • write:数据进入高优先级队列,优先发送。
    • lwrite:数据进入低优先级队列,高优先级队列为空时发送。
      在 Skynet 框架中,socket.write 方法的返回值取决于数据是否成功写入内核的发送缓冲区。以下是具体说明:

2.1 socket.write(fd, data) 的返回值

  1. 成功时

    • 返回 true,表示数据已成功加入内核的发送队列,不保证对端已接收
    • 注意:返回值仅表示数据成功提交到操作系统的网络协议栈,实际网络传输是异步的。
  2. 失败时

    • 返回 nil + 错误信息 (如 "closed" 表示连接已关闭)。
    • 常见错误:
      • "closed": 连接已关闭。
      • "timeout": 发送超时(需结合 socketdriver.settimeout 设置)。
      • "error": 其他底层错误。

2.2 示例代码

lua 复制代码
local skynet = require "skynet"
local socket = require "skynet.socket"

local fd = ...  -- 假设 fd 是已建立的客户端连接

-- 尝试发送数据
local ok, err = socket.write(fd, "Hello World")
if not ok then
    skynet.error("Send failed:", err)
    socket.close(fd)  -- 关闭失效连接
end

2.3 关键注意事项

  1. 异步发送socket.write 是非阻塞的,数据可能仍在发送队列中未实际传输。
  2. 流量控制:若发送速度超过网络带宽或对端接收速度,可能导致缓冲区积压,最终触发错误。
  3. 错误处理 :务必检查返回值,及时关闭失效的 fd,避免资源泄漏。
  4. 大包分片:单次写入数据过大可能被系统拆分,需结合业务逻辑处理完整性(如添加长度头)。

2.4 与其他函数的区别

  • socket.send :与 socket.write 行为一致,两者是别名关系。
  • socket.lwrite:专用于发送 Lua 字符串(内部优化),行为相同。

2.5 底层原理

Skynet 的 socket.write 最终调用操作系统的 send 系统调用,但通过非阻塞模式封装。若内核发送缓冲区已满,数据会排队等待,此时返回 true;若连接已异常(如对端关闭),则直接返回错误。


2.6 总结

  • 返回值意义true 表示数据提交成功,nil + err 表示失败。
  • 必须处理错误 :尤其要捕获 "closed" 错误,及时清理连接状态。
  • 性能影响 :高频发送时建议结合 socketdriver.setqueue_max 控制缓冲区大小,避免内存暴涨。

三、UDP 处理

1. 创建 UDP 句柄

lua 复制代码
local udp_fd = socket.udp(function(data, from)
    print("收到 UDP 数据:", data, "来源:", socket.udp_address(from))
end, "0.0.0.0", 9999) -- 绑定 9999 端口
  • socket.udp(callback [, host, port])
    创建 UDP 句柄并绑定回调,收到数据时触发 callback(data, from)

2. 发送 UDP 数据

lua 复制代码
socket.sendto(udp_fd, from_address, "ACK") -- 发送到指定地址
socket.write(udp_fd, "Ping") -- 若已设置默认地址,直接写入
  • socket.sendto(fd, from, data)
    from 为接收到的来源地址字符串,不可手动构造。

四、高级控制与监控

1. 缓冲区过载警告

lua 复制代码
socket.warning(client_fd, function(fd, size)
    if size > 0 then
        print("警告:待发数据超过", size, "KB")
    else
        print("缓冲区已清空")
    end
end)
  • socket.warning(fd, callback)
    监控待发数据量,超过 1MB 触发回调(默认每超 64KB 打印错误日志)。

2. 域名解析

lua 复制代码
local dns = require "skynet.dns"
dns.server("8.8.8.8") -- 设置 DNS 服务器
local ip, all_ips = dns.resolve("www.example.com") -- 解析域名
  • dns.resolve(name [, ipv6])
    返回解析到的 IP 地址及所有 IP 列表,避免阻塞 socket 线程。

五、SocketChannel 封装

1. 创建 Channel 对象

lua 复制代码
local sc = require "skynet.socketchannel"
local channel = sc.channel {
    host = "127.0.0.1",
    port = 6379,
    response = function(sock)
        return true, sock:readline("\r\n") -- 解析 Redis 响应
    end,
}
  • 模式选择
    • 提供 response 函数则进入 Session 模式(如 MongoDB)。
    • 否则为 请求-回应模式(如 Redis)。

2. 发送请求

lua 复制代码
local resp = channel:request("PING\r\n") -- 请求并等待响应
local resp2 = channel:request("GET key\r\n", function(sock)
    return true, sock:read(5) -- 自定义响应解析
end)
  • channel:request(req [, response | session])
    发送请求并自动匹配响应,支持自定义解析逻辑。

六、最佳实践与注意事项

  1. 连接生命周期管理

    • 使用 socket.close 确保安全关闭。
    • 避免在 __gc 中使用阻塞操作,优先用 shutdown
  2. 协程调度优化

    • 高频读写时,合理使用 socket.lwrite 避免阻塞关键数据。
    • 结合 skynet.fork 处理并发请求。
  3. 错误处理

    • 所有读写操作需包裹在 pcall 中捕获异常。
    • UDP 需处理乱序和丢包,不可依赖时序。
  4. 性能监控

    • 使用 socket.warning 监控缓冲区,防止内存溢出。
    • 避免频繁 DNS 查询,通过缓存或独立服务处理。

总结

skynet.socket 通过协程化阻塞 API 简化了网络编程复杂度,结合 socketchannel 可高效处理复杂协议。开发者需注意:

  • 连接安全性:合理关闭连接,避免资源泄漏。
  • 协议适配:根据场景选择基础 API 或高级封装。
  • 性能调优:监控缓冲区,平衡吞吐量与内存消耗。

通过阅读 lualib/socket.lua 和参考 service/gate.lua,可深入理解底层实现机制。

相关推荐
慢慢沉19 小时前
Lua(数据库访问)
开发语言·数据库·lua
慢慢沉19 小时前
Lua协同程序(coroutine)
lua
慢慢沉2 天前
Lua元表(Metatable)
lua
慢慢沉2 天前
Lua(字符串)
开发语言·lua
慢慢沉2 天前
Lua(数组)
开发语言·lua
慢慢沉2 天前
Lua(迭代器)
开发语言·lua
慢慢沉2 天前
Lua基本语法
开发语言·lua
Feng.Lee3 天前
接口测试Postman工具高级使用技巧
功能测试·测试工具·lua·postman·可用性测试
三翼鸟数字化技术团队3 天前
鸿蒙平台运行Lua脚本
lua·harmonyos