首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

基於 libev 的高併發網絡服務器 C 實現完整範例

     編輯:關於C語言

專案背景

原生 epoll 版本服務器出現詭異問題,需快速切換至成熟事件庫 libev 上線。本文記錄一個功能完整的 Echo 服務器範例,包含連接接受、數據收發、心跳定時器、資源釋放,適合作為生產環境骨架參考。

核心架構

  • 事件循環:ev_default_loop() + ev_run()。
  • I/O Watcher:ev_io 監聽 EV_READ 事件,分 accept_cb (監聽 socket) 與 read_cb (客戶端 socket)。
  • Timer Watcher:ev_timer 週期性發送心跳 (範例中註釋啟用)。
  • 連接管理:靜態陣列 libevlist[MAX_ALLOWED_CLIENT] 映射 fd → ev_io*,O(1) 查找。

完整代碼 (含註解)

[c]
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 57789
#define BUFFER_SIZE 2048
#define MAX_ALLOWED_CLIENT 10240

struct ev_io *libevlist[MAX_ALLOWED_CLIENT] = {NULL};

void accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents);
void read_cb(struct ev_loop *loop, struct ev_io *watcher, int revents);
void timer_beat(struct ev_loop *loop, struct ev_timer *watcher, int revents);
int freelibev(struct ev_loop *loop, int fd);

int main() {
    struct ev_loop *loop = ev_default_loop(0);
    int sd;
    struct sockaddr_in addr;
    int addr_len = sizeof(addr);

    struct ev_io socket_accept;
    struct ev_timer timeout_w;

    sd = socket(PF_INET, SOCK_STREAM, 0);
    if (sd < 0) { perror("socket"); return -1; }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { perror("bind"); return -1; }
    if (listen(sd, SOMAXCONN) < 0) { perror("listen"); return -1; }

    int reuse = 1;
    setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

    ev_io_init(&socket_accept, accept_cb, sd, EV_READ);
    ev_io_start(loop, &socket_accept);

    // 心跳定時器 (每 2 秒)
    // ev_timer_init(&timeout_w, timer_beat, 2., 2.);
    // ev_timer_start(loop, &timeout_w);

    ev_run(loop, 0);
    return 0;
}

void accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents) {
    if (EV_ERROR & revents) { perror("accept error event"); return; }

    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int client_sd = accept(watcher->fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_sd < 0) { perror("accept"); return; }

    if (client_sd >= MAX_ALLOWED_CLIENT) {
        fprintf(stderr, "fd too large [%d]\n", client_sd);
        close(client_sd);
        return;
    }
    if (libevlist[client_sd] != NULL) {
        fprintf(stderr, "fd [%d] already in use\n", client_sd);
        close(client_sd);
        return;
    }

    struct ev_io *w_client = malloc(sizeof(struct ev_io));
    if (!w_client) { perror("malloc"); close(client_sd); return; }

    ev_io_init(w_client, read_cb, client_sd, EV_READ);
    ev_io_start(loop, w_client);
    libevlist[client_sd] = w_client;
    printf("client connected: fd=%d\n", client_sd);
}

void read_cb(struct ev_loop *loop, struct ev_io *watcher, int revents) {
    if (EV_ERROR & revents) { perror("read error event"); freelibev(loop, watcher->fd); return; }

    char buffer[BUFFER_SIZE];
    ssize_t n = recv(watcher->fd, buffer, BUFFER_SIZE, 0);
    if (n < 0) {
        if (errno != EAGAIN && errno != EWOULDBLOCK) perror("recv");
        return;
    }
    if (n == 0) { // 客戶端正常關閉
        printf("client disconnected: fd=%d\n", watcher->fd);
        freelibev(loop, watcher->fd);
        return;
    }

    printf("recv fd=%d: %.*s\n", watcher->fd, (int)n, buffer);
    send(watcher->fd, buffer, n, 0); // Echo 回傳
}

void timer_beat(struct ev_loop *loop, struct ev_timer *watcher, int revents) {
    if (EV_ERROR & revents) return;
    printf("heartbeat\n");
    // 此處可遍歷 libevlist 發送心跳包
}

int freelibev(struct ev_loop *loop, int fd) {
    if (fd >= MAX_ALLOWED_CLIENT || libevlist[fd] == NULL) return -1;
    close(fd);
    ev_io_stop(loop, libevlist[fd]);
    free(libevlist[fd]);
    libevlist[fd] = NULL;
    return 1;
}

Makefile

[makefile]
CC = gcc
CFLAGS = -I. -I/path/to/libev/include -Wall -O2
LDFLAGS = -L/path/to/libev/lib -lev
OBJS = server.o
TARGET = server

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)

%.o: %.c
	$(CC) $(CFLAGS) -c $<

clean:
	rm -f $(OBJS) $(TARGET)

關鍵優化與生產化建議

  1. fd 映射改用動態哈希表或 epoll 原生 epoll_data:靜態陣列限制最大連接數且浪費記憶體 (稀疏 fd)。
  2. 緩衝區管理:採用 buffer_t (read/write 索引、擴容) 替代棧上固定陣列,支援大包、分包、粘包處理。
  3. 非阻塞 + 邊緣觸發 (ET):fcntl(fd, F_SETFL, O_NONBLOCK) + ev_io_set(..., EV_READ | EV_WRITE) 手動控制寫事件,避免忙輪詢。
  4. 優雅關閉:捕捉 SIGINT/SIGTERM,ev_break(loop, EVBREAK_ALL) 後遍歷釋放所有 watcher。
  5. 多執行緒擴展:ev_loop_new(EVFLAG_NOSIGMASK) 每執行緒一循環,主執行緒負責 accept,分發至工作執行緒循環 (需 ev_async 通知)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved