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

基於 libev 的高併發 TCP 服務器實戰:從零構建網絡通信框架

     編輯:關於C

背景與動機

項目中原有的基於 epoll 手寫的服務器出現難以複現的異常,為快速上線,決定引入成熟的 libev 库替換底層事件驅動。本文記錄一個「五臟俱全」的最小可用服務器示例,包含連接接受、數據收發、心跳定時器、資源釋放等核心功能。

完整服務器代碼

#include <ev.h>
#include <stdio.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>

#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) { printf("socket error\n"); return -1; }

    bzero(&addr, 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) {
        printf("bind error\n"); return -1;
    }
    if (listen(sd, SOMAXCONN) < 0) {
        printf("listen error\n"); return -1;
    }

    int bReuseaddr = 1;
    if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR,
                   (const char *)&bReuseaddr, sizeof(bReuseaddr)) != 0) {
        printf("setsockopt error in reuseaddr[%d]\n", sd); return -1;
    }

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

    // 可選:啟用心跳定時器
    // ev_timer_init(&timeout_w, timer_beat, 1., 0.);
    // ev_timer_start(loop, &timeout_w);

    ev_run(loop, 0);
    return 1;
}

核心回調函數解析

1. 連接接受 accept_cb

void accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents) {
    if (EV_ERROR & revents) { printf("error event in accept\n"); 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) { printf("accept error\n"); return; }

    if (client_sd > MAX_ALLOWED_CLIENT) {
        printf("fd too large[%d]\n", client_sd);
        close(client_sd); return;
    }
    if (libevlist[client_sd] != NULL) {
        printf("client_sd not NULL fd is [%d]\n", client_sd); return;
    }

    printf("client connected\n");
    struct ev_io *w_client = malloc(sizeof(struct ev_io));
    if (!w_client) { printf("malloc error in accept_cb\n"); return; }

    ev_io_init(w_client, read_cb, client_sd, EV_READ);
    ev_io_start(loop, w_client);
    libevlist[client_sd] = w_client;
}

要點:
• 使用文件描述符作為數組下標快速索引監視器
• 檢查 EV_ERROR 避免無效事件
• 新連接創建獨立 ev_io 並註冊 EV_READ

2. 數據讀取與回射 read_cb

void read_cb(struct ev_loop *loop, struct ev_io *watcher, int revents) {
    if (EV_ERROR & revents) { printf("error event in read\n"); return; }

    char buffer[BUFFER_SIZE];
    ssize_t read = recv(watcher->fd, buffer, BUFFER_SIZE, 0);
    if (read < 0) { printf("read error\n"); return; }

    if (read == 0) {
        printf("client disconnected.\n");
        freelibev(loop, watcher->fd);
        return;
    }

    printf("receive message:%s\n", buffer);
    send(watcher->fd, buffer, read, 0);  // 直接回射
    bzero(buffer, read);
}

3. 資源釋放 freelibev

int freelibev(struct ev_loop *loop, int fd) {
    if (libevlist[fd] == NULL) {
        printf("the fd already freed[%d]\n", fd); return -1;
    }
    close(fd);
    ev_io_stop(loop, libevlist[fd]);
    free(libevlist[fd]);
    libevlist[fd] = NULL;
    return 1;
}

4. 可選心跳定時器 timer_beat

void timer_beat(struct ev_loop *loop, struct ev_timer *watcher, int revents) {
    if (EV_ERROR & revents) { printf("error event in timer_beat\n"); return; }
    float timeout = 2.0;
    printf("send beat per[%f]\n", timeout);
    fflush(stdout);
    ev_timer_set(watcher, timeout, 0.);
    ev_timer_start(loop, watcher);  // 重置為一次性定時器
}

Makefile 關鍵配置

CC=gcc
FLAGS=-I. -I/home/lengzijian/c/libev/libev-4.11
LDFLAGS=-L/usr/lib -L/home/lengzijian/c/libev/libev-4.11/.libs -lev -Wall
OBJECTS=server.o
ALL_BIN=server

all: $(ALL_BIN)
$(ALL_BIN): $(OBJECTS)
    $(CC) $(FLAGS) $(LDFLAGS) -o $@ $^
%.o: %.c
    $(CC) -c $< $(FLAGS)
clean:
    rm -fr $(OBJECTS) $(ALL_BIN)

注意調整 -I 和 -L 路徑至本機 libev 安裝目錄。

生產環境避坑指南

  • 文件描述符上限:生產環境需調大 ulimit -n,或改用 epoll 直接管理而非固定數組
  • 邊緣觸發 vs 水平觸發:libev 默認水平觸發(LT),高併發可考慮 EV_EMASK_ET 切換 ET 模式
  • 信號處理:添加 ev_signal 監聽 SIGINT/SIGTERM 實現優雅關閉
  • 內存洩漏:確保所有異常路徑(accept 失敗、malloc 失敗)均正確釋放資源
  • 緩衝區處理:示例中 recv 單次讀取,實際應處理粘包/拆包,採用長度前綴或分隔符協議
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved