注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。

Reactor一般都是epoll + 非阻塞io

(很少用阻塞io的:一次事件触发只能读或发送数据一次,效率低下)。

所以接下来的io就默认都是非阻塞的了

一、关键结构体

1. conn

typedef struct conn
{

    int fd;
    int sent; // 已发送的字节数(跟踪发送进度)

    // 兼容小数据和大数据的发送接收, 兼容阻塞和非阻塞变量的定义。
    // 相比于非阻塞的动态扩张,这种方案更好,可以避免realloc带来的指针变化问题(当数据很长时,找不到连续的空间,realloc会分配失败)
    node wlink; // 不存数据。length表示该链表的总字节长度
    // 发送链表节点, 处理buffer写不完的问题。业务层向链表中添加处理好后的数据
    node rlink;  // 不存数据。length表示该链表的总字节长度
    // 接收链表节点, 处理buffer读不完的问题(如果约定包的信息包含长度,也可以处理半包问题和粘包问题)。业务层从链表中读取数据

    char *filename;
    int file_name_length;

    int status; // { header, body} ,实现头和体的分开发送(用于sendfile)

} conn;

2. 链表节点

typedef struct node
{
    struct node *next;
    struct node *prev;
    int length;
    char data[]; // 柔性数组,存放数据
    // 注意不能定义为 char* data,否则malloc分配内存时就只能是sizeof(node)了
} node;

两个链表:

rlink:读链表

wlink:写链表

  • 特点:可动态扩展,兼容大数据和小数据的收发。比如rlink,每读取一次,就存一个buffer进去(以链表节点的形式)。buffer一次读不完,就循环多读几次,多存几个节点就好了。
  • 注意:我们是一个节点一个send,所以wlink中一个节点的data不能超过发送缓存区,否则就发送不出回去了!!!

网络与业务的解耦:

网络层收到数据只需放入rlink。

业务层只需从rlink中取数据


业务层处理好数据之后将需要发的数据放到wlink中

网络层只需从wlink中取数据进行发送

代码实现:

reactor.h

#ifndef __REACTOR_H__
#define __REACTOR_H__

#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <poll.h>
#include <sys/epoll.h>
#include <errno.h>
#include <sys/time.h>
#include <stdlib.h>
#include <fcntl.h>

#define NO_BLOCK 0
#define BLOCK 0
#define LIST_USE 1
#define SEND_FILE_USE 1

typedef int (*RCALLBACK)(int fd);

// 注意要加()否则优先级不对,比如 传入&conn_list->prev,实际上是这样 &(conn_list->prev),所以要加个括号
// 插入节点:将item插入到链表末尾(头节点的前一个位置,即原末尾节点之后)
#define LIST_INSERT_TAIL(item, list)                                                  \
    do                                                                                \
    {                                                                                 \
        (item)->prev = (list)->prev; /* 新节点的前序指向原末尾节点(头节点的prev) */ \
        (item)->next = (list);       /* 新节点的后序指向头节点(维持循环) */         \
        (list)->prev->next = (item); /* 原末尾节点的后序指向新节点 */                 \
        (list)->prev = (item);       /* 头节点的前序指向新节点(新节点成为末尾) */   \
    } while (0)

// 删除节点:从链表中移除item(item为数据节点,非头节点)
#define LIST_REMOVE(item, list)                                                             \
    do                                                                                      \
    {                                                                                       \
        (item)->prev->next = (item)->next; /* item的前序节点的后序指针指向item的后序节点 */ \
        (item)->next->prev = (item)->prev; /* item的后序节点的前序指针指向item的前序节点 */ \
        (item)->prev = NULL;               /* 清理item的前序指针 */                         \
        (item)->next = NULL;               /* 清理item的后序指针 */                         \
    } while (0)

// 初始化链表头节点
#define LIST_INIT(list)                                    \
    do                                                     \
    {                                                      \
        (list)->next = (list); /* 头节点的后序指向自己 */  \
        (list)->prev = (list); /* 头节点的前序指向自己 */  \
        (list)->length = 0;    /* 头节点的长度初始化为0 */ \
    } while (0)

// 判断链表是否为空
#define LIST_EMPTY(list) ((list)->next == (list) && (list)->prev == (list))

typedef struct node
{
    struct node *next;
    struct node *prev;
    int length;
    char data[]; // 柔性数组,存放数据
     // 注意不能定义为 char* data,否则malloc分配内存时就只能是sizeof(node)了,无法动态分配data的空间
} node;

typedef struct conn
{

    int fd;

    int sent; // 已发送的字节数(跟踪发送进度)
#if BLOCK
    char *rbuffer;
    int rlength;

    char *wbuffer;
    int wlength;
#endif

#if NO_BLOCK
    // 非阻塞模式变量:接收缓冲区
    char *rlbuffer; // 动态接收缓冲区(拼接所有分片数据)
    int rllength;   // 动态接收缓冲区的实际总长度(避免依赖strlen)

    // 非阻塞模式变量:发送缓冲区
    char *wlbuffer; // 动态发送缓冲区(待发送的完整数据)
    int wllength;   // 动态发送缓冲区的总长度(实际字节数)

#endif
    RCALLBACK in_callback;
    RCALLBACK out_callback;

#if LIST_USE

    // 兼容小数据和大数据的发送接收, 兼容阻塞和非阻塞变量的定义(事实上epoll一般搭配的是非阻塞,很少带阻塞的)。
    // 相比于非阻塞的动态扩张,这种方案更好,可以避免realloc带来的指针变化问题(当数据很长时,找不到连续的空间,realloc会分配失败)
    node wlink; // 发送链表节点, 处理buffer写不完的问题。业务层向链表中添加处理好后的数据
    node rlink; // 接收链表节点, 处理buffer读不完的问题(如果约定包的信息包含长度,也可以处理半包问题和粘包问题)。业务层从链表中读取数据
#endif

    char *filename;
    int file_name_length;


    int status; // { header, body}

} conn;

static int epfd_ = 0;    // 全局变量,方面设置事件,如果用于多线程记得加锁。static 修饰后,仅当前文件可访问
static conn *conn_list_; // static 修饰后,仅当前文件可访问
int init_epfd()
{
    epfd_ = epoll_create(1); // epoll_create只能在程序执行时能调用,这里封装成函数
    return epfd_;
}

conn *get_connlist(int n);


int insert_list(char *buffer, int len, node *list);
int send_list(int fd, node *list);
int print_list(node *list);
int set_event(int fd, int event, int flag);
int conn_register(int fd, int buffer_length, RCALLBACK in_callback, RCALLBACK out_callback, int block_use);
int setNonblock(int fd);
int setReUseAddr(int fd);
int conn_close(int fd);
int close_connlist(conn *clist, int n);


// 清空链表
int clear_list(node *list)
{
    node *current = list->next;
    while (current != list)
    {
        node *temp = current;
        current = current->next;
        free(temp);
    }

    // 重新初始化头节点
    LIST_INIT(list);
    return 0;
}

// 插入链表
int insert_list(char *buffer, int len, node *list)
{
    node *new_node = (node *)malloc( (sizeof(node) + len ) * sizeof(char));
    
    memcpy(new_node->data, buffer, len);
    new_node->length = len;

    list->length += len;

    LIST_INSERT_TAIL(new_node, list);
    return 0;
}

// 发送链表节点
int send_list(int fd, node *list)
{
    //printf("Sending list on fd %d:\n", fd);
    int sent_total = 0;
    node *current = list->next;
    while (current != list)
    {
        int sent = send(fd, current->data, current->length, 0);
        if (sent < 0)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                // 套接字缓冲区满,无法继续发送
                break;
            }
            else
            {
                // 其他错误,关闭连接
                return -1;
            }
        }

        printf("%.*s", current->length, current->data);

        sent_total += sent;
        node *temp = current;
        current = current->next;

       
        LIST_REMOVE(temp, list);
        free(temp);
        
    }

    return sent_total;
}

// 展示链表的内容
int print_list(node *list)
{
    struct node *current = list->next;
    while (current != list)
    {
        printf("%.*s", current->length, current->data);
        current = current->next;
    }
    printf("\n");

    return 0;
}





// 设置非阻塞
int setNonblock(int fd)
{
    int flags;

    flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0)
        return flags;
    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) < 0)
        return -1;
    return 0;
}

// 设置端口复用
int setReUseAddr(int fd)
{
    int reuse = 1;
    return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
}

// 设置事件
int set_event(int fd, int event, int flag)
{
    struct epoll_event ev;
    ev.events = event;
    ev.data.fd = fd;

    if (flag == 0) // 还未加入epfd
    {
        epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev);
    }

    else if (flag == 1) // 已经加入了
    {
        epoll_ctl(epfd_, EPOLL_CTL_MOD, fd, &ev);
    }

    else if (flag == -1)
    {
        epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, NULL);
    }

    return 0;
}

// 获取连接列表
conn *get_connlist(int n)
{
    conn_list_ = malloc(sizeof(conn) * n);
    memset(conn_list_, 0, sizeof(conn) * n);
    return conn_list_;
}

// 注册连接
int conn_register(int fd, int buffer_length, RCALLBACK in_callback, RCALLBACK out_callback, int block_use)
{

    conn_list_[fd].fd = fd;
    conn_list_[fd].sent = 0;

#if BLOCK
    conn_list_[fd].rlength = 0;
    conn_list_[fd].wlength = 0;
    conn_list_[fd].rbuffer = conn_list_[fd].wbuffer = NULL;
    // 给阻塞模式使用的固定长度的buffer分配空间
    if (block_use == 1)
    {
        conn_list_[fd].rbuffer = malloc(sizeof(char) * buffer_length);
        memset(conn_list_[fd].rbuffer, 0, buffer_length);

        conn_list_[fd].wbuffer = malloc(sizeof(char) * buffer_length);
        memset(conn_list_[fd].wbuffer, 0, buffer_length);
    }

#endif
    conn_list_[fd].in_callback = in_callback;
    conn_list_[fd].out_callback = out_callback;

#if NO_BLOCK
    // 初始化非阻塞模式变量
    conn_list_[fd].rlbuffer = NULL;
    conn_list_[fd].rllength = 0;
    conn_list_[fd].wlbuffer = NULL;
    conn_list_[fd].wllength = 0;
    

#endif

#if LIST_USE
    // 初始化链表节点
    LIST_INIT(&conn_list_[fd].rlink);
    conn_list_[fd].rlink.length = 0; // 链表内字节数据总长度

    LIST_INIT(&conn_list_[fd].wlink);
    conn_list_[fd].wlink.length = 0; // 链表内字节数据总长度
    

#endif

    conn_list_[fd].status = 0;
    conn_list_[fd].filename = malloc(sizeof(char) * buffer_length);
    memset(conn_list_[fd].filename, 0, buffer_length);
    conn_list_[fd].file_name_length = 0;

    return 0;
}

// 关闭连接
int conn_close(int fd)
{
    if (conn_list_[fd].fd == 0)
    {
        return 0;
    }
#if BLOCK
    if (conn_list_[fd].rbuffer != NULL)
    {
        conn_list_[fd].rbuffer = NULL;
        conn_list_[fd].rlength = 0;
        free(conn_list_[fd].rbuffer);
    }
    if (conn_list_[fd].wbuffer != NULL)
    {
        conn_list_[fd].wbuffer = NULL;
        conn_list_[fd].wlength = 0;
        free(conn_list_[fd].wbuffer);
    }
#endif

#if NO_BLOCK
    if (conn_list_[fd].rlbuffer != NULL)
    {
        conn_list_[fd].rlbuffer = NULL;
        conn_list_[fd].rllength = 0;
        free(conn_list_[fd].rlbuffer);
    }
    if (conn_list_[fd].wlbuffer != NULL)
    {
        conn_list_[fd].wlbuffer = NULL;
        conn_list_[fd].wllength = 0;
        free(conn_list_[fd].wlbuffer);
    }
#endif


    if (conn_list_[fd].filename != NULL)
    {
        conn_list_[fd].filename = NULL;
        free(conn_list_[fd].filename);
        conn_list_[fd].file_name_length = 0;
    }


#if LIST_USE
    clear_list(&conn_list_[fd].rlink);
    clear_list(&conn_list_[fd].wlink);
#endif

    conn_list_[fd].fd = 0;
    memset(&conn_list_[fd], 0, sizeof(conn));
    epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, NULL);
    close(fd);
    return 0;
}

// 关闭连接列表
int close_connlist(conn *clist, int n)
{
    for (int i = 0; i < n; i++)
    {
        if (clist[i].fd != 0)
        {
            conn_close(clist[i].fd);
        }
    }

    free(clist);
    return 0;
}

#if NO_BLOCK

// 非阻塞模式下接收数据
int recv_noblock(int fd, int n_buffer_length)
{
    if (conn_list_[fd].rlbuffer == NULL)
    {
        conn_list_[fd].rlbuffer = malloc(sizeof(char)); // 初始分配1字节(避免realloc NULL问题)
        memset(conn_list_[fd].rlbuffer, 0, sizeof(char));
    }

    int rlen = 0;
    if (conn_list_[fd].rllength != 0)
    {
        rlen = conn_list_[fd].rllength;
    }
    char *buffer = malloc(n_buffer_length * sizeof(char));

    // 循环读取服务器响应
    while (1)
    {

        // 清空缓冲区,准备接收数据
        memset(buffer, 0, n_buffer_length);
        // 从套接字接收数据
        int count = recv(fd, buffer, n_buffer_length, 0);
        if (count == 0)
        {
            printf("client disconnect: %d\n", fd);
            conn_close(fd);
            return 0;
        }
        else if (count < 0)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                // 非阻塞下暂时无数据,返回已读字节数
                break;
            }
            else
            {
                // 真正的错误(如连接重置、中断)
                printf("recv errno: %d --> %s\n", errno, strerror(errno));
                conn_close(fd);
                return -1;
            }
        }

        else
        {
            char *new_rlbuffer = realloc(conn_list_[fd].rlbuffer, (rlen + count) * sizeof(char));
            if (new_rlbuffer == NULL)
            {
                printf("realloc failed\n");
                return -1;
            }

            conn_list_[fd].rlbuffer = new_rlbuffer;

            memcpy(conn_list_[fd].rlbuffer + rlen, buffer, count);
            rlen += count;
        }
    }
    free(buffer);

    conn_list_[fd].rllength = rlen;
    return rlen;
}

// 重置非阻塞接收缓冲区
int reset_r_noblock(int fd)
{
    if (conn_list_[fd].rlbuffer != NULL)
    {
        free(conn_list_[fd].rlbuffer);
        conn_list_[fd].rlbuffer = NULL;
    }
    conn_list_[fd].rllength = 0;
    return 0;
}

// 重置非阻塞发送缓冲区
int reset_w_noblock(int fd)
{
    if (conn_list_[fd].wlbuffer != NULL)
    {
        free(conn_list_[fd].wlbuffer);
        conn_list_[fd].wlbuffer = NULL;
    }
    conn_list_[fd].wllength = 0;
    conn_list_[fd].w_sent = 0;
    return 0;
}

// flag = 1 追加模式,  flag = 0 覆盖模式
int add_to_w_noblock(int fd, char *buffer, int length, int flag)
{
    if (buffer != NULL && length != 0)
    {
        if (flag == 1)
        {
            // 追加模式:在wlbuffer末尾追加rlbuffer内容
            if (conn_list_[fd].wlbuffer != NULL)
            {
                char *new_wlbuffer = realloc(conn_list_[fd].wlbuffer, (conn_list_[fd].wllength + length) * sizeof(char));
                if (new_wlbuffer == NULL)
                {
                    printf("realloc failed\n");
                    return -1;
                }
                conn_list_[fd].wlbuffer = new_wlbuffer;
            }
            else
            {

                conn_list_[fd].wlbuffer = malloc(length * sizeof(char));
                conn_list_[fd].wllength = 0;
                memset(conn_list_[fd].wlbuffer, 0, length * sizeof(char));
            }
            memcpy(conn_list_[fd].wlbuffer + conn_list_[fd].wllength, buffer, length);
            conn_list_[fd].wllength += length;
            return conn_list_[fd].wllength;
        }
        else
        {
            // 覆盖模式:清空wlbuffer内容,复制rlbuffer内容
            if (conn_list_[fd].wlbuffer != NULL)
            {
                free(conn_list_[fd].wlbuffer);
                conn_list_[fd].wlbuffer = NULL;
                conn_list_[fd].wllength = 0;
            }
            conn_list_[fd].wlbuffer = malloc(length * sizeof(char));
            conn_list_[fd].wllength = 0;
            memset(conn_list_[fd].wlbuffer, 0, length * sizeof(char));
            memcpy(conn_list_[fd].wlbuffer, buffer, length);
            conn_list_[fd].wllength = length;
            return conn_list_[fd].wllength;
        }
    }
    return -1;
}

int send_noblock(int fd)
{
    // 非阻塞模式

    char *wbuffer = conn_list_[fd].wlbuffer;
    int total_len = conn_list_[fd].wllength;
    int sent = conn_list_[fd].w_sent;

    // 防御:无数据可发送时直接切换回读事件
    if (wbuffer == NULL || total_len == 0)
    {
        set_event(fd, EPOLLIN | EPOLLET, 1);
        return 0;
    }

    // 循环发送剩余数据
    while (sent < total_len)
    {
        // 发送从“已发送位置”开始的剩余数据
        int nsend = send(fd, wbuffer + sent, total_len - sent, 0);

        if (nsend > 0)
        {
            // 成功发送部分数据,更新进度
            sent += nsend;
            conn_list_[fd].w_sent = sent; // 保存当前进度
        }
        else if (nsend == 0)
        {
            // 连接关闭(对方可能断开),清理资源
            printf("client disconnect during send: %d\n", fd);
            conn_close(fd);
            return -1;
        }
        else
        {
            // 发送错误处理
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                return sent;
            }
            else
            {
                // 其他错误(如连接重置),清理资源
                printf("send errno: %d --> %s\n", errno, strerror(errno));
                conn_close(fd);
                return -1;
            }
        }
    }
    // 所有数据发送完成
    return sent;

    return 0;
}
#endif

#endif // __REACTOR_H__

http_server.h

#ifndef __HTTP_SERVER_H__
#define __HTTP_SERVER_H__

#include "reactor.h"
#include <sys/stat.h>
#include <sys/sendfile.h>

#define HTTP_RESPONSE_HEADER_LEN 1024
#define TEXT 0


#if NO_BLOCK
// 判断http请求是否完整,完整则做处理
int is_http_complete(const char *buf, int len)
{

    // 确保缓冲区长度至少能容纳 "\r\n\r\n"(4 字节)
    if (len >= 4)
    {
        // 定位到末尾 4 字节的起始位置
        const char *end = buf + len - 4;
        // 检查这 4 字节是否严格匹配 "\r\n\r\n"
        // printf("检查结尾4字节: %.*s\n", 4, end);
        if (end[0] == '\r' && end[1] == '\n' &&
            end[2] == '\r' && end[3] == '\n')
        {
            return 1; // 请求头完整
        }
    }

    return 0; // 不完整,继续等待
}
#endif

#if LIST_USE
int is_http_complete(conn *c)
{
    int len = 0;
    struct node *current = c->rlink.prev;
    while (len < 4)
    {
        struct node *prev = current->prev;
        len += current->length;
        if (len >= 4 || current == &c->rlink)
        {
            break;
        }
        current = prev;
    }

    if (len < 4)
    {
        return 0; // 不完整,继续等待
    }

    char *buffer = (char *)malloc(sizeof(char) * len);

    len = 0;
    while (current != &c->rlink)
    {

        memcpy(buffer + len, current->data, sizeof(char) * current->length);
        len += current->length;
        current = current->next;
    }

    // printf("buffer_len:%d \n", len);
    // printf("%.*s\n", len, buffer);

    int i = 5;
    int j = 0;

    // 确保缓冲区长度至少能容纳 "\r\n\r\n"(4 字节)
    if (len >= 4)
    {
        // 定位到末尾 4 字节的起始位置
        const char *end = buffer + len - 4;
        // 检查这 4 字节是否严格匹配 "\r\n\r\n"
        // printf("检查结尾4字节: %.*s\n", 4, end);
        if (end[0] == '\r' && end[1] == '\n' &&
            end[2] == '\r' && end[3] == '\n')
        {
            free(buffer);
            return 1; // 请求头完整
        }
    }

    free(buffer);
    return 0;
}

#endif

// 在浏览器输入 ip:port/path    以我为例: 192.168.248.130:8000/index.html
// 服务器接收到:
// GET /index.html HTTP/1.1
// Host: 192.168.248.130:8000
// Connection: keep-alive
// Cache-Control: max-age=0
// Upgrade-Insecure-Requests: 1
// User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0
// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
// Accept-Encoding: gzip, deflate
// Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

// 我们简单点处理,只做资源文件的提取!!!

#if NO_BLOCK
int http_request(conn *c)
{
    // 请求头完整,处理请求
    printf("请求头完整,准备处理请求:\n");
    printf("recvLen: %d\n", c->rllength);
    printf("RECV: %.*s\n", c->rllength, c->rlbuffer);

    char buffer[100] = {0};
    c->file_name_length = 0;
    int len = c->rllength;
    int i = 5;
    int j = 0;
    for (; i < len; i++, j++)
    {
        if (c->rlbuffer[i] != ' ')
        {
            c->filename[j] = c->rlbuffer[i];
            c->file_name_length++;
        }
        else
        {
            break;
        }
    }
    c->filename[c->file_name_length] = '\0';
    printf("filename: %s\n", c->filename);
}
#endif

#if LIST_USE
int http_request(conn *c)
{
    // 请求头完整,处理请求
    printf("请求头完整,准备处理请求:\n");

    print_list(&c->rlink);

    char *buffer = malloc(sizeof(char) * c->rlink.next->length);
    memcpy(buffer, c->rlink.next->data, sizeof(char) * c->rlink.next->length);

    int i = 0;

    c->file_name_length = 0;
    int len = c->rlink.next->length;

    i = 5;
    int j = 0;
    for (; i < len; i++, j++)
    {
        if (buffer[i] != ' ')
        {
            c->filename[j] = buffer[i];
            c->file_name_length++;
        }
        else
        {
            break;
        }
    }
    c->filename[c->file_name_length] = '\0';
    printf("filename: %s\n", c->filename);

    free(buffer);
    clear_list(&c->rlink);
}

#endif

// 这里只做html资源的响应,favicon.ico等其他资源不做响应
// 服务器收到响应后,显示网页内容
// 响应内容示例:
// HTTP/1.1 200 OK
// Date: Wed, 11 Oct 2025 12:00:00 GMT  # 当前GMT时间(需动态生成)
// Server: MyEpollServer/1.0            # 你的服务器标识(自定义)
// Connection: keep-alive               # 与请求的Connection: keep-alive对应,保持长连接
// Content-Type: text/html; charset=utf-8  # 响应体类型(HTML文件,编码UTF-8)
// Content-Length: 138                  # 响应体的字节数(需根据实际HTML内容计算)

// <!DOCTYPE html>
// <html lang="zh-CN">
// <head>
//     <meta charset="UTF-8">
//     <title>Index Page</title>
// </head>
// <body>
//     <h1>Hello! This is the index.html page.</h1>
// </body>
// </html>

// response_max_length必须大于回复头,这个使用者会知道的,但是文件大小使用者不必知道

#if NO_BLOCK
int http_response(conn *c)
{

    if (c->file_name_length == 0)
    {
        printf("no file_name\n");
        return -2;
    }

    // 使用stat获取文件大小
    int filefd = open(c->filename, O_RDONLY);
    if (filefd < 0)
    {

        int response_max_length = HTTP_RESPONSE_HEADER_LEN;
        char *response = malloc(response_max_length * sizeof(char));
        memset(response, 0, response_max_length * sizeof(char));
        printf("open failed\n");

        int len = sprintf(response,
                          "HTTP/1.1 404 Not Found\r\n"
                          "Content-Type: text/html\r\n"
                          "Content-Length: 113\r\n"
                          "Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n"
                          "<html><head><title>404 Not Found</title></head>"
                          "<body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></body></html>");
        add_to_w_noblock(c->fd, response, len, 0);

        c->status = -1;
    }

    struct stat stat_buf;
    fstat(filefd, &stat_buf);

    if (c->status == 0)
    {
        printf("status: %d, response\n", c->status);
        int response_max_length = HTTP_RESPONSE_HEADER_LEN;
        char *response = malloc(response_max_length * sizeof(char));
        memset(response, 0, response_max_length * sizeof(char));
        int len = sprintf(response,
                          "HTTP/1.1 200 OK\r\n"
                          "Content-Type: image/png\r\n"
                          "Accept-Ranges: bytes\r\n"
                          "Content-Length: %ld\r\n"
                          "Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
                          stat_buf.st_size);

        add_to_w_noblock(c->fd, response, len, 0);

        c->status = 1;
    }

    if (c->status == 2)
    {
        printf("status: %d, response\n", c->status);

#if 1
        int len = 0;
        int times = 0;
        lseek(filefd, 0, SEEK_SET);
        while (1)
        {
            int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size - len);
            if (ret == -1)
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    // 发送缓存区满了!!!
                    // sleep(1); 启动这个会发现图片由上至下慢慢生成,十分有意思
                    times++;
                    continue;
                }
                printf("errno: %d\n", errno);
            }
            len += ret; // 这里要放在errno的下面否则就把-1加上了。
            if (ret == 0)
            {
                break;
            }
        }

        printf("send successfully : bytes: %d\n", len);
        printf("阻塞的次数: %d\n", times);

#else

#endif

        c->status = 3;
    }

    close(filefd);
}

#endif

#if LIST_USE
int http_response(conn *c)
{

    if (c->file_name_length == 0)
    {
        printf("no file_name\n");
        return -2;
    }

    // 使用stat获取文件大小
    int filefd = open(c->filename, O_RDONLY);
    if (filefd < 0)
    {

        int response_max_length = HTTP_RESPONSE_HEADER_LEN;
        char *response = malloc(response_max_length * sizeof(char));
        memset(response, 0, response_max_length * sizeof(char));
        printf("open failed\n");


        int len = sprintf(response,
                          "HTTP/1.1 404 Not Found\r\n"
                          "Content-Type: text/html\r\n"
                          "Content-Length: 113\r\n"
                          "Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n"
                          "<html><head><title>404 Not Found</title></head>"
                          "<body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></body></html>");


        // 插入写链表
        insert_list(response, len, &(c->wlink));

        c->status = -1;
    }

    struct stat stat_buf;
    fstat(filefd, &stat_buf);

    if (c->status == 0)
    {
        printf("status: %d, response\n", c->status);
        int response_max_length = HTTP_RESPONSE_HEADER_LEN;
        char *response = malloc(response_max_length * sizeof(char));
        memset(response, 0, response_max_length * sizeof(char));
#if TEXT

        int len = sprintf(response,
                          "HTTP/1.1 200 OK\r\n"
                          "Content-Type: text/html\r\n"
                          "Accept-Ranges: bytes\r\n"
                          "Content-Length: %ld\r\n"
                          "Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
                          stat_buf.st_size);
#else
        int len = sprintf(response,
                          "HTTP/1.1 200 OK\r\n"
                          "Content-Type: image/png\r\n"
                          "Accept-Ranges: bytes\r\n"
                          "Content-Length: %ld\r\n"
                          "Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
                          stat_buf.st_size);
#endif

        insert_list(response, len, &(c->wlink));

#if !SEND_FILE_USE
        int read_len = 0;
        while (read_len != stat_buf.st_size)
        {
            int ret = read(filefd, response, response_max_length);
            insert_list(response, ret, &(c->wlink));
            read_len += ret;
        }
#endif
        
        
        c->status = 1;
    }

    if (c->status == 2)
    {
        printf("status: %d, response\n", c->status);

#if SEND_FILE_USE
        int len = 0;
        int times = 0;
        lseek(filefd, 0, SEEK_SET);
        while (1)
        {
            int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size - len);
            if (ret == -1)
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    // 发送缓存区满了!!!
                    // sleep(1); 启动这个会发现图片由上至下慢慢生成,十分有意思
                    times++;
                    continue;
                }
                printf("errno: %d\n", errno);
                return -1;
            }
            len += ret; // 这里要放在errno的下面否则就把-1加上了。
            if (ret == 0)
            {
                break;
            }
        }

        printf("send successfully : bytes: %d\n", len);
        printf("阻塞的次数: %d\n", times);

#endif

        c->status = 3;
    }

    close(filefd);
}

#endif

#endif

reactor.c

#include "reactor.h"
#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <poll.h>
#include <sys/epoll.h>
#include <errno.h>
#include <sys/time.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "http_server.h"

#define BUFFER_LENGTH 1024

#define CONNECTION_SIZE 100 // 1048576 // 1024 * 1024

#define MAX_PORTS 20

int accept_cb(int fd);
int recv_cb(int fd);
int send_cb(int fd);
int init_server(int sockfd, int port);

conn *conn_list;

int accept_cb(int fd)
{
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);

    int clientfd = accept(fd, (struct sockaddr *)&clientaddr, &len);
    printf("clientfd:%d\n", clientfd);

    conn_register(clientfd, BUFFER_LENGTH, recv_cb, send_cb, 0);
    set_event(clientfd, EPOLLIN | EPOLLET, 0);
#if 1
    setNonblock(clientfd); // 设置非阻塞
#endif
    return 0;
}

int recv_cb(int fd)
{

    // 双向链表接收
    while (1)
    {
        char buffer[BUFFER_LENGTH] = {0};
        int ret = recv(fd, buffer, BUFFER_LENGTH, 0);
        if (ret == 0)
        {
            printf("client disconnect: %d\n", fd);
            conn_close(fd);
            return 0;
        }
        else if (ret < 0)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                // 非阻塞下暂时无数据,返回已读字节数
                break;
            }
            else
            {
                // 真正的错误(如连接重置、中断)
                printf("recv errno: %d --> %s\n", errno, strerror(errno));
                conn_close(fd);
                return -1;
            }
        }
        else
        {
            // 将数据添加到接收链表
            insert_list(buffer, ret, &conn_list[fd].rlink);
        }
    }

    //printf("recvLen: %d\n", conn_list[fd].rlink.length);
    //printf("RECV: \n");
    //print_list(&conn_list[fd].rlink);

    if (is_http_complete(&conn_list[fd]))
    {

        http_request(&conn_list[fd]);
        set_event(fd, EPOLLOUT | EPOLLET, 1);
    }

    else
    {
        // 请求头不完整,继续等待更多数据
        set_event(fd, EPOLLIN | EPOLLET, 1);
    }
}

int send_cb(int fd)
{
    if (LIST_EMPTY(&conn_list[fd].wlink) && conn_list[fd].file_name_length == 0)
    {
        // 防御:无数据可发送时直接切换回读事件
        set_event(fd, EPOLLIN | EPOLLET, 1);
        return 0;
    }

    if (conn_list[fd].status == 0 || conn_list[fd].status == 2)
    {
        int ret = http_response(&conn_list[fd]);
    }

    if (conn_list[fd].status == -1)
    {
        printf("status: %d, send\n", conn_list[fd].status);

        int sent = send_list(fd, &conn_list[fd].wlink);
        if (sent < 0)
        {
            // 发送过程中出错,连接已关闭
            return -1;
        }


        conn_list[fd].sent += sent;
        if (!LIST_EMPTY(&conn_list[fd].wlink))
        {
            // 还有数据未发送完,继续监听写事件
            
            set_event(fd, EPOLLOUT | EPOLLET, 1);
            return 0;
        }

        else
        {
            // 数据发送完毕
            conn_list[fd].status = 0;

            printf("sendLen: %d\n", conn_list[fd].sent);

            conn_list[fd].sent = 0;
            clear_list(&conn_list[fd].wlink);

            set_event(fd, EPOLLIN | EPOLLET, 1);
            memset(conn_list[fd].filename, 0, BUFFER_LENGTH);
            conn_list[fd].file_name_length = 0;
            return 0;
        }
    }

    if (conn_list[fd].status == 1)
    {
        printf("status: %d, send\n", conn_list[fd].status);

        int sent = send_list(fd, &conn_list[fd].wlink);
        
        if (sent < 0)
        {
            // 发送过程中出错,连接已关闭
            return -1;
        }

        conn_list[fd].sent += sent;

        if (!LIST_EMPTY(&conn_list[fd].wlink))
        {
            // 还有数据未发送完,继续监听写事件
            set_event(fd, EPOLLOUT | EPOLLET, 1);
            return 0;
        }

        else
        {
            // 数据发送完毕
            conn_list[fd].status = 2;

            printf("sendLen: %d\n", conn_list[fd].sent);
           
            conn_list[fd].sent = 0;
            clear_list(&conn_list[fd].wlink);

            set_event(fd, EPOLLOUT | EPOLLET, 1);
            return 0;
        }
    }

    if (conn_list[fd].status == 3)
    {
        printf("status: %d, finished\n", conn_list[fd].status);
        conn_list[fd].status = 0;

        memset(conn_list[fd].filename, 0, BUFFER_LENGTH);
        conn_list[fd].file_name_length = 0;
        set_event(fd, EPOLLIN | EPOLLET, 1);
    }
}

int main()
{
    conn_list = get_connlist(CONNECTION_SIZE);
    int epfd = init_epfd();
    printf("epfd: %d\n", epfd);

    struct epoll_event *events = malloc(sizeof(struct epoll_event) * CONNECTION_SIZE);
    memset(events, 0, sizeof(struct epoll_event) * CONNECTION_SIZE);

    int socketfd = socket(AF_INET, SOCK_STREAM, 0);
    init_server(socketfd, 8000);
    set_event(socketfd, EPOLLIN, 0);
    conn_register(socketfd, BUFFER_LENGTH, accept_cb, NULL, 0);

    while (1)
    {

        int nready = epoll_wait(epfd, events, CONNECTION_SIZE, -1);

        for (int i = 0; i < nready; i++)
        {
            int fd = events[i].data.fd;
            if (events[i].events & EPOLLIN)
            {
                conn_list[fd].in_callback(fd);
            }

            if (events[i].events & EPOLLOUT)
            {
                conn_list[fd].out_callback(fd);
            }
        }
    }

    close(epfd);
    free(events);
    close_connlist(conn_list, CONNECTION_SIZE);
    return 0;
}

int init_server(int sockfd, int port)
{
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    setReUseAddr(sockfd); // 启用端口复用,注意一定要放在bind前面。否则端口复用无效,程序断开重启后,bind会失效

    bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    listen(sockfd, 5);

    printf("listenfd: %d\n", sockfd);
}

更多推荐