使用 libevent 处理 TCP 粘包问题(基于 Content-Length 或双 \r\n)

发布于:2025-04-03 ⋅ 阅读:(37) ⋅ 点赞:(0)

在基于 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 

网站公告

今日签到

点亮在社区的每一天
去签到