在基于 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-Length
if (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