轻量级的 C 语言网络通信库, 采用多 IO 线程 + 事件驱动模型, 支持 TCP / UDP / KCP 三种传输方式。
支持的IO复用机制: epoll和kqueue
- 读事件(
EV_READ) - 写事件(
EV_WRITE) - 超时事件(
EV_TIMEOUT) - 在三种事件类型的基础上, 支持事件驻留在事件集中的永久模式(
EV_PERSIST)
EV_PERSIST可避免每次事件触发后重新注册, 在读事件上开启后极端场景下可提升 IO 性能约 40%, 对应会话级别的开关是iolayer_set_persist()。
| 方法 | 说明 |
|---|---|
event_create() |
创建事件对象 |
event_set() |
设置事件属性(描述符 + 事件类型) |
event_set_callback() |
设置事件回调函数及上下文 |
event_destroy() |
销毁事件对象 |
| 方法 | 说明 |
|---|---|
evsets_create() |
创建事件集, 参数为时间精度 |
evsets_add() |
向事件集中添加事件 |
evsets_del() |
从事件集中删除事件 |
evsets_dispatch() |
分发并处理事件(阻塞直到有事件就绪或超时) |
evsets_destroy() |
销毁事件集 |
evsets_get_version() |
获取库版本号 |
每个网络线程持有独立的事件集(evsets_t)和任务队列, 线程之间不共享会话。
主线程
└── iothreads_start()
├── IO线程 1 → evsets_dispatch() 循环
├── IO线程 2 → evsets_dispatch() 循环
├── ...
└── IO线程 N → evsets_dispatch() 循环
会话按 sid 哈希绑定到固定的 IO 线程, 生命周期内不迁移。
因此 ioservice_t 的各回调对同一会话而言总在同一线程中被触发, 会话内部状态无需加锁。
- 每个 IO 线程持有一个任务队列, 由自旋锁保护, 采用队列交换(swap)方式批量取出
- 仅当队列由空转为非空时才写
eventfd唤醒目标线程, 避免高频唤醒带来的上下文切换 - 对应的对外接口是
iolayer_invoke()和iolayer_perform()
nthreads: 指定网络线程的个数nclients: 推荐连接数precision: 事件集的时间精度(建议值 20ms)
- 设置线程上下文:
iolayer_set_iocontext() - 设置网络层数据改造方法:
iolayer_set_transform()
transform作用于该网络层的所有会话, 常用于统一的加密或压缩。 若只需针对个别会话做变换, 应在ioservice_t::transform上单独实现。改造后返回的新缓冲区由网络层负责
free(), 因此transform的实现必须使用malloc分配; 若返回的指针与入参相同, 则视为未改造, 网络层不做释放。
type: 网络类型, 支持TCP,UDP和KCPhost: 绑定的地址port: 监听的端口号options: 服务器全局参数(当前主要是KCP的参数配置)callback: 新会话创建成功的回调context: 上下文参数
发起异步连接, 连接结果(无论成功或失败)均通过回调通知。
回调返回值决定后续行为:
| 返回值 | 含义 |
|---|---|
0 |
接受本次结果; 失败时由网络层按退避策略自动重连 |
-1 |
拒绝这条连接, 网络层关闭并清理 |
-2 |
放弃重连, 不再尝试 |
底层
tcp_connect()失败时同样投递事件到网络层, 重连的定时与退避完全在网络层内部消化, 业务层只需在回调中表态是否继续。
把外部创建的描述符纳入网络层管理, 适用于已有 fd 需要复用事件循环的场景。
| 方法 | 说明 |
|---|---|
iolayer_set_timeout() |
设置会话的超时时间 |
iolayer_set_keepalive() |
设置会话的保活时间 |
iolayer_set_service() |
设置会话的IO服务逻辑 |
iolayer_set_persist() |
设置会话的读事件常驻事件集 |
iolayer_set_sndqlimit() |
设置会话的发送队列长度限制 |
iolayer_set_mtu() |
设置最大传输单元(仅限 KCP 有效) |
iolayer_set_minrto() |
设置最小重传时间(仅限 KCP 有效) |
iolayer_set_wndsize() |
设置发送接收窗口(仅限 KCP 有效) |
会话的全部业务回调集中在这个结构体中, 通过 iolayer_set_service() 注册。
| 回调 | 触发时机 | 说明 |
|---|---|---|
start() |
会话建立完成 | 断线重连后同样触发, 适合放握手/注册逻辑 |
process() |
收到数据 | 返回已消费的字节数, 未消费部分留在接收缓冲区 |
transform() |
发送前 | 会话级别的数据改造, 可为空 |
timeout() |
超时 | 返回非 0 关闭会话 |
keepalive() |
保活周期到达 | 通常在此发送心跳包 |
error() |
发生错误 | 返回非 0 关闭会话 |
perform() |
任务投递到本会话 | 配合 iolayer_perform() 使用 |
shutdown() |
会话关闭 | 释放业务资源的唯一时机 |
process()返回值是已消费长度而非处理结果。 半包场景下应返回已完整解析的部分, 剩余数据会与下次收到的数据拼接后再次回调。
int32_t iolayer_send( iolayer_t self, sid_t id,
const char * buf, size_t nbytes, int32_t isfree );isfree 决定缓冲区的归属:
| 取值 | 语义 |
|---|---|
0 |
网络层拷贝一份, 调用方保留缓冲区所有权 |
1 |
所有权转移, 网络层发送完成后 free() |
iolayer_broadcast(): 向指定的会话列表广播iolayer_broadcast2(): 向网络层内的全部会话广播
广播任务会被投递到每个 IO 线程, 由各线程并行遍历自己持有的会话。 广播规模较大时, 单条消息产生的系统调用次数为会话总数, 这是该模型的固有开销; 若需进一步优化, 应从减少会话数量或降低广播频率入手。
iolayer_shutdown(): 关闭单个会话iolayer_shutdowns(): 批量关闭会话列表
调用后不会立即销毁会话, 待发送队列冲刷完毕后才触发 ioservice_t::shutdown()。
iolayer_invoke(): 提交任务到所有 IO 线程, 需提供clone方法为每个线程复制一份iolayer_perform(): 提交任务到指定会话所在的 IO 线程, 触发ioservice_t::perform()
iolayer_perform() 投递失败时会立即调用传入的 recycle 方法回收任务, 调用方无需重复释放。
- 停止对外提供接入服务, 不再接受新的连接;
- 停止所有连接的接收服务, 不再回调
ioservice_t::process()
已建立的连接仍可发送数据, 适用于服务器优雅停机的过渡阶段。
停止所有 IO 线程并释放全部资源。调用前应确保已完成 iolayer_stop() 及必要的数据落地。
// 1. 创建网络层
iolayer_t layer = iolayer_create( 4, 10000, 20, 1 );
// 2. 监听端口
iolayer_listen( layer, NETWORK_TCP, "0.0.0.0", 9029, NULL, on_accept, ctx );
// 3. 在 on_accept 中为新会话注册服务逻辑
static int32_t on_accept( void * context, void * iocontext,
sid_t id, const char * host, uint16_t port )
{
ioservice_t service = {
.start = on_start,
.process = on_process,
.timeout = on_timeout,
.keepalive = on_keepalive,
.error = on_error,
.shutdown = on_shutdown,
.perform = on_perform,
.transform = NULL,
};
iolayer_set_service( layer, id, &service, session_create() );
iolayer_set_timeout( layer, id, 30 );
return 0;
}static ssize_t on_process( void * context, const char * buffer, size_t nbytes )
{
size_t nprocess = 0;
for ( ;; ) {
size_t nleft = nbytes - nprocess;
const char * p = buffer + nprocess;
if ( nleft < sizeof( struct msghead ) ) break;
struct msghead head;
decode_head( p, &head );
if ( nleft < sizeof( struct msghead ) + head.size ) break;
handle_message( context, &head, p + sizeof( struct msghead ) );
nprocess += sizeof( struct msghead ) + head.size;
}
return nprocess; // 返回已消费长度, 剩余部分留待下次
}ioservice_t的所有回调均在 IO 线程中触发, 业务对象若需被逻辑线程访问必须自行同步iolayer_set_*()系列方法只能在 IO 线程中调用, 通常是在accept/connect回调内shutdown()是释放业务资源的唯一时机,error()和timeout()返回非 0 后仍会走到shutdown()transform()返回的新缓冲区必须由malloc分配, 网络层会在发送完成后free()- 会话与 IO 线程的绑定关系在生命周期内固定, 不要跨线程直接操作会话对象, 应通过
iolayer_perform()投递