skynet Socket 线程详解

Socket 线程详解

目录

  1. 概述
  2. 主要职责
  3. [I/O 多路复用](#I/O 多路复用)
  4. 代码实现
  5. 工作流程
  6. 网络事件处理
  7. [Socket 管理](#Socket 管理)
  8. [与 Worker 线程的交互](#与 Worker 线程的交互)

概述

Socket 线程 是 Skynet 的网络 I/O 处理线程,负责监听和处理所有网络事件,包括 TCP、UDP、Unix 域套接字等。Socket 线程使用 I/O 多路复用技术(Linux: epoll, macOS: kqueue, Windows: IOCP)高效处理大量并发连接。

特点:

  • 使用 I/O 多路复用,支持高并发
  • 异步非阻塞 I/O,不阻塞其他线程
  • 将网络事件转换为 Skynet 消息
  • 支持 TCP、UDP、Unix 域套接字

文件位置 : skynet/skynet-src/socket_server.c

线程数量: 1 个


主要职责

1. 监听网络事件

功能: 使用 I/O 多路复用监听所有 socket

监听的事件类型:

  • EPOLLIN: 可读事件(数据到达)
  • EPOLLOUT: 可写事件(缓冲区可用)
  • EPOLLERR: 错误事件
  • EPOLLHUP: 对端关闭

2. 处理网络 I/O

功能: 读取和写入网络数据

操作:

  • 接受新的连接(accept)
  • 读取数据(read)
  • 写入数据(write)
  • 关闭连接(close)

3. 转换为 Skynet 消息

功能: 将网络事件转换为 Skynet 消息发送到对应服务

消息类型:

  • PTYPE_SOCKET: Socket 事件消息

I/O 多路复用

epoll 原理

epoll 是 Linux 特有的 I/O 多路复用机制,比 select/poll 更高效。

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      epoll 工作原理                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 创建 epoll 实例                                          │
│     int epfd = epoll_create1(0);                            │
│                                                             │
│  2. 添加 socket 到 epoll                                     │
│     struct epoll_event ev;                                  │
│     ev.events = EPOLLIN;  // 监听可读事件                    │
│     ev.data.fd = sockfd;                                    │
│     epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);            │
│                                                             │
│  3. 等待事件发生                                             │
│     int n = epoll_wait(epfd, events, MAX_EVENTS, timeout);  │
│                                                             │
│  4. 处理事件                                                 │
│     for (i = 0; i < n; i++) {                               │
│         if (events[i].events & EPOLLIN) {                   │
│             // 可读事件,读取数据                             │
│         }                                                   │
│     }                                                       │
│                                                             │
│  5. 关闭 epoll                                               │
│     close(epfd);                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

epoll vs select/poll

特性 select poll epoll
最大连接数 1024 无限制 无限制
效率 O(n) O(n) O(1)
内存拷贝 每次拷贝 每次拷贝 无拷贝
跨平台 仅 Linux

跨平台支持

Skynet 根据不同的操作系统选择不同的 I/O 多路复用机制:

c 复制代码
#ifdef __linux__
    // Linux 使用 epoll
    #include <sys/epoll.h>
#elif defined(__APPLE__)
    // macOS 使用 kqueue
    #include <sys/event.h>
#elif defined(_WIN32)
    // Windows 使用 IOCP
    #include <windows.h>
#else
    // 其他系统使用 poll
    #include <poll.h>
#endif

代码实现

Socket 线程主函数

文件 : skynet/skynet-src/skynet_start.c

位置 : skynet_start.c:147-163

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) {
            CHECK_ABORT  // 检查是否所有服务已退出
            continue;   // 没有事件,继续等待
        }
        
        // 有事件处理,唤醒 worker
        wakeup(m, 0);
    }
    
    return NULL;
}

Socket 初始化

文件 : skynet/skynet-src/skynet_socket.c

c 复制代码
void
skynet_socket_init() {
    struct socket_server * s = socket_server_create();
    SOCKET_SERVER = s;
}

struct socket_server *
socket_server_create() {
    struct socket_server *s = skynet_malloc(sizeof(*s));
    
    // 初始化 epoll
#ifdef __linux__
    s->event_fd = epoll_create1(EPOLL_CLOEXEC);
#endif
    
    // 初始化 socket 管理数组
    s->slot = skynet_malloc(sizeof(struct socket *) * MAX_SOCKET);
    
    // 初始化其他字段
    s->alloc_id = 0;
    s->event_n = 0;
    
    return s;
}

添加 Socket 到监听

文件 : skynet/skynet-src/skynet_socket.c

c 复制代码
int
skynet_socket_listen(int listenfd) {
    struct socket_server *s = SOCKET_SERVER;
    
    // 创建 socket 对象
    struct socket *sck = socket_server_alloc(s, listenfd);
    
    // 设置为非阻塞
    socket_nonblocking(listenfd);
    
    // 添加到 epoll
#ifdef __linux__
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = listenfd;
    epoll_ctl(s->event_fd, EPOLL_CTL_ADD, listenfd, &ev);
#endif
    
    return listenfd;
}

处理网络事件

文件 : skynet/skynet-src/socket_server.c

c 复制代码
int
skynet_socket_poll() {
    struct socket_server *s = SOCKET_SERVER;
    
    // 等待网络事件
    int n = epoll_wait(s->event_fd, s->ev, MAX_EVENT, 1);
    
    // 处理事件
    int i;
    for (i = 0; i < n; i++) {
        struct socket *sck = s->slot[s->ev[i].data.fd];
        
        if (sck == NULL) {
            continue;
        }
        
        uint32_t events = s->ev[i].events;
        
        if (events & EPOLLIN) {
            // 可读事件
            socket_read(sck);
        }
        
        if (events & EPOLLOUT) {
            // 可写事件
            socket_write(sck);
        }
        
        if (events & (EPOLLERR | EPOLLHUP)) {
            // 错误或对端关闭
            socket_close(sck);
        }
    }
    
    return n;
}

工作流程

复制代码
┌─────────────────────────────────────────────────────────────┐
│                  Socket 线程工作流程                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  线程启动                                                    │
│     │                                                       │
│     ▼                                                       │
│  初始化线程局部存储                                           │
│  skynet_initthread(THREAD_SOCKET)                           │
│     │                                                       │
│     ▼                                                       │
│  初始化 epoll                                                │
│  epoll_create1()                                            │
│     │                                                       │
│     ▼                                                       │
│  主循环                                                     │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  while (!quit) {                                    │    │
│  │      │                                              │    │
│  │      ├─ skynet_socket_poll()                        │    │
│  │      │   │                                          │    │
│  │      │   ├─ epoll_wait()                            │    │
│  │      │   │   等待网络事件                            │    │
│  │      │   │                                          │    │
│  │      │   ├─ 处理事件                                 │    │
│  │      │   │   │                                      │    │
│  │      │   │   ├─ EPOLLIN (可读)                       │    │
│  │      │   │   │   ├─ socket_read()                   │    │
│  │      │   │   │   ├─ 读取数据                         │    │
│  │      │   │   │   └─ 发送消息到服务                   │    │
│  │      │   │   │                                      │    │
│  │      │   │   ├─ EPOLLOUT (可写)                      │    │
│  │      │   │   │   ├─ socket_write()                  │    │
│  │      │   │   │   ├─ 写入数据                         │    │
│  │      │   │   │   └─ 更新写缓冲区                     │    │
│  │      │   │   │                                      │    │
│  │      │   │   └─ EPOLLERR/EPOLLHUP                   │    │
│  │      │   │       ├─ socket_close()                  │    │
│  │      │   │       └─ 清理资源                         │    │
│  │      │   │                                          │    │
│  │      │   └─ 返回事件数量                             │    │
│  │      │                                              │    │
│  │      ├─ if (r == 0)                                 │    │
│  │      │   break  // 要求退出                          │    │
│  │      │                                              │    │
│  │      ├─ if (r < 0)                                  │    │
│  │      │   CHECK_ABORT                                │    │
│  │      │   continue  // 没有事件                       │    │
│  │      │                                              │    │
│  │      ├─ wakeup(m, 0)                                │    │
│  │      │   唤醒 worker 线程                            │    │
│  │      │                                              │    │
│  │      └─ 继续循环                                     │    │
│  │  }                                                  │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                             │
│     ▼                                                       │
│  退出时清理                                                  │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  skynet_socket_free()                               │    │
│  │  ├─ 关闭所有 socket                                  │    │
│  │  ├─ 关闭 epoll                                       │    │
│  │  └─ 释放资源                                         │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                             │
│     ▼                                                       │
│  return NULL                                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

网络事件处理

Accept 事件

功能: 接受新的 TCP 连接

c 复制代码
static int
socket_accept(struct socket *s) {
    // 接受新连接
    int connfd = accept(s->fd, NULL, NULL);
    if (connfd < 0) {
        return -1;
    }
    
    // 创建新的 socket 对象
    struct socket *conn = socket_server_alloc(SOCKET_SERVER, connfd);
    
    // 添加到 epoll
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = connfd;
    epoll_ctl(SOCKET_SERVER->event_fd, EPOLL_CTL_ADD, connfd, &ev);
    
    // 发送连接消息到服务
    skynet_send(NULL, 0, s->service_handle, PTYPE_SOCKET, 
                SOCKET_ACCEPT, &connfd, sizeof(connfd));
    
    return connfd;
}

Read 事件

功能: 读取网络数据

c 复制代码
static int
socket_read(struct socket *s) {
    // 读取数据到缓冲区
    int n = read(s->fd, s->buffer, BUFFER_SIZE);
    
    if (n < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            return 0;  // 无数据可读
        }
        return -1;  // 错误
    }
    
    if (n == 0) {
        // 对端关闭连接
        socket_close(s);
        return 0;
    }
    
    // 发送数据到服务
    skynet_send(NULL, 0, s->service_handle, PTYPE_SOCKET, 
                SOCKET_DATA, s->buffer, n);
    
    return n;
}

Write 事件

功能: 写入网络数据

c 复制代码
static int
socket_write(struct socket *s) {
    if (s->wb_size == 0) {
        // 无数据可写
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = s->fd;
        epoll_ctl(SOCKET_SERVER->event_fd, EPOLL_CTL_MOD, s->fd, &ev);
        return 0;
    }
    
    // 写入数据
    int n = write(s->fd, s->wb, s->wb_size);
    
    if (n < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            return 0;  // 缓冲区满
        }
        return -1;  // 错误
    }
    
    // 更新写缓冲区
    s->wb += n;
    s->wb_size -= n;
    
    if (s->wb_size == 0) {
        // 数据全部发送完毕
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = s->fd;
        epoll_ctl(SOCKET_SERVER->event_fd, EPOLL_CTL_MOD, s->fd, &ev);
    }
    
    return n;
}

Socket 管理

Socket 数据结构

c 复制代码
struct socket {
    int fd;                    // 文件描述符
    uint32_t service_handle;    // 关联的服务句柄
    int type;                  // socket 类型
    int status;                // 状态
    char *buffer;              // 读缓冲区
    int buffer_size;           // 读缓冲区大小
    char *wb;                  // 写缓冲区
    int wb_size;               // 写缓冲区大小
    int wb_offset;             // 写缓冲区偏移
};

Socket 状态

c 复制代码
#define SOCKET_TYPE_INVALID 0
#define SOCKET_TYPE_LISTEN  1
#define SOCKET_TYPE_CONNECT 2
#define SOCKET_TYPE_HALFCLOSE 3

#define SOCKET_STATUS_UNCONNECTED 0
#define SOCKET_STATUS_CONNECTING 1
#define SOCKET_STATUS_CONNECTED 2

创建 Socket

Lua API:

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

-- 创建 TCP socket
local fd = socket.open("127.0.0.1", 8888)

-- 创建 UDP socket
local fd = socket.udp("127.0.0.1", 8888)

-- 监听 socket
local listenfd = socket.listen("0.0.0.0", 8888)

C API:

c 复制代码
// 创建并连接
int fd = skynet_socket_connect(handle, "127.0.0.1", 8888);

// 创建监听
int listenfd = skynet_socket_listen(handle, "0.0.0.0", 8888);

关闭 Socket

Lua API:

lua 复制代码
socket.close(fd)

C API:

c 复制代码
skynet_socket_close(fd);

与 Worker 线程的交互

消息发送流程

复制代码
服务
 │
 ▼
skynet.send(service, PTYPE_SOCKET, data)
 │
 ▼
skynet_send()
 │
 ▼
skynet_socket_send()
 │
 ├─ 将数据写入写缓冲区
 ├─ 设置 EPOLLOUT 事件
 └─ 返回
 │
 ▼
Socket 线程
 │
 ├─ epoll_wait() 检测到 EPOLLOUT
 ├─ socket_write()
 ├─ write(fd, data, len)
 └─ 发送数据到网络

消息接收流程

复制代码
Socket 线程
 │
 ├─ epoll_wait() 检测到 EPOLLIN
 ├─ socket_read()
 ├─ read(fd, buffer, len)
 └─ skynet_send(service, PTYPE_SOCKET, buffer)
     │
     ▼
 全局消息队列
     │
     ▼
Worker 线程
 │
 ├─ skynet_context_message_dispatch()
 ├─ 调用服务回调函数
 └─ 处理消息

配置和调优

默认配置

配置项 默认值 说明
MAX_SOCKET 100000 最大 socket 数量
MAX_EVENT 64 每次处理的最大事件数
BUFFER_SIZE 65536 缓冲区大小(64KB)

调优建议

1. 增加最大连接数
c 复制代码
// 修改 MAX_SOCKET
#define MAX_SOCKET 1000000  // 100 万连接
2. 调整缓冲区大小
c 复制代码
// 修改 BUFFER_SIZE
#define BUFFER_SIZE 131072  // 128KB
3. 调整 epoll_wait 超时
c 复制代码
// 修改超时时间
int n = epoll_wait(s->event_fd, s->ev, MAX_EVENT, 1);  // 1ms
// 改为
int n = epoll_wait(s->event_fd, s->ev, MAX_EVENT, 10);  // 10ms

使用示例

TCP 服务端

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

skynet.start(function()
    -- 监听端口
    local listenfd = socket.listen("0.0.0.0", 8888)
    socket.start(listenfd, function(clientfd, addr)
        print("Accept connection:", clientfd, addr)
        
        -- 启动 socket
        socket.start(clientfd)
        
        -- 读取数据
        while true do
            local data = socket.readline(clientfd, "\n")
            if not data then
                print("Connection closed:", clientfd)
                socket.close(clientfd)
                return
            end
            
            print("Receive:", data)
            
            -- 回显数据
            socket.write(clientfd, "Echo: " .. data)
        end
    end)
end)

TCP 客户端

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

skynet.start(function()
    -- 连接服务器
    local fd = socket.open("127.0.0.1", 8888)
    
    -- 发送数据
    socket.write(fd, "Hello Skynet\n")
    
    -- 读取响应
    local data = socket.readline(fd, "\n")
    print("Receive:", data)
    
    -- 关闭连接
    socket.close(fd)
end)

常见问题

Q1: Socket 线程是阻塞的吗?

答案 : Socket 线程是阻塞的,在 epoll_wait() 上等待事件。

Q2: 如何处理大量连接?

答案: 使用 epoll,可以高效处理 10 万+ 并发连接。

Q3: Socket 线程会处理业务逻辑吗?

答案: 不会,Socket 线程只处理网络 I/O,业务逻辑由 Worker 线程处理。

Q4: 如何监控 Socket 线程?

答案 : 可以使用 skynet.stat 命令查看 socket 统计信息。


总结

Socket 线程的核心作用:

  1. 网络监听: 监听所有 socket 的网络事件
  2. I/O 处理: 读取和写入网络数据
  3. 消息转换: 将网络事件转换为 Skynet 消息
  4. 高并发: 支持 10 万+ 并发连接

关键机制:

  • I/O 多路复用:epoll/kqueue
  • 异步非阻塞:不阻塞其他线程
  • 消息驱动:将网络事件转换为消息

与其他线程的关系:

  • Worker 线程:处理网络消息
  • 主线程:创建 Socket 线程
  • Timer 线程:更新 socket 时间
相关推荐
林鸿群1 天前
Cocos2d-x Lua 游戏前端工程架构深度解析
游戏·mvc·lua·游戏开发·cocos2d·游戏架构
林鸿群1 天前
Lua 5.4 语法与核心知识学习总结
lua
007张三丰2 天前
软件测试专栏(7/20):接口测试全攻略:Postman+Newman实现API自动化
自动化·lua·接口测试·postman·api测试·newman
于眠牧北2 天前
重写RedisTemplate后在lua脚本中传递参数不需要二次转换
java·junit·lua
csdn_aspnet2 天前
技术难题:高并发场景下的“超卖”现象(库存一致性)
redis·lua·秒杀
shuair2 天前
redis执行lua脚本
数据库·redis·lua
小白-Tester3 天前
2026最新Postman安装教程[简单易懂]附安装包
开发语言·lua
qq_246839755 天前
Redis lua 执行性能优化
redis·性能优化·lua
豆浆煮粉5 天前
基于 Linux+CMake 从零集成 Lua 脚本引擎 (附 Sol2 避坑指南)
linux·lua