skynet——网络模块学习

设计原理

epoll/kqueue + 非阻塞

设计巧思:socket 线程只负责 IO 和封 message,不碰 Lua;worker 只从 mq 取 message 调 Lua,不碰 fd。两边靠 mq 解耦,完全没有"IO 线程 vs 业务线程抢同一份数据"的问题。

设计初衷

Skynet 不强制协议,不定义 RPC,不规定编码,甚至"不定义出类似 TCP 连接的概念"------socket 层只搬字节流。

与其他语言对比

  • Go net包:bufio.Reader、textproto、http全家桶都绑在 net 上

  • Python asyncio.protocol:data_received()给你 bytes,但至少帮你分了 Protocol/Transport 两层

  • Skynet socket:C 层连分包都不管,一个 SOCKET_DATA就是一段 raw bytes,分包/协议解析全甩给 Lua 层的 Gate(gateserver.lua模板做 \r\n或自定义分包)

设计缘由

  • gate 是先于 socket 模块存在的历史产物,早期直接在内核做接入;后来抽了 C 层 socket API 出来,但协议层故意不放 C------游戏协议千奇百怪(二进制 / protobuf / sproto / 自定义),C 层写死了反而僵。Lua 层写 Gate 模板,用户自己改分包逻辑更方便。
  • 网络 IO 和业务消息共用同一套调度------worker 不用区分"这是网络事件还是服务 A 发给服务 B 的消息",都是 mq 里的一条
  • socket 线程和 worker 之间不用任何 mutex------pipe 发命令,mq 是 worker 单线程消费(每个 worker 拿自己绑的 mq 槽),天然无锁,代价是多一次 mq 入队 + 一次 Lua 层 dispatch,延迟比"epoll 直接回调"高一点点

socket模块

数据接收

复制代码
客户端 TCP
   ↓
C 层 socket_server(独立 socket 线程,epoll_wait)
   ↓  pipe 发命令 / 收事件
   ↓  socket_server_poll 返回 SOCKET_DATA / OPEN / CLOSE ...
   ↓  封装成 skynet_message 投进 对应服务的次级 mq
   ↓  全局 mq
   ↓  worker 线程消费 → 调 Lua 服务 callback(Gate/Agent)

核心数据结构

全局上下文

c 复制代码
// socket_server.c
struct socket_server {
    int recvctrl_fd;   // pipe 读端(socket 线程读 worker 的命令)
    int sendctrl_fd;   // pipe 写端(worker 写命令给 socket 线程)
    poll_fd event_fd;  // epoll fd(Linux) / kqueue fd(Mac)
    int alloc_id;      // 下一个分配的 socket id(全局递增)
    int event_n;       // 本次 epoll_wait 返回的事件数
    int event_index;   // 正在处理的事件下标
    struct socket slot[MAX_SOCKET];  // socket 池,默认 16K 个
    fd_set rfds;       // select 用来非阻塞探 pipe
};

单个连接

c 复制代码
struct socket {
    uintptr_t opaque;      // 绑定的 Lua 服务地址(如 Gate),数据最终投给这
    struct wb_list high;   // 高优发送队列
    struct wb_list low;    // 低优发送队列
    int64_t wb_size;       // 发送缓冲区总字节(用来做限流报警)
    int fd;                // 内核 fd
    int id;                // slot 下标,lua 层看到的"socket id"
    uint8_t protocol;       // TCP / UDP
    uint8_t type;          // 状态机:LISTEN / CONNECTING / CONNECTED / CLOSE ...
};

工作流

c 复制代码
// skynet_start.c
static void *thread_socket(void *p) {
    struct monitor *m = p;
    skynet_initthread(THREAD_SOCKET);
    for (;;) {
        int r = skynet_socket_poll();
        if (r == 0) break;    // 退出
        if (r < 0) continue;  // 这帧没出 message,跳过
        wakeup(m, 0);         // 有消息产生了,唤醒 worker 来取
    }
}
c 复制代码
int socket_server_poll(struct socket_server *ss,
                       struct socket_message *result,
                       int *more)
{
    for (;;) {
        // ① 先看 pipe 有没有待处理命令
        if (ss->checkctrl && has_cmd(ss)) {
            int type = ctrl_cmd(ss, result);  // ⭐在 socket 线程里执行
            if (type != -1) return type;
            continue;
        }

        // ② epoll_wait 拿一批事件
        if (ss->event_index == ss->event_n) {
            ss->event_n = sp_wait(ss->event_fd, ss->ev, MAX_EVENT);
            ss->checkctrl = 1;
            ss->event_index = 0;
            if (ss->event_n <= 0) return -1;
        }

        // ③ 处理事件:accept / tcp-read / udp / error ...
        struct event *e = &ss->ev[ss->event_index++];
        struct socket *s = e->s;
        if (s == NULL) continue; // pipe 事件已在上一步消化

        // default 分支:TCP 可读 → forward_message_tcp
        //              TCP 可写 → flush write buffer
    }
}

ctrl_cmd的 switch

c 复制代码
static int ctrl_cmd(struct socket_server *ss, struct socket_message *result) {
    uint8_t header[2];
    block_readpipe(ss->recvctrl_fd, header, 2);
    int type = header[0];
    int len  = header[1];
    uint8_t buffer[256];
    block_readpipe(ss->recvctrl_fd, buffer, len);

    switch (type) {
    case 'L': return listen_socket(ss,(...*)buffer, result);
    case 'O': return open_socket(ss,(...*)buffer, result);   // connect
    case 'K': return close_socket(ss,(...*)buffer, result);
    case 'D': return send_socket(ss, ..., PRIORITY_HIGH, NULL); // write-high
    case 'P': return send_socket(ss, ..., PRIORITY_LOW, NULL);  // write-low
    case 'S': return start_socket(ss,(...*)buffer, result);    // 开始监听读
    case 'R': return resume_socket(ss,(...*)buffer, result);
    case 'X': return SOCKET_EXIT;
    default: ...
    }
}
  1. 先看 pipe 有没有 worker 发来的命令(listen / start / send / close等)

    Worker 不是直接调 epoll_ctl,而是把请求打包成 struct request_package,通过 sendctrl_fd写进 pipe,socket 线程在 ctrl_cmd()里读出来执行。这样 worker 和 socket 线程之间零锁------pipe 本身线程安全,slot 只有 socket 线程改。

  2. 再 sp_wait()等 epoll 事件

    有事件(新连接 / 可读 / 可写)就处理,产出 SOCKET_OPEN / SOCKET_ACCEPT / SOCKET_DATA / SOCKET_CLOSE等 message,填进 struct socket_message,返回给 skynet_socket_poll,再封装成 skynet_message投进 opaque对应服务的 mq。

相关推荐
hPw0eKIqD7 分钟前
一步一步学习使用FireMonkey动画() 使用动画组件为窗体添加动态效果
学习
red_redemption1 小时前
自由学习记录(210)
学习
llilian_161 小时前
便携式相位测量模块应用方案的核心优势
网络·功能测试·单片机
MartinYeung52 小时前
[论文学习]PACT:溯源感知能力合约——面向智能体安全的参数级溯源
人工智能·学习·安全
QH139292318802 小时前
# R&S ZNB43 ZNA43 ZNA67矢量网络分析仪
开发语言·网络·php
栋***t2 小时前
2026年不止防作弊,麦塔在线培训系统培训与考试安全的体系化设计
网络·安全
小O的算法实验室3 小时前
AAAI-26,全局相关性学习:通过谱图神经网络增强进化算法
神经网络·学习·算法
宁风NF3 小时前
JavaScript:网络请求与前端通信
开发语言·前端·javascript·网络·学习·ecmascript
孪生质数-4 小时前
AI Agent 工程实践(一):大模型 API 接入示范
网络·人工智能·ai·chatgpt·github·claude·claudecode
雾里0不看花4 小时前
【App Service Linux】在Linux App Service中安装 tcpdump 并抓取网络包
linux·网络·tcpdump