设计原理epoll/kqueue 非阻塞设计巧思socket 线程只负责 IO 和封 message不碰 Luaworker 只从 mq 取 message 调 Lua不碰 fd。两边靠 mq 解耦完全没有IO 线程 vs 业务线程抢同一份数据的问题。设计初衷Skynet 不强制协议不定义 RPC不规定编码甚至不定义出类似 TCP 连接的概念——socket 层只搬字节流。与其他语言对比Go net包bufio.Reader、textproto、http全家桶都绑在 net 上Python asyncio.protocoldata_received()给你 bytes但至少帮你分了 Protocol/Transport 两层Skynet socketC 层连分包都不管一个 SOCKET_DATA就是一段 raw bytes分包/协议解析全甩给 Lua 层的 Gategateserver.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 服务 callbackGate/Agent核心数据结构全局上下文// socket_server.cstructsocket_server{intrecvctrl_fd;// pipe 读端socket 线程读 worker 的命令intsendctrl_fd;// pipe 写端worker 写命令给 socket 线程poll_fd event_fd;// epoll fdLinux / kqueue fdMacintalloc_id;// 下一个分配的 socket id全局递增intevent_n;// 本次 epoll_wait 返回的事件数intevent_index;// 正在处理的事件下标structsocketslot[MAX_SOCKET];// socket 池默认 16K 个fd_set rfds;// select 用来非阻塞探 pipe};单个连接structsocket{uintptr_topaque;// 绑定的 Lua 服务地址如 Gate数据最终投给这structwb_listhigh;// 高优发送队列structwb_listlow;// 低优发送队列int64_twb_size;// 发送缓冲区总字节用来做限流报警intfd;// 内核 fdintid;// slot 下标lua 层看到的socket iduint8_tprotocol;// TCP / UDPuint8_ttype;// 状态机LISTEN / CONNECTING / CONNECTED / CLOSE ...};工作流// skynet_start.cstaticvoid*thread_socket(void*p){structmonitor*mp;skynet_initthread(THREAD_SOCKET);for(;;){intrskynet_socket_poll();if(r0)break;// 退出if(r0)continue;// 这帧没出 message跳过wakeup(m,0);// 有消息产生了唤醒 worker 来取}}intsocket_server_poll(structsocket_server*ss,structsocket_message*result,int*more){for(;;){// ① 先看 pipe 有没有待处理命令if(ss-checkctrlhas_cmd(ss)){inttypectrl_cmd(ss,result);// ⭐在 socket 线程里执行if(type!-1)returntype;continue;}// ② epoll_wait 拿一批事件if(ss-event_indexss-event_n){ss-event_nsp_wait(ss-event_fd,ss-ev,MAX_EVENT);ss-checkctrl1;ss-event_index0;if(ss-event_n0)return-1;}// ③ 处理事件accept / tcp-read / udp / error ...structevent*ess-ev[ss-event_index];structsocket*se-s;if(sNULL)continue;// pipe 事件已在上一步消化// default 分支TCP 可读 → forward_message_tcp// TCP 可写 → flush write buffer}}ctrl_cmd的 switchstaticintctrl_cmd(structsocket_server*ss,structsocket_message*result){uint8_theader[2];block_readpipe(ss-recvctrl_fd,header,2);inttypeheader[0];intlenheader[1];uint8_tbuffer[256];block_readpipe(ss-recvctrl_fd,buffer,len);switch(type){caseL:returnlisten_socket(ss,(...*)buffer,result);caseO:returnopen_socket(ss,(...*)buffer,result);// connectcaseK:returnclose_socket(ss,(...*)buffer,result);caseD:returnsend_socket(ss,...,PRIORITY_HIGH,NULL);// write-highcaseP:returnsend_socket(ss,...,PRIORITY_LOW,NULL);// write-lowcaseS:returnstart_socket(ss,(...*)buffer,result);// 开始监听读caseR:returnresume_socket(ss,(...*)buffer,result);caseX:returnSOCKET_EXIT;default:...}}先看 pipe 有没有 worker 发来的命令listen / start / send / close等Worker 不是直接调 epoll_ctl而是把请求打包成 struct request_package通过 sendctrl_fd写进 pipesocket 线程在 ctrl_cmd()里读出来执行。这样 worker 和 socket 线程之间零锁——pipe 本身线程安全slot 只有 socket 线程改。再 sp_wait()等 epoll 事件有事件新连接 / 可读 / 可写就处理产出 SOCKET_OPEN / SOCKET_ACCEPT / SOCKET_DATA / SOCKET_CLOSE等 message填进 struct socket_message返回给 skynet_socket_poll再封装成 skynet_message投进 opaque对应服务的 mq。