Socket 线程详解
目录
- 概述
- 主要职责
- [I/O 多路复用](#I/O 多路复用)
- 代码实现
- 工作流程
- 网络事件处理
- [Socket 管理](#Socket 管理)
- [与 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 线程的核心作用:
- 网络监听: 监听所有 socket 的网络事件
- I/O 处理: 读取和写入网络数据
- 消息转换: 将网络事件转换为 Skynet 消息
- 高并发: 支持 10 万+ 并发连接
关键机制:
- I/O 多路复用:epoll/kqueue
- 异步非阻塞:不阻塞其他线程
- 消息驱动:将网络事件转换为消息
与其他线程的关系:
- Worker 线程:处理网络消息
- 主线程:创建 Socket 线程
- Timer 线程:更新 socket 时间