雲風的skynet服務器框架設計精妙,其通信層內核尤為值得深究。本文系列首部分析並優化其核心思想:統一封裝不同平台的I/O多路復用接口——Linux epoll、BSD/macOS kqueue(Windows IOCP機制差異較大,暫不封裝)。
掌握epoll LT/ET模式讀寫細節、EWOULDBLOCK/EAGAIN語義、錯誤碼EINTR處理是前置條件。推薦參考:
typedef int poll_fd; struct event { void *s; bool read; bool write; }; static bool sp_invalid(poll_fd fd); static poll_fd sp_create(); static void sp_release(poll_fd fd); static int sp_add(poll_fd fd, int sock, void *ud); static void sp_del(poll_fd fd, int sock); static void sp_write(poll_fd fd, int sock, void *ud, bool enable); static int sp_wait(poll_fd fd, struct event *e, int max); static void sp_nonblocking(int sock); #ifdef __linux__ #include "socket_epoll.h" #endif #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) #include "socket_kqueue.h" #endif
設計亮點:統一接口poll_fd、event,平台相關實現隔離在socket_epoll.h/socket_kqueue.h,編譯期按宏切換。但存在細節問題:
sp_write缺少poll_fd參數名。#if非互斥,雖不出錯但增加編譯期判斷開銷,建議#if/#elif/#else。// 原版關鍵點
static int sp_create() { return epoll_create(1024); } // 硬編碼尺寸
static int sp_add(int efd, int sock, void *ud) {
struct epoll_event ev = { .events = EPOLLIN, .data.ptr = ud };
return epoll_ctl(efd, EPOLL_CTL_ADD, sock, &ev) == -1 ? 1 : 0; // 返回1表示失敗,違背慣例
}
static void sp_write(int efd, int sock, void *ud, bool enable) {
struct epoll_event ev = { .events = EPOLLIN | (enable ? EPOLLOUT : 0), .data.ptr = ud };
epoll_ctl(efd, EPOLL_CTL_MOD, sock, &ev); // 無錯誤檢查
}
static int sp_wait(int efd, struct event *e, int max) {
struct epoll_event ev[max];
int n = epoll_wait(efd, ev, max, -1);
for (int i = 0; i < n; i++) {
e[i].s = ev[i].data.ptr;
e[i].read = ev[i].events & EPOLLIN;
e[i].write = ev[i].events & EPOLLOUT;
}
return n;
}
核心改進:
poll_t、參數命名(fd統一為輪詢句柄,sock為socket描述符)。#if/#elif/#else #error結構。_INT_MAXEPOLL替代魔數。static inline提示內聯,C99可變長數組struct epoll_event ev[max]。sp_wait採用指針遍歷優化性能。kqueue無需預設尺寸,機制上單獨註冊讀寫事件(EV_ADD/EV_DELETE/EV_ENABLE/EV_DISABLE)。優化版修正返回值約定(失敗返回-1),統一參數名,sp_wait同樣採用指針遍歷。
// 核心邏輯:LT模式模擬ET讀寫
for (;;) {
n = sp_wait(fd, es, _INT_EVS);
for (i = 0; i < n; i++) {
if (es[i].s == sock) { // 新連接
csock = accept(...);
sp_nonblocking(csock);
sp_add(fd, csock, (void*)csock);
} else if (es[i].read) { // 讀事件
do { nr = read(nd, buf, BUFSIZ-1); }
while (nr > 0 && errno == EINTR);
if (nr > 0) sp_write(fd, nd, (void*)nd, true); // 註冊寫
} else if (es[i].write) { // 寫事件
while (nw < sum) { nr = write(nd, html+nw, sum-nw); ... }
close(nd); // 發送完畢關閉
}
}
}
關鍵細節:讀循環處理EINTR/EAGAIN,寫循環處理分包發送,發送完成後關閉連接。測試通過,證明封裝正確可用。
臨摹大神代碼、優化細節、編寫測試,是掌握高性能網絡編程的捷徑。下一步將進一步封裝業務邏輯,構建完整服務器框架。熟能生巧,勤能補拙。