項目中原有的基於 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;
}
accept_cbvoid 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
read_cbvoid 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);
}
freelibevint 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;
}
timer_beatvoid 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); // 重置為一次性定時器
}
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 直接管理而非固定數組EV_EMASK_ET 切換 ET 模式ev_signal 監聽 SIGINT/SIGTERM 實現優雅關閉accept 失敗、malloc 失敗)均正確釋放資源recv 單次讀取,實際應處理粘包/拆包,採用長度前綴或分隔符協議