L-Chat——1、引入 连接上下文(Conn) 和 输入缓冲区(in_buf)

5 阅读3分钟

1、添加connect.h和connect.c

connect.h

#ifndef _CONNECTION_H_
#define _CONNECTION_H_

#include<stdint.h>
#include<stddef.h>
#include"../include/protocol.h"

#define BUFFER_SIZE 65536

typedef struct {
    int fd;
    char in_buf[BUFFER_SIZE];
    size_t in_pos;

    uint32_t uid;
    int is_auth;

} Conn;

extern Conn* g_conns[1024];

Conn* conn_new(int fd);
void conn_delete(int fd);

#endif

connect.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include"connection.h"

Conn* g_conns[1024] = {NULL};

Conn* conn_new(int fd){
    if (fd<0 || fd >=1024) return NULL;

    Conn* c = (Conn*)malloc(sizeof(Conn));
    if(!c)return  NULL;

    memset(c,0,sizeof(Conn));
    
    c->fd = fd;
    g_conns[fd] = c;
    return c;
}
void conn_delete(int fd){
    if(fd<0 || fd>1024) return;
    Conn* c = g_conns[fd];
    if(c){

        close(c->fd);
        free(c);
        g_conns[fd] = NULL;
    }
}

2、改进server.c

server.c改进

void handle_login_request(Conn *conn, LoginRequest *req) {
    printf(">>> [LOGIN] Username: %s, Password: %s\n", req->username, req->password);
    
    // 构造响应
    char send_buf[1024];
    AppHeader *resp_header = (AppHeader *)send_buf;
    LoginResponse *resp_body = (LoginResponse *)(send_buf + sizeof(AppHeader));

    resp_body->uid = htonl(1001);
    resp_body->result = 1;
    strcpy(resp_body->msg, "Login Success (M1 Version)!");

    resp_header->magic = htonl(PROTOCOL_MAGIC);
    resp_header->version = htons(1);
    resp_header->type = htons(MSG_TYPE_LOGIN_RESP);
    resp_header->sequence = htonl(0);
    resp_header->length = htonl(sizeof(LoginResponse));

    // M1 阶段暂时直接 write,M2 阶段这里也会改成 output buffer
    send(conn->fd, send_buf, sizeof(AppHeader) + sizeof(LoginResponse), 0);
}

void handle_client_message(int sockfd, int epoll_fd) {
    Conn *conn = g_conns[sockfd];
    if (!conn) return; // 保护性检查

    // 1. 【读取阶段】尽可能多地把数据读到 in_buf 里
    while (1) {
        // 计算还有多少空间:总大小 - 当前已存的位置
        size_t free_space = BUFFER_SIZE - conn->in_pos;
        if (free_space == 0) {
            printf("Error: Buffer full! Dropping connection.\n");
            conn_delete(sockfd);
            return;
        }

        ssize_t n = recv(sockfd, conn->in_buf + conn->in_pos, free_space, 0);
        
        if (n > 0) {
            conn->in_pos += n; // 推进指针
        } else if (n == 0) {
            printf("Client fd=%d disconnected\n", sockfd);
            conn_delete(sockfd);
            return;
        } else {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                break; // 数据读完了,跳出循环去处理
            }
            perror("Recv error");
            conn_delete(sockfd);
            return;
        }
    }

    // 2. 【解析阶段】循环处理缓冲区里的数据 (解决粘包)
    while (conn->in_pos >= sizeof(AppHeader)) {
        // 预览头部
        AppHeader *header = (AppHeader *)conn->in_buf;
        uint32_t magic = ntohl(header->magic);
        uint32_t payload_len = ntohl(header->length);

        //安全检查
        if (magic != PROTOCOL_MAGIC) {
            printf("Error: Invalid Magic 0x%X. Kicking.\n", magic);
            conn_delete(sockfd);
            return;
        }

        //  检查是否“半包”:现有数据够不够一个完整的包
        // 完整包长度 = 头部 + 负载
        size_t total_frame_len = sizeof(AppHeader) + payload_len;
        if (conn->in_pos < total_frame_len) {
            // 数据不够,停止解析,等待下一次 recv 把剩下的拼起来
            break; 
        }

        // 3. 【分发阶段】已有完整的包
        printf("Debug: Complete frame received. Type=%d, Len=%d\n", 
               ntohs(header->type), payload_len);

        // 获取 Body 指针
        void *body = conn->in_buf + sizeof(AppHeader);
        uint16_t type = ntohs(header->type);

        switch (type) {
            case MSG_TYPE_LOGIN_REQ:
                handle_login_request(conn, (LoginRequest *)body);
                break;
            case MSG_TYPE_CHAT_REQ:
                printf(">>> [CHAT] Msg received.\n");
                break;
            default:
                printf(">>> [UNKNOWN] Type: %d\n", type);
        }

        // 4. 【清理阶段】将处理完的数据移出缓冲区
        // 将后面剩余的数据搬运到缓冲区头部
        size_t remaining = conn->in_pos - total_frame_len;
        if (remaining > 0) {
            memmove(conn->in_buf, conn->in_buf + total_frame_len, remaining);
        }
        conn->in_pos = remaining; // 更新当前数据末尾指针
    }


3、提升点

  • 1、解决半包和粘包问题

先全部存进 conn->in_buf,然后在一个 while 循环里看缓冲区,处理完一块,用 memmove 把剩下的数据推到最前面,腾出空间

  • 2、IO 层与业务层分离
  • 3、Conn 结构体 (包含 fd, uid, buffer),为后续保存用户状态打下基础