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。

相关推荐
努力努力再努力wz8 小时前
【C++ 高阶系列】C++ 高性能网络编程:当 Reactor 遇见协程:从 epoll 回调到异步 MySQL,彻底打通任务挂起与恢复
网络·c++·mysql
loulanyue_8 小时前
当AI学会自主攻击与渗透破壁:Agent行为链失控与Mythos级攻防颠覆下的安全范式重构——读CNCC2026第六届网络生态治理论坛实录
网络·人工智能·安全
andrsted8 小时前
企业培训时间难统一?练题簿让在线免费刷题与培训考核更好安排
学习·微信小程序·小程序·学习方法·刷题
李航19838 小时前
虚拟盖房子,在游戏中学习房子的建造过程
人工智能·学习·游戏·计算机视觉·ai编程
霍霍的袁8 小时前
【C++11】左值右值引用与移动语义详细笔记(左值右值、右值引用、移动构造、移动赋值、引用折叠、万能转发)
c++·笔记·学习·visual studio
2401_868534788 小时前
英语演讲:麻省理工校长2024年毕业典礼演讲
服务器·网络
外收内放8 小时前
FastAPI猜字谜实战(6):日志与异常
开发语言·python·学习·fastapi
码完就睡9 小时前
Linux——以太网协议
运维·服务器·网络·1024程序员节
CV爱数码9 小时前
论文精读|RED-CNN:残差编解码网络,低剂量 CT 去噪的经典基线
网络·人工智能·算法·cnn·ct·重建·x-ray
知识分享小能手9 小时前
C学习教程,从入门到精通,C语言单片机基础 — 完整知识点手册(19)
c语言·单片机·学习