当前位置: 首页 > news >正文

web服务器HTTP协议处理部分

 // 处理http_conn::HTTP_CODE http_conn::process_read(){LINE_STATUS line_status = LINE_OK;//行解析状态HTTP_CODE ret = NO_REQUEST;//HTTP请求解析结果char *text = 0;//指向当前解析到的行数据的指针LOG_TRACE << "in process_read: m_check_state: " << m_check_state << " line_status: " << line_status;while ((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) ||((line_status = parse_line()) == LINE_OK)){text = get_line();m_start_line = m_checked_idx;LOG_TRACE << "while => pares_line : text: " << text;LOG_TRACE << "m_start_line: " << m_start_line;LOG_TRACE << "m_check_state: " << m_check_state << " " << getCheckState(m_check_state);switch (m_check_state){case CHECK_STATE_REQUESTLINE:{ret = parse_request_line(text);LOG_INFO << "parse_request_line ret: " << static_cast<int>(ret) << " " << Get_HTTPCODE(ret);if (ret == BAD_REQUEST){return BAD_REQUEST;}break;}case CHECK_STATE_HEADER:{ret = parse_headers(text);LOG_INFO<<" parse_headers(text) text: "<<text<<" ret: "<<static_cast<int>(ret) << " " << Get_HTTPCODE(ret);if (ret == BAD_REQUEST){return BAD_REQUEST;}else if (ret == GET_REQUEST){return do_request();}break;}case CHECK_STATE_CONTENT:{ret = parse_content(text);if (ret == GET_REQUEST){return do_request();}line_status = LINE_OPEN;break;}default:return INTERNAL_ERROR;}}LOG_TRACE << " end http_conn::process_read ";return NO_REQUEST;}

这段代码是http_conn类中的process_read方法,主要功能是处理 HTTP 请求的读取和解析过程,是 HTTP 请求处理的核心逻辑之一。

核心作用

按照 HTTP 协议规范,逐步解析请求数据(请求行、请求头、请求体),并根据解析状态决定后续操作(如调用do_request处理请求或返回错误)。

关键变量

  1. line_status:行解析状态(LINE_OK表示解析到完整行,LINE_BAD表示格式错误,LINE_OPEN表示未解析完成)。
  2. ret:HTTP 请求解析结果(NO_REQUEST表示需继续解析,GET_REQUEST表示解析完成,BAD_REQUEST等表示错误)。
  3. text:指向当前解析到的行数据的指针。
  4. m_check_state:当前解析阶段(状态机状态),包括:
    • CHECK_STATE_REQUESTLINE:解析请求行阶段
    • CHECK_STATE_HEADER:解析请求头阶段
    • CHECK_STATE_CONTENT:解析请求体阶段

逻辑流程

  1. 循环解析行数据通过while循环持续解析请求数据中的行:

    • 条件 1:若当前处于解析请求体阶段(CHECK_STATE_CONTENT)且上一行解析正常(LINE_OK),继续解析。
    • 条件 2:调用parse_line()解析新行,若成功解析到完整行(LINE_OK),继续处理。
  2. 按阶段解析 HTTP 请求根据m_check_state的不同,分阶段处理:

    • 请求行解析(CHECK_STATE_REQUESTLINE

      • 调用parse_request_line(text)解析请求方法(GET/POST)、URL、HTTP 版本。
      • 若解析失败(返回BAD_REQUEST),直接返回错误。
      • 解析成功后,状态机自动进入CHECK_STATE_HEADER(请求头解析阶段)。
    • 请求头解析(CHECK_STATE_HEADER

      • 调用parse_headers(text)解析各请求头(如ConnectionContent-Length等)。
      • 若解析失败(BAD_REQUEST),返回错误。
      • 若解析完成(GET_REQUEST,如无请求体或请求头已读完),调用do_request()处理请求。
    • 请求体解析(CHECK_STATE_CONTENT

      • 调用parse_content(text)解析请求体数据(如 POST 表单数据)。
      • 若解析完成(GET_REQUEST),调用do_request()处理请求。
      • 若未完成,将line_status设为LINE_OPEN,等待更多数据。
  3. 异常处理

    • m_check_state为未知状态,返回INTERNAL_ERROR(服务器内部错误)。
    • 循环退出(如数据不完整)时,返回NO_REQUEST,表示需继续读取数据。

总结

该方法通过状态机模式分阶段解析 HTTP 请求,配合parse_line(行解析)、parse_request_line(请求行解析)等辅助函数,逐步完成请求的解析工作。解析过程中若发现错误则立即返回,若解析完成则调用do_request处理具体业务(如返回静态资源或处理 CGI 请求),是 HTTP 服务器处理请求的核心逻辑实现。

http_conn::HTTP_CODE http_conn::do_request(){strcpy(m_real_file, doc_root);int len = strlen(doc_root);const char *p = strrchr(m_url, '/');LOG_INFO<<"m_real_file: "<<m_real_file;LOG_INFO<<"m_url: "<<m_url;// 处理cgiif (cgi == 1 && (*(p + 1) == '2' || *(p + 1) == '3')){// 根据标志判断是登录检测还是注册检测char flag = m_url[1];char *m_url_real = (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, "/");strcat(m_url_real, m_url + 2);strncpy(m_real_file + len, m_url_real, FILENAME_LEN - len - 1);LOG_INFO<<"m_real_file: "<<m_real_file;free(m_url_real);// 将用户名和密码提取出来// user=123&passwd=123char name[100], password[100];int i;for (i = 5; m_string[i] != '&'; ++i)name[i - 5] = m_string[i];name[i - 5] = '\0';int j = 0;for (i = i + 10; m_string[i] != '\0'; ++i, ++j)password[j] = m_string[i];password[j] = '\0';if (*(p + 1) == '3'){// 如果是注册,先检测数据库中是否有重名的// 没有重名的,进行增加数据char *sql_insert = (char *)malloc(sizeof(char) * 200);strcpy(sql_insert, "INSERT INTO user(username, passwd) VALUES(");strcat(sql_insert, "'");strcat(sql_insert, name);strcat(sql_insert, "', '");strcat(sql_insert, password);strcat(sql_insert, "')");if (users.find(name) == users.end()){m_lock.lock();int res = mysql_query(mysql, sql_insert);users.insert(pair<string, string>(name, password));m_lock.unlock();if (!res)strcpy(m_url, "/log.html");elsestrcpy(m_url, "/registerError.html");}elsestrcpy(m_url, "/registerError.html");}// 如果是登录,直接判断// 若浏览器端输入的用户名和密码在表中可以查找到,返回1,否则返回0else if (*(p + 1) == '2'){if (users.find(name) != users.end() && users[name] == password)strcpy(m_url, "/welcome.html");elsestrcpy(m_url, "/logError.html");}}LOG_INFO<<"p "<<p;if (*(p + 1) == '0'){char *m_url_real = (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, "/register.html");strncpy(m_real_file + len, m_url_real, strlen(m_url_real));free(m_url_real);}else if (*(p + 1) == '1'){char *m_url_real = (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, "/log.html");strncpy(m_real_file + len, m_url_real, strlen(m_url_real));free(m_url_real);}else if (*(p + 1) == '5'){char *m_url_real = (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, "/picture.html");strncpy(m_real_file + len, m_url_real, strlen(m_url_real));free(m_url_real);}else if (*(p + 1) == '6'){char *m_url_real = (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, "/video.html");strncpy(m_real_file + len, m_url_real, strlen(m_url_real));free(m_url_real);}else if (*(p + 1) == '7'){char *m_url_real = (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, "/fans.html");strncpy(m_real_file + len, m_url_real, strlen(m_url_real));free(m_url_real);}elsestrncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1);if (stat(m_real_file, &m_file_stat) < 0)//若 stat 调用失败(返回值 <0),说明文件不存在return NO_RESOURCE;//获取 m_real_file 对应的文件元信息(如是否存在、类型、权限等),存入 m_file_stat 结构体if (!(m_file_stat.st_mode & S_IROTH))//检查文件是否允许其他用户读取return FORBIDDEN_REQUEST;if (S_ISDIR(m_file_stat.st_mode))//用于判断文件是否为目录return BAD_REQUEST;LOG_INFO<<" int fd = open(m_real_file, O_RDONLY): "<<m_real_file;int fd = open(m_real_file, O_RDONLY);m_file_address = (char *)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);LOG_INFO<<"m_file_address: "<<m_file_address;close(fd);LOG_TRACE<<" end: ";return FILE_REQUEST;}

这段代码是http_conn类中的do_request方法,主要功能是处理 HTTP 请求,根据请求的 URL 路径确定要访问的资源文件,处理 CGI 相关的登录 / 注册逻辑,并准备好对应的文件资源供后续响应使用。

核心流程概述

  1. 基于请求的 URL 路径(m_url)和网站根目录(doc_root),构建实际要访问的本地文件路径(m_real_file)。
  2. 处理 CGI 相关的登录(URL路径含/2)和注册(URL路径含/3)请求,与数据库交互验证或插入用户信息,并跳转至对应结果页面。
  3. 处理其他静态页面请求(如注册页/0、登录页/1、图片页/5等),映射到对应的 HTML 文件。
  4. 验证目标文件的存在性、访问权限,最终通过内存映射(mmap)加载文件内容,为响应做准备。

关键代码解析

1. 初始化文件路径
strcpy(m_real_file, doc_root);  // 将网站根目录(如/var/www/)复制到实际文件路径
int len = strlen(doc_root);     // 获取根目录长度
const char *p = strrchr(m_url, '/');  // 找到URL中最后一个'/'的位置(用于解析路径)
  • m_real_file:存储本地文件系统中实际要访问的文件路径。
  • strrchr(m_url, '/'):定位 URL 中最后一个/,用于提取路径中的关键标识(如/2/3等)。
2. 处理 CGI 登录 / 注册请求(cgi == 1

cgi标志为 1,且 URL 路径中最后一个/后为2(登录)或3(注册)时,触发 CGI 处理逻辑:

(1)解析 URL 与表单数据
// 提取URL中的标志(如URL为"/2xxx"时,标志为'2')
char flag = m_url[1];
// 构建实际访问的CGI文件路径(忽略标志部分)
char *m_url_real = (char *)malloc(...);
strcpy(m_url_real, "/");
strcat(m_url_real, m_url + 2);  // 跳过标志部分(如从"/2login"中提取"login")
strncpy(m_real_file + len, m_url_real, ...);  // 拼接根目录与实际路径
(2)提取用户名和密码(表单数据格式:user=xxx&passwd=xxx
char name[100], password[100];
// 从m_string(表单数据)中提取用户名(跳过"user=")
for (i = 5; m_string[i] != '&'; ++i)name[i - 5] = m_string[i];
name[i - 5] = '\0';// 提取密码(跳过"&passwd=")
for (i = i + 10; m_string[i] != '\0'; ++i, ++j)password[j] = m_string[i];
password[j] = '\0';
  • 假设表单数据为user=alice&passwd=123,则namealicepassword123
(3)注册逻辑(*(p + 1) == '3'
// 构建SQL插入语句:INSERT INTO user(username, passwd) VALUES('name', 'password')
char *sql_insert = (char *)malloc(...);
strcpy(sql_insert, "INSERT INTO user(username, passwd) VALUES(");
strcat(sql_insert, "'");
strcat(sql_insert, name);
strcat(sql_insert, "', '");
strcat(sql_insert, password);
strcat(sql_insert, "')");// 检查用户是否已存在(users为内存中的用户映射表)
if (users.find(name) == users.end()) {m_lock.lock();  // 加锁保证线程安全int res = mysql_query(mysql, sql_insert);  // 执行SQL插入users.insert(pair<string, string>(name, password));  // 更新内存映射表m_lock.unlock();// 根据插入结果跳转页面(成功→登录页,失败→注册失败页)strcpy(m_url, res ? "/registerError.html" : "/log.html");
} else {strcpy(m_url, "/registerError.html");  // 用户名已存在
}
(4)登录逻辑(*(p + 1) == '2'
// 检查用户名是否存在且密码匹配(users为内存映射表)
if (users.find(name) != users.end() && users[name] == password)strcpy(m_url, "/welcome.html");  // 登录成功→欢迎页
elsestrcpy(m_url, "/logError.html");  // 失败→登录错误页
3. 处理静态页面请求

根据 URL 中最后一个/后的标识,映射到对应的 HTML 文件:

if (*(p + 1) == '0')       // 标识0→注册页面strcpy(m_url_real, "/register.html");
else if (*(p + 1) == '1')  // 标识1→登录页面strcpy(m_url_real, "/log.html");
else if (*(p + 1) == '5')  // 标识5→图片页面strcpy(m_url_real, "/picture.html");
else if (*(p + 1) == '6')  // 标识6→视频页面strcpy(m_url_real, "/video.html");
else if (*(p + 1) == '7')  // 标识7→粉丝页面strcpy(m_url_real, "/fans.html");
else                        // 其他URL直接拼接路径strncpy(m_real_file + len, m_url, ...);
4. 验证文件并加载资源
// 检查文件是否存在
if (stat(m_real_file, &m_file_stat) < 0)return NO_RESOURCE;  // 文件不存在// 检查文件是否有其他用户可读权限
if (!(m_file_stat.st_mode & S_IROTH))return FORBIDDEN_REQUEST;  // 无权限// 检查是否为目录(不允许访问目录)
if (S_ISDIR(m_file_stat.st_mode))return BAD_REQUEST;  // 是目录,请求无效// 打开文件并通过mmap映射到内存(高效读取大文件)
int fd = open(m_real_file, O_RDONLY);
m_file_address = (char *)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);  // 映射后可关闭文件描述符return FILE_REQUEST;  // 成功准备好文件资源

总结

该方法是 HTTP 请求处理的核心环节,负责解析请求路径、处理动态 CGI 逻辑(登录 / 注册)、映射静态资源,并通过文件验证和内存映射确保资源可被正确响应。代码中通过 URL 路径的标识字符(如230等)实现不同功能的路由,同时使用内存映射提升文件读取效率。

mmap函数建立高效通道,让你在内存里直接查看和修改文件内容,而这个改动会自动同步到磁盘上。

  • 传统方式 (read/write): 像用桶从井里(磁盘)打水,倒进你家的水缸(内存)里。你要用水(访问数据)只能从水缸里取。想改变井里的水,得把水缸的水打回井里。

  • 内存映射 (mmap): 像给井装了一个魔法镜面。你低头看镜子(内存),直接就看到井底(磁盘)的水,并且你伸手就能直接碰到井水。你在镜面上做的任何改动(修改内存),井里的水(磁盘文件)会自动跟着改变

bool http_conn::write(){int temp = 0;if (bytes_to_send == 0){modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);init();return true;}while (1){LOG_INFO << "types_to_send : " << bytes_to_send;LOG_INFO << "m_sockfd: " << m_sockfd;temp = writev(m_sockfd, m_iv, m_iv_count);LOG_INFO<<"temp: "<<temp;LOG_INFO<<"m_iv[0]: "<<(char*)(m_iv[0].iov_base)<<" "<<m_iv[0].iov_len;LOG_INFO<<"m_iv[1]: "<<(char*)(m_iv[1].iov_base)<<" "<<m_iv[1].iov_len;if (temp < 0){if (errno == EAGAIN){modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);return true;}unmap();return false;}bytes_have_send += temp;bytes_to_send -= temp;if (bytes_have_send >= m_iv[0].iov_len){m_iv[0].iov_len = 0;m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx);m_iv[1].iov_len = bytes_to_send;}else{m_iv[0].iov_base = m_write_buf + bytes_have_send;m_iv[0].iov_len = m_iv[0].iov_len - bytes_have_send;}if (bytes_to_send <= 0){unmap();modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);if (m_linger){init();return true;}else{return false;}}}}

http_conn类中的write()方法,用于处理 HTTP 响应数据的发送,是 Web 服务器向客户端返回数据的核心逻辑。

核心功能

通过writev系统调用高效发送响应数据(包括响应头和响应体),支持非阻塞 I/O 模式,处理发送过程中的部分写入、缓冲区满等情况,并根据连接状态维护 epoll 事件监听模式。

关键变量

  1. bytes_to_send:待发送的总字节数(响应头 + 响应体)。
  2. bytes_have_send:已发送的字节数。
  3. m_iviovec结构体数组,用于分散 / 聚集 I/O(writev的参数),通常包含两部分:
    • m_iv[0]:指向响应头缓冲区(m_write_buf)及长度。
    • m_iv[1]:指向响应体(如通过mmap映射的文件内容m_file_address)及长度。
  4. m_iv_countm_iv数组的元素个数(通常为 1 或 2)。
  5. m_linger:连接是否保持长连接(keep-alive)的标志。

逻辑流程

  1. 检查是否无数据可发bytes_to_send == 0(所有数据已发送完毕):

    • 将套接字m_sockfd的 epoll 事件重新设置为EPOLLIN(等待新的请求)。
    • 初始化连接状态(init()),返回true
  2. 循环发送数据通过while(1)循环持续发送数据,直到所有数据发送完毕或遇到错误:

    • 调用writev发送数据writev可一次性发送分散在多个缓冲区的数据(响应头 + 响应体),提高效率。

    • 处理发送错误

      • temp < 0(发送失败):
        • 若错误码为EAGAIN(缓冲区暂时不可用,非阻塞 I/O 的典型情况):将套接字事件改为EPOLLOUT(等待可写),返回true(后续可继续发送)。
        • 其他错误:释放mmap映射的文件(unmap()),返回false(连接需关闭)。
    • 更新发送进度

      • bytes_have_send累加已发送字节数,bytes_to_send减去已发送字节数。
      • 调整缓冲区指针(根据已发送数据量更新m_iv的指向和长度):
        • 若已发送数据超过响应头长度(bytes_have_send >= m_iv[0].iov_len):响应头已发完,后续只需发送响应体,调整m_iv[1]指向剩余未发的响应体部分。
        • 否则:响应头未发完,调整m_iv[0]指向剩余未发的响应头部分。
    • 发送完成处理

      • bytes_to_send <= 0(所有数据发送完毕):
        • 释放mmap映射(unmap())。
        • 将套接字事件改回EPOLLIN(等待新请求)。
        • 若为长连接(m_linger == true):初始化连接状态,返回true(保持连接)。
        • 若为短连接:返回false(触发连接关闭)。

技术特点

  1. 分散 / 聚集 I/O:使用writev同时发送响应头(内存缓冲区)和响应体(文件映射),减少系统调用次数,提升性能。
  2. 非阻塞 I/O 适配:通过检测EAGAIN错误,配合 epoll 事件切换(EPOLLOUT),实现非阻塞模式下的高效数据发送。
  3. 连接状态管理:根据m_linger标志决定是否保持连接,符合 HTTP 的keep-alive机制。
  4. 资源释放:发送完成后及时释放mmap映射的文件资源,避免内存泄漏。

总结

该方法是 HTTP 响应发送的核心实现,通过高效的 I/O 操作和事件管理,确保响应数据可靠发送,并根据连接类型维护连接状态,是 Web 服务器处理输出的关键逻辑。

 bytes_have_send += temp;bytes_to_send -= temp;if (bytes_have_send >= m_iv[0].iov_len){m_iv[0].iov_len = 0;m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx);m_iv[1].iov_len = bytes_to_send;}else{m_iv[0].iov_base = m_write_buf + bytes_have_send;m_iv[0].iov_len = m_iv[0].iov_len - bytes_have_send;}

这段代码的作用是在非阻塞 I/O 模式下动态调整发送缓冲区,确保 HTTP 响应数据(包括响应头和响应体)能分批次正确发送。我们通过一个具体例子来理解:

假设场景

假设服务器需要向客户端发送一个 HTTP 响应,包含两部分数据:

  1. 响应头(m_write_buf):长度为500字节(例如包含HTTP/1.1 200 OKContent-Length等信息)。
  2. 响应体(m_file_address):长度为2000字节(例如一个 HTML 文件内容)。

初始化时,m_iv(iovec 数组,用于分散写)的状态为:

  • m_iv[0].iov_base = m_write_buf(指向响应头起始地址)
  • m_iv[0].iov_len = 500(响应头总长度)
  • m_iv[1].iov_base = m_file_address(指向响应体起始地址)
  • m_iv[1].iov_len = 2000(响应体总长度)
  • bytes_to_send = 500 + 2000 = 2500(总待发送字节数)
  • bytes_have_send = 0(已发送字节数)

第一次发送(部分数据)

调用writev后,假设只成功发送了300字节(非阻塞模式下常见,数据未一次性发完):

  • bytes_have_send = 300
  • bytes_to_send = 2500 - 300 = 2200

此时判断bytes_have_send (300) < m_iv[0].iov_len (500),执行else分支:

  • 调整响应头缓冲区指针:m_iv[0].iov_base = m_write_buf + 300(从响应头的第 300 字节开始发送剩余部分)
  • 调整响应头剩余长度:m_iv[0].iov_len = 500 - 300 = 200(还剩 200 字节响应头未发)

m_iv状态变为:

  • m_iv[0]:指向响应头第 300 字节,长度 200
  • m_iv[1]:仍指向响应体起始地址,长度 2000

第二次发送(完成响应头,开始响应体)

再次调用writev,发送剩余的 200 字节响应头和部分响应体,假设共发送了1000字节

  • 其中 200 字节来自响应头,800 字节来自响应体
  • bytes_have_send = 300 + 1000 = 1300
  • bytes_to_send = 2200 - 1000 = 1200

此时判断bytes_have_send (1300) >= m_iv[0].iov_len (500)(响应头已发完),执行if分支:

  • 标记响应头缓冲区已用完:m_iv[0].iov_len = 0
  • 调整响应体缓冲区指针:m_iv[1].iov_base = m_file_address + (1300 - 500) = m_file_address + 800(从响应体的第 800 字节开始发送剩余部分)
  • 调整响应体剩余长度:m_iv[1].iov_len = 1200(还剩 1200 字节响应体未发)

m_iv状态变为:

  • m_iv[0]:长度 0(已用完)
  • m_iv[1]:指向响应体第 800 字节,长度 1200

第三次发送(完成所有数据)

最后一次调用writev,发送剩余的 1200 字节响应体:

  • bytes_have_send = 1300 + 1200 = 2500
  • bytes_to_send = 0(所有数据发送完成)

总结

通过动态调整m_iv的指针和长度,代码确保了:

  1. 即使数据分多次发送,也能从上次中断的位置继续发送(避免重复发送或遗漏)。
  2. 优先发送响应头,再发送响应体,符合 HTTP 协议的传输顺序。
  3. 高效利用writev的分散写特性,减少系统调用次数。

这种逻辑是处理非阻塞网络 I/O 的典型实现,适配了网络传输中 “数据分批到达 / 发送” 的场景。

//响应bool http_conn::add_response(const char *format, ...){if (m_write_idx >= WRITE_BUFFER_SIZE){return false;}va_list arg_list;//用于存储可变参数列表的类型va_start(arg_list, format);//初始化可变参数列表int len = vsnprintf(m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list);if (len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx)){va_end(arg_list);return false;}m_write_idx += len;va_end(arg_list);LOG_INFO << "request: " << m_write_buf;return true;}

这段代码定义了http_conn类中的add_response方法,其核心功能是向 HTTP 响应缓冲区中格式化添加数据(如响应头、响应体内容等),并确保缓冲区不溢出。以下是详细解析:

函数作用

add_response是一个可变参数函数,用于将格式化的字符串(如 HTTP 状态行、头部字段、HTML 内容等)追加到响应缓冲区m_write_buf中,为后续发送 HTTP 响应做准备。

代码逻辑拆解

  1. 缓冲区溢出检查

    if (m_write_idx >= WRITE_BUFFER_SIZE)
    {return false;
    }
    
    • m_write_idx:记录当前响应缓冲区中已使用的字节数(即下一个待写入位置的索引)。
    • WRITE_BUFFER_SIZE:响应缓冲区的总大小(宏定义,固定值)。
    • 若当前已使用空间超过缓冲区总大小,直接返回false,表示写入失败。
  2. 处理可变参数

    va_list arg_list;
    va_start(arg_list, format);
    
    • va_list:用于存储可变参数列表的类型。
    • va_start:初始化可变参数列表,format是最后一个固定参数,作为可变参数的起始标记。
    • 目的是解析函数传入的可变参数(如add_response("Content-Length:%d\r\n", len)中的len)。
  3. 格式化写入缓冲区

    int len = vsnprintf(m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list);
    
    • vsnprintf:格式化可变参数并写入指定缓冲区,返回实际写入的字节数(不包含终止符\0)。
    • 参数说明:
      • 第一个参数:写入位置(从缓冲区当前已用位置m_write_idx开始)。
      • 第二个参数:最大可写入字节数(避免溢出,计算方式为 “缓冲区总大小 - 已用大小 - 1”,预留 1 字节给终止符\0)。
      • 第三个参数:格式化字符串(如"HTTP/1.1 %d %s\r\n")。
      • 第四个参数:可变参数列表。
  4. 检查写入结果

    if (len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx))
    {va_end(arg_list);return false;
    }
    
    • vsnprintf返回的实际写入长度len大于等于最大可写入字节数,说明缓冲区空间不足,写入失败,清理参数列表并返回false
  5. 更新缓冲区状态

    m_write_idx += len;
    va_end(arg_list);
    
    • 成功写入后,更新m_write_idx(累加实际写入的字节数)。
    • va_end:清理可变参数列表,释放资源。
  6. 日志输出与返回

    LOG_INFO << "request: " << m_write_buf;
    return true;
    
    • 输出当前缓冲区内容到日志(便于调试)。
    • 返回true表示数据成功添加到缓冲区。

示例场景

例如在构建 HTTP 响应头时:

add_response("HTTP/1.1 %d %s\r\n", 200, "OK");
  • 函数会将"HTTP/1.1 200 OK\r\n"格式化后追加到m_write_buf中,并更新m_write_idx记录写入长度。
  • 若缓冲区空间足够,返回true;否则返回false,避免缓冲区溢出。

总结

add_response是 HTTP 响应构建的核心工具函数,通过可变参数和格式化字符串,安全地向缓冲区添加响应数据,同时通过严格的边界检查确保内存安全,为后续writev发送响应数据提供了正确的缓冲区内容。

    bool http_conn::add_status_line(int status, const char *title){return add_response("%s %d %s\r\n", "HTTP/1.1", status, title);}bool http_conn::add_headers(int content_len){return add_content_length(content_len) && add_linger() && add_blank_line();}bool http_conn::add_content_length(int content_len){return add_response("Content-Length:%d\r\n", content_len);}bool http_conn::add_content_type(){return add_response("Content-Type:%s\r\n", "text/html");}bool http_conn::add_linger(){return add_response("Connection:%s\r\n", (m_linger == true) ? "keep-alive" : "close");}bool http_conn::add_blank_line(){return add_response("%s", "\r\n");}bool http_conn::add_content(const char *content){return add_response("%s", content);}

这段代码是http_conn类中用于构建 HTTP 响应头部和内容的一系列辅助函数,它们基于之前提到的add_response方法,封装了 HTTP 响应各组成部分的生成逻辑,确保响应格式符合 HTTP 协议规范。

各函数功能解析

  1. add_status_line:生成 HTTP 响应状态行

    bool http_conn::add_status_line(int status, const char *title)
    {return add_response("%s %d %s\r\n", "HTTP/1.1", status, title);
    }
    
    • 作用:构造 HTTP 响应的第一行(状态行),格式为 HTTP版本 状态码 状态描述\r\n
    • 示例:调用 add_status_line(200, "OK") 会生成 HTTP/1.1 200 OK\r\n,表示请求成功。
    • 参数:status 是 HTTP 状态码(如 200、404),title 是对应的状态描述(如 "OK"、"Not Found")。
  2. add_headers:批量添加 HTTP 响应头部字段

    bool http_conn::add_headers(int content_len)
    {return add_content_length(content_len) && add_linger() && add_blank_line();
    }
    
    • 作用:组合调用多个头部生成函数,一次性添加核心响应头部,包括内容长度、连接状态,并以空行结束头部区域。
    • 逻辑:通过&&确保所有头部字段都添加成功才返回true,只要有一个失败则整体失败。
  3. add_content_length:添加内容长度头部

    bool http_conn::add_content_length(int content_len)
    {return add_response("Content-Length:%d\r\n", content_len);
    }
    
    • 作用:生成 Content-Length: 长度值\r\n 头部,告知客户端响应体的字节数。
    • 示例:add_content_length(1024) 生成 Content-Length:1024\r\n,帮助客户端判断数据是否接收完整。
  4. add_content_type:添加内容类型头部

    bool http_conn::add_content_type()
    {return add_response("Content-Type:%s\r\n", "text/html");
    }
    
    • 作用:生成 Content-Type: 类型\r\n 头部,这里固定为text/html,表示响应体是 HTML 文本。
    • 说明:实际应用中可能根据文件类型(如图片、视频)动态修改类型值(如image/jpeg),此处简化为固定 HTML 类型。
  5. add_linger:添加连接状态头部

    bool http_conn::add_linger()
    {return add_response("Connection:%s\r\n", (m_linger == true) ? "keep-alive" : "close");
    }
    
    • 作用:生成 Connection: 状态\r\n 头部,决定 TCP 连接是否保持(长连接 / 短连接)。
    • 逻辑:根据成员变量m_linger判断:
      • m_linger为true:生成Connection: keep-alive,表示连接保持,可复用。
      • m_linger为false:生成Connection: close,表示响应后关闭连接。
  6. add_blank_line:添加头部结束标记

    bool http_conn::add_blank_line()
    {return add_response("%s", "\r\n");
    }
    
    • 作用:生成\r\n(空行),这是 HTTP 协议规定的头部与响应体的分隔符,标志头部区域结束。
  7. add_content:添加响应体内容

    bool http_conn::add_content(const char *content)
    {return add_response("%s", content);
    }
    
    • 作用:将具体内容(如 HTML 文本、错误信息)添加到响应体中。
    • 示例:调用add_content("<h1>Hello World</h1>")会将 HTML 内容追加到响应缓冲区。

整体逻辑与作用

这些函数通过封装add_response,实现了 HTTP 响应的模块化构建:

  • 先调用add_status_line生成状态行;
  • 再通过add_headers添加核心头部字段(内容长度、连接状态等);
  • 最后通过add_content添加响应体内容。

整个过程严格遵循 HTTP 协议格式,确保客户端能正确解析响应。例如,一个完整的成功响应构建流程可能是:

add_status_line(200, "OK");       // 状态行
add_headers(1024);                // 头部字段(含长度、连接状态、空行)
add_content("<html>...</html>");  // 响应体内容
bool http_conn::process_write(HTTP_CODE ret){LOG_TRACE << " ret : " << static_cast<int>(ret) << " " << Get_HTTPCODE(ret);switch (ret){case INTERNAL_ERROR:{add_status_line(500, error_500_title);add_headers(strlen(error_500_form));if (!add_content(error_500_form))return false;break;}case BAD_REQUEST:{add_status_line(404, error_404_title);add_headers(strlen(error_404_form));if (!add_content(error_404_form))return false;break;}case FORBIDDEN_REQUEST:{add_status_line(403, error_403_title);add_headers(strlen(error_403_form));if (!add_content(error_403_form))return false;break;}case FILE_REQUEST:{add_status_line(200, ok_200_title);LOG_INFO<<"m_file_stat.st_size: "<<m_file_stat.st_size;if (m_file_stat.st_size != 0){add_headers(m_file_stat.st_size);m_iv[0].iov_base = m_write_buf;m_iv[0].iov_len = m_write_idx;m_iv[1].iov_base = m_file_address;m_iv[1].iov_len = m_file_stat.st_size;m_iv_count = 2;bytes_to_send = m_write_idx + m_file_stat.st_size;LOG_INFO<<"bytes_to_send: "<<bytes_to_send;LOG_INFO<<"m_write_buf: "<<m_write_buf;LOG_INFO<<"m_write_idx: "<<m_write_idx;LOG_INFO<<"m_file_address: "<<m_file_address;LOG_INFO<<"m_file_stat.st_size: "<<m_file_stat.st_size;return true;}else{const char *ok_string = "<html><body></body></html>";add_headers(strlen(ok_string));if (!add_content(ok_string))return false;}}default:return false;}m_iv[0].iov_base = m_write_buf;m_iv[0].iov_len = m_write_idx;m_iv_count = 1;bytes_to_send = m_write_idx;return true;}

这段代码是http_conn类中的process_write方法,核心功能是根据 HTTP 请求处理的结果(HTTP_CODE)构建对应的 HTTP 响应数据,为后续通过write方法发送响应做准备。以下是详细解析:

函数作用

process_write接收一个HTTP_CODE类型的参数ret(表示请求处理的结果,如成功、错误等),根据不同的结果生成对应的 HTTP 响应内容(包括状态行、响应头、响应体),并初始化用于发送数据的缓冲区结构(iovec数组)和发送状态变量(待发送字节数等)。

核心逻辑拆解

1. 基于HTTP_CODE的分支处理

通过switch语句根据ret的值(请求处理结果)生成不同的响应:

  • INTERNAL_ERROR(500 服务器内部错误)

    add_status_line(500, error_500_title);  // 添加状态行:HTTP/1.1 500 Internal Error
    add_headers(strlen(error_500_form));    // 添加响应头(含内容长度等)
    if (!add_content(error_500_form))       // 添加响应体(500错误页面HTML)return false;
    
    • 当服务器处理请求时发生内部错误(如代码异常),生成 500 响应,包含预设的错误页面内容。
  • BAD_REQUEST(404 资源未找到)

    add_status_line(404, error_404_title);  // 状态行:HTTP/1.1 404 Not Found
    add_headers(strlen(error_404_form));    // 响应头(含错误页面长度)
    if (!add_content(error_404_form))       // 响应体(404错误页面HTML)return false;
    
    • 当请求的资源不存在时,生成 404 响应。
  • FORBIDDEN_REQUEST(403 禁止访问)

    add_status_line(403, error_403_title);  // 状态行:HTTP/1.1 403 Forbidden
    add_headers(strlen(error_403_form));    // 响应头
    if (!add_content(error_403_form))       // 响应体(403错误页面HTML)return false;
    
    • 当请求的资源存在但客户端无访问权限时,生成 403 响应。
  • FILE_REQUEST(200 成功请求文件)这是处理成功请求的核心分支,分两种情况:

    • 请求的文件非空

      add_status_line(200, ok_200_title);    // 状态行:HTTP/1.1 200 OK
      add_headers(m_file_stat.st_size);      // 响应头(含文件实际大小)
      // 初始化iovec数组(分散写结构)
      m_iv[0].iov_base = m_write_buf;        // 第1个缓冲区:响应头(已写入m_write_buf)
      m_iv[0].iov_len = m_write_idx;         // 响应头长度
      m_iv[1].iov_base = m_file_address;     // 第2个缓冲区:文件内容(内存映射地址)
      m_iv[1].iov_len = m_file_stat.st_size; // 文件大小
      m_iv_count = 2;                        // 缓冲区数量为2
      bytes_to_send = m_write_idx + m_file_stat.st_size;  // 总待发送字节数(头+文件)
      return true;
      
      • 这里使用iovec数组(分散写)将响应头(在m_write_buf中)和文件内容(通过内存映射m_file_address指向)合并发送,减少系统调用次数,提高效率。
    • 请求的文件为空

      const char *ok_string = "<html><body></body></html>";  // 空文件默认响应体
      add_headers(strlen(ok_string));    // 响应头(含空内容长度)
      if (!add_content(ok_string))       // 添加空响应体return false;
      
      • 若文件大小为 0,返回一个空的 HTML 页面作为响应体。
  • default(未处理的状态):直接返回false,表示响应构建失败。

2. 响应发送结构初始化(错误响应和空文件场景)

对于非FILE_REQUEST的场景(错误响应)或空文件场景,执行以下逻辑:

m_iv[0].iov_base = m_write_buf;  // 缓冲区指向响应头+响应体(均在m_write_buf中)
m_iv[0].iov_len = m_write_idx;   // 总长度(头+体)
m_iv_count = 1;                  // 仅需1个缓冲区
bytes_to_send = m_write_idx;     // 待发送字节数等于缓冲区长度
return true;
  • 这些场景中,响应内容(头 + 体)都存储在m_write_buf中,因此只需一个iovec缓冲区即可。

关键变量说明

  • m_iviovec类型数组,用于writev系统调用的分散写操作,可同时发送多个不连续的缓冲区。
  • m_iv_countm_iv数组中有效缓冲区的数量。
  • bytes_to_send:记录当前需要发送的总字节数,供write方法判断发送进度。
  • m_write_buf:存储响应头和小响应体(如错误页面)的缓冲区。
  • m_file_address:通过mmap映射的文件内容地址(用于大文件高效发送)。

总结

process_write是 HTTP 响应构建的 “总装厂”:

  1. 根据请求处理结果(HTTP_CODE)生成对应状态的响应内容(状态行、头、体)。
  2. 针对不同响应类型(文件 / 错误)优化缓冲区结构:文件响应使用双缓冲区(头 + 文件映射),错误响应使用单缓冲区(统一存储在m_write_buf)。
  3. 初始化发送相关的变量(m_ivbytes_to_send等),为后续write方法的实际发送提供数据基础。

这一设计既符合 HTTP 协议规范,又通过分散写(writev)和内存映射(mmap)提升了大文件传输的效率。

 void http_conn::process(){LOG_TRACE << "in process";HTTP_CODE read_ret = process_read();LOG_INFO << "read_ret: " << static_cast<int>(read_ret) << Get_HTTPCODE(read_ret);if (read_ret == NO_REQUEST){modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);return;}bool write_ret = process_write(read_ret);LOG_INFO << "read_ret : " << static_cast<int>(read_ret) << Get_HTTPCODE(read_ret);LOG_INFO<<"write_ret: "<<write_ret;if (!write_ret){close_conn();}modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);LOG_TRACE << "end process";}

这段代码是http_conn类中的process方法,是 HTTP 请求处理的核心流程控制器,负责协调请求的读取、处理结果的响应以及事件监听模式的切换。以下是详细解析:

函数核心作用

process方法是单次请求处理的主入口,通过调用请求读取函数(process_read)和响应构建函数(process_write),完成 “读取请求→处理请求→构建响应” 的完整流程,并根据处理状态调整 epoll 对套接字的事件监听模式。

代码逻辑拆解

  1. 日志记录与请求读取

    LOG_TRACE << "in process";
    HTTP_CODE read_ret = process_read();
    LOG_INFO << "read_ret: " << static_cast<int>(read_ret) << Get_HTTPCODE(read_ret);
    
    • 首先通过LOG_TRACE记录进入处理流程的日志。
    • 调用process_read()读取并解析客户端的 HTTP 请求,返回HTTP_CODE类型的处理结果(如NO_REQUESTFILE_REQUESTBAD_REQUEST等)。
    • 记录process_read的返回值(状态码及对应描述),便于调试和监控。
  2. 处理 “未完成请求” 场景

    if (read_ret == NO_REQUEST)
    {modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);return;
    }
    
    • NO_REQUEST表示请求未完全读取(如数据不完整),需要继续等待客户端发送数据。
    • 调用modfd调整 epoll 对当前套接字(m_sockfd)的监听事件为读事件(EPOLLIN),并保持原触发模式(m_TRIGMode,边缘触发或水平触发)。
    • 直接返回,等待下次 epoll 通知有可读数据时再继续处理。
  3. 构建响应并处理结果

    bool write_ret = process_write(read_ret);
    LOG_INFO << "read_ret : " << static_cast<int>(read_ret) << Get_HTTPCODE(read_ret);
    LOG_INFO<<"write_ret: "<<write_ret;
    
    • 根据process_read返回的请求处理结果(read_ret),调用process_write构建对应的 HTTP 响应(状态行、头部、响应体等)。
    • process_write返回bool值,表示响应是否成功构建。
    • 记录响应构建的结果日志,便于追踪问题。
  4. 处理响应构建失败的情况

    if (!write_ret)
    {close_conn();
    }
    
    • 如果process_write返回false(响应构建失败),调用close_conn关闭当前连接,释放相关资源。
  5. 切换为写事件监听

    modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);
    LOG_TRACE << "end process";
    
    • 无论响应构建是否成功(若失败已在上述步骤关闭连接),将 epoll 对当前套接字的监听事件调整为写事件(EPOLLOUT),以便后续通过write方法将响应数据发送给客户端。
    • 记录流程结束的日志。

关键设计思路

  • 事件驱动切换:通过modfd动态调整 epoll 监听的事件类型(EPOLLIN→EPOLLOUT),符合非阻塞 I/O 的事件驱动模型,避免资源浪费。
  • 分步处理:将请求处理拆分为 “读取解析” 和 “响应构建” 两个阶段,通过HTTP_CODE传递状态,逻辑清晰。
  • 容错处理:对请求未完成(NO_REQUEST)和响应构建失败的场景分别处理,保证连接状态的正确性。

总结

process方法是 HTTP 请求处理的 “调度中心”,它:

  1. 触发请求的读取与解析;
  2. 根据解析结果决定是否继续等待数据或构建响应;
  3. 动态调整 epoll 的事件监听模式,为下一步的读 / 写操作做准备;
  4. 通过日志和错误处理确保流程的可追踪性和稳定性。

这一设计是高性能 Web 服务器中 “一次请求 - 响应” 流程的典型实现,适配了 epoll 的 I/O 多路复用机制。

http://www.dtcms.com/a/447820.html

相关文章:

  • 手机网站开发在pc端上海建设安全协会官网
  • 网页界面设计基础知识seo排名公司
  • 做水果网站平台wordpress文章加载
  • 给网站划分栏目前端开发是什么意思
  • 网站子域名怎么设置餐饮公司做网站好处
  • 搭建网站合同做除尘环保的如何推广自己的网站
  • 长春网长春网站设计站建设宁波网站开发制作
  • 安阳网站开发随州哪里学做网站
  • 新乡网站优化中国建设银行官网招聘信息
  • 网站的分析手机网站 兼容
  • 做网站平台应该注意哪些网站建设浩森宇特
  • 苏州网站设计公司官网wordpress图文插件
  • 小企业网站维护一年多少钱凉山建设网站
  • 微信建网站平台的网站制作怎样容易
  • 网站建设与管理的主要内容绵阳网站设计制作
  • 帮别人做网站哪里可以接单wordpress图片上浮特效
  • 电商erp网站开发电脑维修网站模板下载
  • 冷门且好听的公司名字济南外贸seo
  • 优秀英文企业网站WordPress站内搜索代码
  • dede网站管理系统演示柞水县住房和城乡建设局网站
  • 一级做爰片c视频网站哪个网站做电子请帖好
  • 论坛网站建设软件在线图片转文字识别
  • 企业搭建一个营销型网站多少钱北邻京网站茵建设
  • 贵州便宜网站推广优化电话网站建设全部教程
  • 教育行业网站建设审批惠网 做网站
  • 自建电商网站有哪些做淘宝联盟网站
  • 天津网站建设制作品牌公司动漫设计专升本考什么
  • dedecms网站迁移奉贤建设机械网站
  • 徐州建站模板公司潮汕学院网站开发
  • 网站关键词优化多少钱陕西秦地建设有限公司网站