在基于 libevent 的 TCP 服務器開發中,處理消息邊界是常見需求。以下是兩種主流分包方案的完整實現:
一、基于 Content-Length 的分包方案
1.1 數據結構設計
typedef struct {struct bufferevent *bev;int content_length; // 當前消息的預期長度int received_bytes; // 已接收字節數char *buffer; // 消息緩沖區size_t buffer_size; // 緩沖區大小
} tcp_session_t;
1.2 核心處理邏輯
void read_cb(struct bufferevent *bev, void *arg) {tcp_session_t *session = (tcp_session_t *)arg;struct evbuffer *input = bufferevent_get_input(bev);// 階段1:讀取消息頭if (session->content_length == -1) {char *line = evbuffer_readln(input, NULL, EVBUFFER_EOL_CRLF);if (!line) return; // 不完整的行// 解析Content-Lengthif (strstr(line, "Content-Length:") != NULL) {sscanf(line, "Content-Length: %d", &session->content_length);session->buffer = malloc(session->content_length + 1);}free(line);// 檢查是否到達頭部結束(空行)line = evbuffer_readln(input, NULL, EVBUFFER_EOL_CRLF);if (line && strlen(line) == 0) {free(line);if (session->content_length == -1) {// 沒有Content-Length的簡單消息session->content_length = evbuffer_get_length(input);}} else {return; // 繼續等待頭部結束}}// 階段2:讀取消息體size_t avail = evbuffer_get_length(input);size_t need = session->content_length - session->received_bytes;size_t to_read = avail < need ? avail : need;evbuffer_remove(input, session->buffer + session->received_bytes, to_read);session->received_bytes += to_read;// 階段3:完整消息處理if (session->received_bytes == session->content_length) {session->buffer[session->content_length] = '\0';process_complete_message(session->buffer);// 重置狀態free(session->buffer);session->buffer = NULL;session->content_length = -1;session->received_bytes = 0;}
}
二、基于雙 CRLF 的分包方案
2.1 數據結構設計
typedef struct {struct bufferevent *bev;int header_complete; // 頭部是否解析完成char *header; // 消息頭緩沖區char *body; // 消息體緩沖區size_t body_len; // 消息體長度
} tcp_session_t;
2.2 核心處理邏輯
void read_cb(struct bufferevent *bev, void *arg) {tcp_session_t *session = (tcp_session_t *)arg;struct evbuffer *input = bufferevent_get_input(bev);// 階段1:解析消息頭if (!session->header_complete) {char *line = evbuffer_readln(input, NULL, EVBUFFER_EOL_CRLF);if (!line) return;if (session->header == NULL) {session->header = malloc(1024);session->header[0] = '\0';}// 空行表示頭部結束if (strlen(line) == 0) {session->header_complete = 1;free(line);// 檢查是否有消息體(如POST請求)const char *content_len = strstr(session->header, "Content-Length:");if (content_len) {sscanf(content_len, "Content-Length: %zu", &session->body_len);session->body