注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。
引入
- 之前发送文件都是先从磁盘读取出来,经过用户态的buffer,然后再传到内核。如果是小文件还好,大型文件就很慢了。
- 这里采用sendfile直接在内核区实现文件的传输,不经过用户态,这样子传输效率更高。
- 我们必须先实现发送响应头,然后再发送文件内容,可以使用状态机实现!!!
易错:
Content-Type记得修改(否则显示不正确):
传图片:image/jpg 或 image/png
传网页:text/html
注意:如果是image/jpg/png 浏览器会直接下载该文件而不是显示
测试:
在浏览器输入:
192.168.248.130/sample.jpg
192.168.248.130/lovc_c.png
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>
typedef int (*RCALLBACK)(int fd);
typedef struct conn
{
int fd;
char *rbuffer;
int rlength;
char *wbuffer;
int wlength;
#if 1
// 非阻塞模式变量:接收缓冲区
char *rlbuffer; // 动态接收缓冲区(拼接所有分片数据)
int rllength; // 动态接收缓冲区的实际总长度(避免依赖strlen)
// 非阻塞模式变量:发送缓冲区
char *wlbuffer; // 动态发送缓冲区(待发送的完整数据)
int wllength; // 动态发送缓冲区的总长度(实际字节数)
int w_sent; // 已发送的字节数(跟踪发送进度)
#endif
RCALLBACK in_callback;
RCALLBACK out_callback;
#if 1
int status; // { header, body}
char *filename;
int file_name_length;
#endif
} 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 set_event(int fd, int event, int flag);
int conn_register(int fd, int buffer_length, RCALLBACK in_callback, RCALLBACK out_callback, int flag);
int setNonblock(int fd);
int setReUseAddr(int fd);
int conn_close(int fd);
int close_connlist(conn *clist, int n);
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 flag)
{
conn_list_[fd].fd = fd;
conn_list_[fd].rlength = 0;
conn_list_[fd].wlength = 0;
conn_list_[fd].in_callback = in_callback;
conn_list_[fd].out_callback = out_callback;
conn_list_[fd].rbuffer = conn_list_[fd].wbuffer = NULL;
// 给阻塞模式使用的固定长度的buffer分配空间
if (flag == 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);
}
#if 1
// 初始化非阻塞模式变量
conn_list_[fd].rlbuffer = NULL;
conn_list_[fd].rllength = 0;
conn_list_[fd].wlbuffer = NULL;
conn_list_[fd].wllength = 0;
conn_list_[fd].w_sent = 0;
#endif
#if 1
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;
#endif
return 0;
}
int conn_close(int fd)
{
if (conn_list_[fd].fd == 0)
{
return 0;
}
if (conn_list_[fd].rbuffer != NULL)
{
conn_list_[fd].rbuffer = NULL;
free(conn_list_[fd].rbuffer);
}
if (conn_list_[fd].wbuffer != NULL)
{
conn_list_[fd].wbuffer = NULL;
free(conn_list_[fd].wbuffer);
}
if (conn_list_[fd].rlbuffer != NULL)
{
conn_list_[fd].rlbuffer = NULL;
free(conn_list_[fd].rlbuffer);
}
if (conn_list_[fd].wlbuffer != NULL)
{
conn_list_[fd].wlbuffer = NULL;
free(conn_list_[fd].wlbuffer);
}
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;
}
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
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)
{
// 处理非阻塞接收
if (recv_noblock(fd, BUFFER_LENGTH) <= 0)
{
return 0;
}
if (is_http_complete(conn_list[fd].rlbuffer, conn_list[fd].rllength))
{
http_request(&conn_list[fd]);
reset_r_noblock(fd);
set_event(fd, EPOLLOUT | EPOLLET, 1);
}
else
{
// 请求头不完整,继续等待更多数据
set_event(fd, EPOLLIN | EPOLLET, 1);
}
}
int send_cb(int fd)
{
if (conn_list[fd].wllength == 0 && conn_list[fd].wlbuffer == NULL && 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_noblock(fd);
if (sent < 0)
{
// 发送过程中出错,连接已关闭
return -1;
}
else if (sent < conn_list[fd].wllength)
{
// 还有数据未发送完,继续监听写事件
set_event(fd, EPOLLOUT | EPOLLET, 1);
return 0;
}
else if (sent == conn_list[fd].wllength)
{
// 数据发送完毕
conn_list[fd].status = 0;
printf("sendLen: %d\n", conn_list[fd].wllength);
printf("SEND: %.*s\n", conn_list[fd].wllength, conn_list[fd].wlbuffer);
reset_w_noblock(fd);
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_noblock(fd);
if (sent < 0)
{
// 发送过程中出错,连接已关闭
return -1;
}
else if (sent < conn_list[fd].wllength)
{
// 还有数据未发送完,继续监听写事件
set_event(fd, EPOLLOUT | EPOLLET, 1);
return 0;
}
else if (sent == conn_list[fd].wllength)
{
// 数据发送完毕
conn_list[fd].status = 2;
printf("sendLen: %d\n", conn_list[fd].wllength);
printf("SEND: %.*s\n", conn_list[fd].wllength, conn_list[fd].wlbuffer);
reset_w_noblock(fd);
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);
}
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
// 判断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; // 不完整,继续等待
}
// 在浏览器输入 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
// 我们简单点处理,只做资源文件的提取!!!
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);
}
// 这里只做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必须大于回复头,这个使用者会知道的,但是文件大小使用者不必知道
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 ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);
printf("ret: %d\n", ret);
if (ret == -1)
{
printf("errno: %d\n", errno);
}
if (ret == stat_buf.st_size)
{
printf("send successfully : bytes: %ld\n", stat_buf.st_size);
// 显示文件的内容:
// char buffer[stat_buf.st_size + 1];
// off_t new_offset = lseek(filefd, 0, SEEK_SET);
// int count = read(filefd, buffer, stat_buf.st_size);
// buffer[stat_buf.st_size] = '\0';
// printf("second send: %s", buffer);
// send(c->fd, buffer, stat_buf.st_size, 0);
}
#else
#endif
c->status = 3;
}
close(filefd);
}
#endif

问题:
文件长度为4789382,一次发送不完(发送缓存区装不下,之前测试百万并发,发送缓存区设置得很小),这里改为循环发送。
修改后:
#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
// 判断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; // 不完整,继续等待
}
// 在浏览器输入 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
// 我们简单点处理,只做资源文件的提取!!!
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);
}
// 这里只做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必须大于回复头,这个使用者会知道的,但是文件大小使用者不必知道
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
结果:



测试成功:
有该文件
状态依次由0、1、2、3变化,根据不同状态按序执行不同函数。
- 0 :配置请求头 (http_response)
- 1: 调用send (发送请求头)
- 2: 调用sendfile(http_response) :发送图片
- 3: 发送结束,恢复相关变量的状态,同时设置事件为读事件,然后将状态置为0
无该文件:状态直接变为-1:执行顺序
- 配置请求头 (http_response)
- -1:发送(send_cb):发送结束,恢复相关变量的状态,同时设置事件为读事件,然后将状态置为0
2031

被折叠的 条评论
为什么被折叠?



