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

项目中HTTP协议处理部分(续)

 // 循环读取客户数据,直到无数据可读或对方关闭连接// 非阻塞ET工作模式下,需要一次性将数据读完bool http_conn::read_once(){LOG_TRACE << "in read_once: m_read_idx: " << m_read_idx << " m_TRIGMode: " << m_TRIGMode;if (m_read_idx >= READ_BUFFER_SIZE){return false;}int bytes_read = 0;// LT读取数据if (0 == m_TRIGMode){LOG_TRACE << "0 == m_TRIGMODE  LT读取数据";bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);LOG_INFO << "call recv: m_sockfd: " << m_sockfd;LOG_INFO << " bytes_read: " << bytes_read;LOG_INFO << "m_read_buf : " << m_read_buf;LOG_INFO << "m_read_idx: " << m_read_idx;LOG_INFO << "m_read_buf + m_read_idx : " << m_read_buf + m_read_idx;m_read_idx += bytes_read;if (bytes_read <= 0){return false;}return true;}else{// ET读数据LOG_TRACE << "1 == m_TRIGMODE" << "  ET读数据 ";while (true){bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);if (bytes_read == -1){if (errno == EAGAIN || errno == EWOULDBLOCK){break;}return false;}else if (bytes_read == 0){return false;}m_read_idx += bytes_read;}return true;}}

这段代码是 http_conn 类中的 read_once 方法,用于从客户端套接字读取 HTTP 请求数据到缓冲区,并根据配置的触发模式(LT 或 ET)处理非阻塞 I/O 读取逻辑,确保高效且完整地获取请求数据。

核心功能与设计背景

在 HTTP 服务器中,客户端的请求数据通过套接字传输,由于使用非阻塞 I/O,需要通过该方法读取数据并存储到 m_read_buf 缓冲区中。该方法针对水平触发(LT) 和边缘触发(ET) 两种模式做了不同处理,以适配 epoll 的两种工作模式。

关键变量说明

  • m_read_buf:用于存储读取到的请求数据的缓冲区。
  • m_read_idx:记录缓冲区中已读取数据的末尾位置(即下一次读取的起始点)。
  • READ_BUFFER_SIZE:缓冲区的最大容量(预定义常量),避免缓冲区溢出。
  • m_sockfd:当前连接的客户端套接字文件描述符。
  • m_TRIGMode:触发模式标记(0 为 LT 模式,1 为 ET 模式)。
  • bytes_read:单次 recv 调用读取到的字节数

代码逻辑详解

1. 缓冲区溢出检查
if (m_read_idx >= READ_BUFFER_SIZE) {return false;
}
  • 若当前已读取数据长度(m_read_idx)达到缓冲区上限,无法继续读取,返回 false
2. 水平触发(LT)模式读取(m_TRIGMode == 0

LT 模式下,epoll 会持续通知有数据可读,直到数据被读完。因此只需读取一次:

bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);
m_read_idx += bytes_read;
if (bytes_read <= 0) {return false;
}
return true;
  • 调用 recv 从套接字读取数据,存储到 m_read_buf 中 m_read_idx 之后的位置,读取长度为缓冲区剩余空间(READ_BUFFER_SIZE - m_read_idx)。
  • 若 bytes_read <= 0
    • bytes_read == 0:客户端关闭连接。
    • bytes_read == -1:读取出错(如连接异常)。
    • 均返回 false 表示读取失败。
  • 否则更新 m_read_idx 并返回 true,表示读取成功。
3. 边缘触发(ET)模式读取(m_TRIGMode == 1

ET 模式下,epoll 仅在数据到达时通知一次,需一次性读完所有可用数据:

while (true) {bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);if (bytes_read == -1) {if (errno == EAGAIN || errno == EWOULDBLOCK) {break; // 数据已读完,退出循环}return false; // 其他错误,读取失败} else if (bytes_read == 0) {return false; // 客户端关闭连接}m_read_idx += bytes_read; // 更新已读数据长度
}
return true;
  • 使用 while 循环持续调用 recv,直到无数据可读。
  • 当 bytes_read == -1 且错误码为 EAGAIN 或 EWOULDBLOCK 时,表明当前数据已全部读完(非阻塞模式下的正常返回),退出循环。
  • 若 bytes_read == 0 或其他错误,返回 false
  • 循环结束后返回 true,表示已读完所有可用数据。

总结

read_once 方法是 HTTP 服务器处理请求数据的关键步骤,通过区分 LT/ET 模式实现了高效的非阻塞读取:

  • LT 模式下按需单次读取,依赖 epoll 持续通知。
  • ET 模式下通过循环一次性读完所有数据,减少 epoll 通知次数,提升性能。

该方法确保了请求数据被完整存入缓冲区,为后续的 HTTP 协议解析(如解析请求行、请求头)提供了完整的输入数据。

    // 解析http请求行,获得请求方法,目标url及http版本号http_conn::HTTP_CODE http_conn::parse_request_line(char *text){LOG_TRACE << "text: " << text;m_url = strpbrk(text, " \t");LOG_INFO << "m_url: " << m_url;if (nullptr == m_url || *m_url == '\0'){return BAD_REQUEST;}*m_url++ = '\0';char *method = text;if (strcasecmp(method, "GET") == 0){m_method = GET;}else if (strcasecmp(method, "POST") == 0){m_method = POST;cgi = 1;}else{return BAD_REQUEST;}LOG_INFO << "m_method: " << m_method << " " << getMethod(m_method);m_url += strspn(m_url, " \t");//检索匹配连续的\tm_version = strpbrk(m_url, " \t");//查找从m_url开始后第一个\t的位置LOG_INFO << "m_version : " << m_version;if (nullptr == m_version){return BAD_REQUEST;}*m_version++ = '\0';m_version += strspn(m_version, " \t");//可能不止一个\t,GET https://www.baidu.com/ HTTP/1.1LOG_INFO << "m_version: " << m_version;if (strcasecmp(m_version, "HTTP/1.1") != 0){return BAD_REQUEST;}if (strncasecmp(m_url, "http://", 7) == 0){m_url += 7;m_url = strchr(m_url, '/');}if (strncasecmp(m_url, "https://", 8) == 0){m_url += 8;m_url = strchr(m_url, '/');}LOG_INFO << "m_version: " << m_version;// for(int i = 0;m_url[i] != '\0'; ++i)//   {//       printf("%d => %d => %c \n",i,m_url[i],m_url[i]);//   }if (nullptr == m_url || m_url[0] != '/'){return BAD_REQUEST;}// 当url为/时,显示判断界面if (strlen(m_url) == 1){strcat(m_url, "judge.html");}LOG_INFO << "m_url : " << m_url;m_check_state = CHECK_STATE_HEADER;return NO_REQUEST;//return }
int main()
{const char* str1 = "importent siglificant improve";const char* str2 = "si";auto x = strpbrk(str1, str2);//在第一个字符串str1中查找第一个匹配第二个字符串str2中任一字符的字符,并返回该字符在str1中的位置cout << *x << endl;
}

这段代码是 http_conn 类中的 parse_request_line 方法,用于解析 HTTP 请求行,提取请求方法(如 GET/POST)、目标 URL 和 HTTP 版本号,并进行格式校验。请求行是 HTTP 请求的第一行,格式为 方法 URL 版本号(如 GET /index.html HTTP/1.1)。

核心功能与流程

1. 定位 URL 位置
m_url = strpbrk(text, " \t");
  • strpbrk(text, " \t") 在请求行字符串 text 中查找第一个空格或制表符,定位到 URL 的起始位置(请求方法后的分隔符)。
  • 若未找到分隔符(m_url 为空),说明请求行格式错误,返回 BAD_REQUEST
2. 提取并校验请求方法
*m_url++ = '\0';  // 将分隔符替换为字符串结束符,拆分出请求方法
char *method = text;  // text 此时指向请求方法(如 "GET")
if (strcasecmp(method, "GET") == 0) {m_method = GET;
} else if (strcasecmp(method, "POST") == 0) {m_method = POST;cgi = 1;  // POST 方法通常需要 CGI 处理
} else {return BAD_REQUEST;  // 不支持其他方法
}
  • 通过替换分隔符为 \0,将 text 截断为纯请求方法字符串(如 text 变为 "GET")。
  • 仅支持 GET 和 POST 方法,其他方法返回错误。
3. 定位并校验 HTTP 版本号
m_url += strspn(m_url, " \t");  // 跳过 URL 前的空格/制表符
m_version = strpbrk(m_url, " \t");  // 查找 URL 后的分隔符,定位版本号
if (nullptr == m_version) {return BAD_REQUEST;  // 无版本号,格式错误
}*m_version++ = '\0';  // 拆分出 URL
m_version += strspn(m_version, " \t");  // 跳过版本号前的空格/制表符if (strcasecmp(m_version, "HTTP/1.1") != 0) {return BAD_REQUEST;  // 仅支持 HTTP/1.1 版本
}
  • 先跳过 URL 前的空白字符,确保 m_url 指向 URL 实际内容。
  • 再通过分隔符定位版本号,拆分后校验是否为 HTTP/1.1(仅支持该版本)。
4. 处理 URL 格式
// 移除 URL 中的协议前缀(http:// 或 https://)
if (strncasecmp(m_url, "http://", 7) == 0) {m_url += 7;m_url = strchr(m_url, '/');  // 定位到域名后的路径(如 /index.html)
}
if (strncasecmp(m_url, "https://", 8) == 0) {m_url += 8;m_url = strchr(m_url, '/');
}// 校验 URL 格式(必须以 / 开头)
if (nullptr == m_url || m_url[0] != '/') {return BAD_REQUEST;
}// 特殊处理:若 URL 为根路径 /,默认指向 judge.html
if (strlen(m_url) == 1) {strcat(m_url, "judge.html");
}
  • 移除可能存在的 http:// 或 https:// 前缀,仅保留路径部分(如将 http://example.com/index.html 处理为 /index.html)。
  • 确保 URL 以 / 开头(HTTP 规范要求),否则返回错误。
  • 根路径 / 自动映射到 judge.html 页面。
5. 状态更新与返回
m_check_state = CHECK_STATE_HEADER;  // 解析完请求行后,下一步解析请求头
return NO_REQUEST;  // 表示需要继续解析(尚未获取完整请求)

关键变量说明

  • text:指向请求行的字符串(如 GET /index.html HTTP/1.1)。
  • m_url:存储解析后的 URL 路径(如 /index.html)。
  • m_method:存储请求方法(GET 或 POST)。
  • m_version:存储 HTTP 版本号(需为 HTTP/1.1)。
  • m_check_state:状态机变量,更新为 CHECK_STATE_HEADER 表示下一步解析请求头。
  • cgi:标记是否需要 CGI 处理(POST 方法设为 1)。

总结

parse_request_line 是 HTTP 请求解析的第一步,通过字符串处理函数拆分请求行,校验格式合法性,并提取关键信息(方法、URL、版本)。解析成功后,将状态机切换到请求头解析阶段,为后续处理(如解析请求头、处理 CGI 等)奠定基础。

 // 解析http请求的一个头部信息http_conn::HTTP_CODE http_conn::parse_headers(char *text){if (text[0] == '\0'){if (m_content_length != 0) //{m_check_state = CHECK_STATE_CONTENT;LOG_INFO<<"m_check_state = CHECK_STATE_CONTENT";return NO_REQUEST;}return GET_REQUEST;}else if (strncasecmp(text, "Connection:", 11) == 0){text += 11;text += strspn(text, " \t");if (strcasecmp(text, "keep-alive") == 0){m_linger = true;}}else if (strncasecmp(text, "Content-length:", 15) == 0){text += 15;text += strspn(text, " \t");m_content_length = atol(text);}else if (strncasecmp(text, "Host:", 5) == 0){text += 5;text += strspn(text, " \t");m_host = text;}else{LOG_INFO << " oop!unknow header: " << text;}return NO_REQUEST;}

这段代码是 http_conn 类中的 parse_headers 方法,用于解析 HTTP 请求头(Header),提取关键头部信息(如连接方式、内容长度、主机名等),并根据解析结果更新状态机,决定下一步处理逻辑。

核心功能与流程

HTTP 请求头由多行键值对组成(如 Connection: keep-alive),每行以 \r\n 结尾,所有头部结束后以空行(\r\n)标识。该方法逐行解析这些头部,提取必要信息并更新状态。

1. 处理头部结束标志(空行)
if (text[0] == '\0') {if (m_content_length != 0) {m_check_state = CHECK_STATE_CONTENT;  // 需要继续解析请求体LOG_INFO << "m_check_state = CHECK_STATE_CONTENT";return NO_REQUEST;}return GET_REQUEST;  // 头部解析完成,且无请求体
}
  • 当 text 为空字符串(text[0] == '\0')时,表示已读到头部结束的空行。
    • 若 m_content_length != 0(存在请求体):状态机切换到 CHECK_STATE_CONTENT,返回 NO_REQUEST 表示需要继续解析请求体。
    • 若 m_content_length == 0(无请求体):返回 GET_REQUEST 表示整个请求解析完成。
2. 解析 Connection 头部
else if (strncasecmp(text, "Connection:", 11) == 0) {text += 11;  // 跳过 "Connection:"text += strspn(text, " \t");  // 跳过头部值前的空格/制表符if (strcasecmp(text, "keep-alive") == 0) {m_linger = true;  // 标记为长连接}
}
  • strncasecmp 不区分大小写比较,判断是否为 Connection 头部。
  • 提取头部值(如 keep-alive),若为 keep-alive 则设置 m_linger = true,表示客户端希望保持连接。
3. 解析 Content-length 头部
else if (strncasecmp(text, "Content-length:", 15) == 0) {text += 15;  // 跳过 "Content-length:"text += strspn(text, " \t");  // 跳过空格/制表符m_content_length = atol(text);  // 转换为整数,记录请求体长度
}
  • 提取 Content-length 的值(请求体的字节数),存储到 m_content_length,用于后续判断请求体是否完整。
4. 解析 Host 头部
else if (strncasecmp(text, "Host:", 5) == 0) {text += 5;  // 跳过 "Host:"text += strspn(text, " \t");  // 跳过空格/制表符m_host = text;  // 记录主机名(如 `example.com`)
}
  • Host 头部是 HTTP/1.1 必需的,用于指定请求的主机名(尤其在虚拟主机场景中),将其存储到 m_host
5. 处理未知头部
else {LOG_INFO << " oop!unknow header: " << text;  // 打印未知头部,不做处理
}
  • 对不认识的头部仅打印日志,不影响整体解析(HTTP 允许扩展头部)。
6. 默认返回值
return NO_REQUEST;  // 表示仍需继续解析其他头部
  • 除头部结束的情况外,解析完一行头部后返回 NO_REQUEST,告知调用者需要继续解析下一行。

关键变量说明

  • text:指向当前待解析的请求头行(已被 parse_line 方法处理为以 \0 结尾的字符串)。
  • m_check_state:状态机变量,用于标记当前解析阶段(此处可能切换到 CHECK_STATE_CONTENT)。
  • m_linger:标记是否为长连接(keep-alive)。
  • m_content_length:记录请求体的长度(用于判断请求体是否完整)。
  • m_host:存储请求的主机名。
  • HTTP_CODE 返回值:NO_REQUEST(需继续解析)、GET_REQUEST(解析完成)。

总结

parse_headers 方法是 HTTP 请求解析的第二阶段(继请求行之后),通过识别关键头部字段(ConnectionContent-lengthHost),获取连接方式、请求体长度等核心信息,并根据是否存在请求体决定下一步解析请求体还是结束解析。该方法确保服务器正确理解客户端的请求细节,为后续处理(如读取请求体、返回响应)提供必要的元数据。

// 判断http请求是否被完整读入http_conn::HTTP_CODE http_conn::parse_content(char *text){if (m_read_idx >= (m_content_length + m_checked_idx))//读取的>=请求头+请求体{text[m_content_length] = '\0';// POST请求中最后为输入的用户名和密码m_string = text;return GET_REQUEST;}return NO_REQUEST;}

http_conn 类中与缓冲区解析相关的三个关键整数成员变量,用于跟踪 HTTP 请求数据在读取缓冲区中的位置和解析进度,是 HTTP 请求解析状态管理的核心变量。

各变量含义与作用:

  1. m_read_idx

    • 表示读取缓冲区中已有效存储的数据长度(即已从 socket 读取到缓冲区的字节总数)。
    • 缓冲区 m_read_buf 的有效数据范围是 [0, m_read_idx - 1],新读取的数据会从 m_read_idx 位置开始存储。
    • 例如:若从 socket 读取了 500 字节数据,则 m_read_idx 会被更新为 500,标识缓冲区前 500 字节为有效数据。
  2. m_checked_idx

    • 表示已解析校验过的缓冲区数据位置(即已完成解析的字节数)。
    • 解析过程中,从 m_checked_idx 开始处理未解析的数据,解析完成后更新该值。
    • 例如:若已解析完前 300 字节,则 m_checked_idx 为 300,下一次解析从 300 位置开始。
  3. m_start_line

    • 表示当前正在解析的请求行 / 请求头的起始位置(在缓冲区中的索引)。
    • HTTP 请求的行(如请求行、请求头)以 \r\n 结尾,解析一行时,m_start_line 指向该行的第一个字符,解析完成后更新为下一行的起始位置。
    • 例如:解析完一行后,通过 \r\n 定位到下一行的开头,将 m_start_line 设为该位置。

三者的协同关系:

  • 缓冲区数据的处理流程:m_start_line(当前行起始)→ 解析该行数据 → 更新 m_checked_idx(已解析到的位置)→ 读取新数据时更新 m_read_idx(总有效数据长度)。
  • 例如:
    • 初始时 m_read_idx = 0(无数据),m_checked_idx = 0(未解析),m_start_line = 0(准备解析第一行)。
    • 读取数据后 m_read_idx 增加,解析时从 m_checked_idx 开始查找 \r\n 分隔符,确定行的范围 [m_start_line, 分隔符位置],解析完成后将 m_start_line 设为分隔符后一位,m_checked_idx 同步更新。
http://www.dtcms.com/a/436077.html

相关文章:

  • 南京网站开发南京乐识不错wordpress文章阅读数
  • 宠物网站设计说明书上海建设网站公司
  • 网站建设具体要求吴江区经济开发区建设工程网站
  • wordpress建外贸网站网站建设专员 岗位职责
  • pc网站同步手机网站千度搜索引擎
  • AI 算力加速指南(中端篇):RTX 3060/i7-12 代 / 16G 内存的多任务优化实战,从卡顿到并行(一)
  • Ymodem协议详解
  • 制作简易网站用织梦做网站有钱途吗
  • 高效无风扇1000W AC-DC电源系统设计:基于开关耦合电感与ZVS技术的实现
  • Spring Boot 集成 JavaMail 发送邮件
  • 返利网 网站开发制作网站赚钱吗
  • [实战] 实时任务 vs 非实时任务:在PREEMPT-RT环境下的编程实践
  • RabbitMq入门之概括
  • 山西营销网站建设那个公司好上海百度seo
  • 经验分享:如何通过SAP HANA数据库优化将SAP B1性能提升50%
  • 免费注册域名邮箱龙岗优化网站建设
  • 如何通过cpa网站做推广产品展厅柜设计公司
  • 机器视觉滤光片怎么选?
  • 韶关市建设与房地产信息网站营销排名seo
  • 波音网站开发php网站开发用什么ide
  • 电子商务网站功能设计与分析微信社群营销怎么做
  • 运城手机网站制作郑州高端设计公司
  • 音乐网站建设规划第1ppt模板免费下载
  • 如何对网站进行分析网站开发合作
  • GUI自动化之pywinauto
  • 杭州网站设计费用app软件下载入口
  • 网站建设php教程建设一个好的网站
  • 遵义网站建设找工作百安居装修口碑怎么样
  • 用别人的网站视频做app网站建设文字设计
  • 网站建设推广兼职地推一手项目平台