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

大型门户网站建设哪便宜wordpress 4.7.3主题

大型门户网站建设哪便宜,wordpress 4.7.3主题,把公司建设成为 现代化企业,义乌制作网站开发目录 ✊请求报文--解析 流程图 状态机 状态机 -- 状态转移图 主状态机 从状态机 http 报文解析 HTTP_CODE 含义 从状态机 逻辑 主状态机 逻辑 #x1f41e;请求报文--响应 基础API stat mmap iovec writev 流程图 HTTP_CODE 含义(2) 代码分析 … 目录 ✊请求报文--解析 流程图 状态机 状态机 -- 状态转移图 主状态机  从状态机 http 报文解析 HTTP_CODE 含义 从状态机 逻辑 主状态机 逻辑 请求报文--响应 基础API stat mmap iovec writev 流程图 HTTP_CODE  含义(2) 代码分析 do_request process_write http_conn::write ✊请求报文--解析 流程图 状态机 状态机 -- 状态转移图 从状态机 -- 读取一行 主状态机 -- 解析该行 主状态机内部调用从状态机从状态机驱动主状态机 状态机转移图结合以下文本理解 主状态机  三种状态标识解析位置 CHECK_STATE_REQUESTLINE -- 解析  请求行CHECK_STATE_HEADER -- 解析  请求头CHECK_STATE_CONTENT -- 解析  消息体仅用于解析  POST请求 从状态机 三种状态标识解析一行的读取状态 LINE_OK完整读取一行LINE_BAD报文语法有误LINE_OPEN读取的行不完整 http 报文解析 流程 上一篇博客介绍了服务器接收 http请求 的流程 也就是浏览器发出 http连接请求服务器 主线程创建 http对象 接收 并将所有数据读入对应的 buffer 将该对象插入任务队列后工作线程从任务队列取出一个任务并处理 各子线程通过 process() 函数处理任务调用 process_read() 函数 和 process_write() 函数分别完成 报文解析 和 报文响应  void http_conn::process() {// 调用 process_read() 处理请求// 并返回 HTTP_CODE 枚举类型状态码HTTP_CODE read_ret process_read();// 请求不完整需要继续接收if (read_ret NO_REQUEST) {// 注册并监听 读事件等待下一次数据到来modfd(m_epollfd, m_sockfd, EPOLLLIN);return;}// 调用 process_write() 完成响应bool write_ret process_write(read_ret);// 响应失败 -- 关闭连接if (!write_ret) close_conn();// 响应成功 -- 注册并监听 写事件等待下一次写入响应数据modfd(m_epollfd, m_sockfd, EPOLLOUT); } HTTP_CODE 含义 HTTP请求的处理结果 头文件初始化了 8 种 报文解析涉及 4 种 NO_REQUEST 请求不完整需要继续读取请求报文数据GET_REQUEST 获得了完整的HTTP请求BAD_REQUEST 语法错误INTERNAL_ERROR 服务器内部错误该结果在 主状态机 逻辑switch 的 default 下一般不会触发 解析报文 整体流程 process_read 通过 while 循环对主从状态机进行封装循环处理报文每一行 判断条件 主状态机 转移到 CHECK_STATE_CONTENT解析消息体从状态机 转移到 LINE_OK解析请求行和请求头部两者为 或 关系条件为真则继续循环否则退出 循环体 从状态机 读取数据调用 get_line() 函数通过 m_start_line() 将 从状态机 读取的数据间接赋给 text主状态机 解析 text // m_start_line 是行在 buffer 起始位置 // 该位置后面的数据赋给 text // 此时的从状态机已提前将一行的末尾字符 // \r\n 变为 \0\0所以text可直接取出完整的行解析 char* get_line() {return m_read_buf m_start_line; }http_conn::HTTP_CODE http_conn::process_read() {// 初始化从状态机状态HTTP请求解析结果LINE_STATUS line_status LINE_OK;HTTP_CODE ret NO_REQUEST;char* text 0;// 为什么要写两个判断条件第一个判断条件// 为什么这样写// 具体主状态机逻辑--后面讲解// parse_line 为从状态机的具体实现while ( (m_check_state CHECK_STATE_CONTENT line_status LINE_OK) ||((line_statusparse_line()) LINE_OK) ) {text get_line();// m_start_line 是每一个数据行在m_read_buf的起始位置// m_checked_edx 从状态机 在m_read_buf中读取的位置m_start_line m_checked_idx;// 主状态机 3 种状态转移逻辑switch(m_check_state) {case CHECK_STATE_REQUESTLINE:{// 解析请求行ret parse_request_line(text);if (ret BAD_REQUEST)return BAD_REQUEST;break;}case CHECK_STATE_HEADER:{// 解析请求头ret parse_headers(text);if (ret BAD_REQUEST)return BAD_REQUEST;break;}case CHECK_STATE_CONTENT:{// 解析消息体ret parse_content(text);// 完整解析POST请求后跳转报文响应函数if (ret GET_REQUEST)return do_request();// 解析完消息体即完成报文解析避免再次进入循环// 更新 line_statusline_status LINE_OPEN;break;}default:return INTERNAL_ERROR;}}return NO_REQUEST; } 从状态机 逻辑 补充个基础知识 HTTP报文中每一行数据由 \r\n 作为结束字符空行只有 \r\n 因此可以通过查找 \r\n 将报文拆解为单独的行进行解析 本项目即利用了这点 从状态机 读取 buffer 中的数据将每行数据末尾的 \r\n 设置为 \0\0 并更新 从状态机 在 buffer 中读取的位置 m_checked_idx 以此驱动 主状态机 解析 从状态机从 m_raed_buf 中逐字节读取判断当前的字节是否为 \r 接下来的字符是 \n将 \r\n 修改为 \0\0将 m_checked_idx 指向下一行的开头则返回LINE_OK接下来到达 buffer 末尾表示 buffer 还需要继续接收返回 LINE_OPEN否则语法错误返回 LINE_BAD 当前字节不是 \r判断是否是 \n如果上次读取到 \r 就到了 buffer 末尾没有接收完整再次接收会出现这个情况 如果前一个字符是 \r则将 \r\n 修改为 \0\0将 m_checked_idx 指向下一行开头返回 LINE_OK当前字节不是 \r也不是 \n 表示接收不完整需要继续接收返回 LINE_OPEN // 从状态机用于分析出一行的内容 // 返回值为行的读取状态有 // LINE_OK, LINE_BAD, LINE_OPEN// m_read_idx 指向缓冲区 m_read_buf 数据末尾下一字节 // m_checked_idx 指向从状态机当前分析的字节http_conn::LINE_STATUS http_conn::parse_line() {char temp;for (; m_checked_idx m_read_idx; m_checked_idx){// temp 要分析的字节temp m_read_buf[m_checked_idx];// 如果当前是 \r则有可能读取到完整行if (temp \r) {// 下一字符达到了 buffer 结尾则接收不完整继续接收if (m_checked_idx 1 m_read_idx)return LINE_OPEN;// 下一字符是 \n将 \r\n 改为 \0\0else if (m_read_buf[m_checked_idx1] \n) {m_read_buf[m_checked_idx] \0;m_read_buf[m_checked_idx] \0;return LINE_OK;}// 都不符合返回 语法错误return LINE_BAD;}// 如果当前字符是 \n也可能读取到完整的行// 一般是上次读取到 \r就到 buffer 末尾没有接收完整// 再次接收时就会出现这种情况else if (temp \n) {// 前一字符是 \r 则接收完整if (m_checked_idx 1 m_read_buf[m_checked_idx-1] \r){m_read_buf[m_checked_idx-1] \0;m_read_buf[m_checked_idx] \0;return LINE_OK;}return LINE_BAD;}}// 没有找到 \r\n 需要继续接收return LINE_OPEN; } 主状态机 逻辑 1 处理请求行 主状态机 初始状态是 CHECK_STATE_REQUESTLINE通过调用 从状态机 驱动 主状态机 主状态机 解析前从状态机已经将每一行末尾的 \r\n 改为 \0\0 以便主状态机直接取出对应字符串进行处理 状态1CHECK_STATE_REQUESTLINE 主状态机 初始状态调用 parse_request_line() 解析 请求行解析函数从 m_read_buf 中解析 HTTP请求行获得请求方法目标URLHTTP版本号解析完成后主状态机状态变为 CHECK_STATE_HEADER // 解析http请求行获得请求方法目标URLhttp版本号 http_conn::HTTP_CODE http_conn::parse_request_line(char *text) {// HTTP报文中请求行用来说明// 请求类型要访问的资源所使用的HTTP版本号// 其中各个部分之间通过 \t 或 空格 分隔// 请求行中最先含有 空格 和 \t 任一字符的位置并返回m_url strpbrk(text, \t);// 如果没有 空格 或 \t则报文格式有误if (!m_url) return BAD_REQUEST;// 该位置改为 \0用于取出前面数据*m_url \0;// 取出数据并通过与 GET 和 POST 比较以确定请求方式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;// m_url 此时跳过了第一个空格或\t字符但不知道之后是否还有// 将 m_url 向后偏移通过查找继续跳过空格和\t字符// 指向请求资源的第一个字符m_url strspn(m_url, \t);// 使用与判断请求方式相同的逻辑判断 HTTP 版本号m_version strpbrk(m_url, \t);if (!m_version)return BAD_REQUEST;*m_version \0;m_version strspn(m_verison, \t);// 仅支持 HTTP/1.1if (strcasecmp(m_verison, HTTP/1.1) ! 0)return BAD_REQUEST;// 对请求资源前 7 个字符进行判断// 这里有些报文的请求资源会代有 http://// 要单独处理这种情况if (strncasecmp(m_url, http://, 7) 0) {m_url 7;m_url strchar(m_url, /);}// 同样的 https 情况if (strncasecmp(m_url, https://, 8) 0) {m_url 8;m_url strchar(m_url, /);}// 一般不会带有上述两种符号// 而是单独的 / 或 /后带访问资源if (!m_url || m_url[0] ! /)return BAD_REQUEST;// 当 url 为 / 时显示欢迎界面if (strlen(m_url) 1) strcat(m_url, judge.html);// 请求行 处理完毕将主状态机转移去处理 请求头m_check_state CHECK_STATE_HEADER;return NO_REQUEST; } 2 处理请求头 解析完 请求行 后主状态机继续分析请求头 报文中请求头 和 空行的处理使用同一个函数 通过判断当前 text 首位是不是 \0 字符 是 -- 当前处理的是 空行 不是 -- 当前处理的是 请求头 状态2CHECK_STATE_HEADER 调用 parse_headers() 解析 请求头判断空行 OR 请求头 是空行的话进而判断 content-length 是否为 0不是 0即 POST请求那么状态转移到 CHECK_STATE_CONTENT是 0说明是 GET 请求则报文解析结束若解析的是 请求头部字段则主要分析 connection 字段content-length 字段其他字段可以直接跳过connection 字段判断是 keep_alive 还是 close决定是长连接还是短连接content-length 字段用于读取 post 请求的 消息体长度 // 解析http请求的一个头部信息 http_conn::HTTP_CODE http_conn::parse_headers(char *text) {// 判断 空行 还是 请求行if (text[0] \0) { // 空行// 判断 GET 还是 POST 请求if (m_content_length ! 0) { // POST 请求// POST 需跳转到 消息体 处理状态m_check_state CHECK_STATE_CONTENT;return NO_REQUEST;}return GET_REQUEST; // GET 请求}// 解析请求头部 连接字段else if (strncasecmp(text, Connection:, 11) 0) {text 11;// 跳过 空格 和 \t 字符text strspn(text, \t);if (strcasecmp(text, keep-alive) 0) {// 如果是长连接将 linger 标志设置为 truem_linger true;}}// 解析请求头部 内容长度字段else if (strncasecmp(text, Content-length:, 15) 0) {text 15text strspn(text, \t);m_content_length atol(text);}// 解析请求头部 HOST字段else if (strncasecmp(text, Host:, 5) 0) {text 5;text strspn(text, \t);m_host text;}else printf(oop! unknown header: %s\n, text);return NO_REQUEST; } 3处理消息体 如果仅仅是 GET 请求比如项目中的欢迎界面那么 主状态机 只设置前两个状态即可 根据之前所说GET 和 POST 请求报文的区别有无消息体部分。 GET 请求没有消息体当解析完空行后便完成了报文解析 但后续的登录和注册功能为了避免将用户名和密码直接暴露在URL中我们在项目中改用了 POST 请求将用户名和密码添加在报文中作为消息体进行封装 为此我们需要在解析报文中添加 解析消息体 的模块 while ( (m_check_stateCHECK_STATE_CONTENT line_statusLINE_OK) || ( (line_statusparse_line() )LINE_OK) ) 判断条件为什么写成这样呢 解析 GET 请求报文中每一行都是 /r/n 结尾所以对报文进行拆解时仅用从状态机的状态 ( line_status parse_line() ) LINE_OK 但在 POST 请求报文中消息体的末尾没有任何字符所以不能使用 从状态机 的状态 这里转而使用 主状态机 的状态作为循环条件入口 那后面的 line_status LINE_OK 又为什么 解析完消息体后报文的完整解析就完成了 但此时 主状态机 的状态还是 CHECK_STATE_CONTENT 也就是说符合循环入口条件 还会再次进入循环这不是我们所希望的 为此增加了下面语句并在完成 消息体 解析后将 line_status 变量更改为 LINE_OPEN 此时可以跳出循环完成报文解析任务 状态3CHECK_STATE_CONTENT 仅用于解析 POST 请求调用 parse_content() 解析 消息体用于保存 post请求 消息体为后面登录和注册做准备 // 判断 http请求 是否被完整读入 http_conn::HTTP_CODE http_conn::parse_content(char *text) {// 判断 buffer 中是否读取了消息体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报文解析 是 TinyWebServer 最繁琐的部分 需要 多读 画图 来理解 请求报文--响应 本博客上半部分我们对 状态机 和 HTTP请求 -- 解析作了介绍 下面再介绍 服务器如何响应 http请求报文并将该报文发送给浏览器 基础API stat, mmap, iovec, writev  为了更好的源码阅读体验这里对源码使用的部分 API 进行介绍 stat stat() 函数 -- 取得指定文件的文件属性并将文件属性存储在 结构体 stat 中 #includesys/types.h #includesys/stat.h #includeunistd.h// 获取文件属性存储在 statbuf 中 int stat(const char *pathname, struct stat *statbuf);struct stat {mode_t st_mode; // 文件类型和权限off_t st_size; // 文件大小字节数 }; mmap 将一个文件 或 其他对象映射到内存提高文件访问速度 start -- 映射区的开始地址设置为 0 时表示由系统决定映射区起始地址length -- 映射区长度prot -- 期望的内存保护标志不能与文件的打开模式冲突 PROT_RAED 表示 页内容可以被读取flags -- 指定映射对象的类型映射选项和映射页是否可以共享 MAP_PRIVATE 建立一个写入时拷贝的私有映射内存区域的写入不会影响到原文件fd -- 有效的文件描述符一般是由 open() 函数返回off_toffset -- 被映射对象内容的起点 void* mmap(void* start, size_t length, int prot,int flags, int fd, off_t offset);int munmap(void* start, size_t length); iovec 定义一个 向量 元素用作一个 多元素数组 iov_base 指向数据的地址iov_len 表示数据长度 struct iovec {void *iov_base; // starting address of buffersize_t iov_len; // size of buffer }; writev 在一次函数调用中写多个 非连续缓冲区有时也将该函数成为 聚集写 filedes 表示文件描述符iov 为 前述 io 向量机制结构体 ioveciovcnt 结构体个数 #includesys/uio.h ssize_t writev(int filedes, const struct iovec *iov, int iovcnt); 成功则返回 已写字节数出错返回 -1 writev 以顺序 iov[0]iov[1] 到 iov[iovcnt - 1] 从缓冲区中聚集输出数据 writev 返回输出的字节总数通常等于所有缓冲区长度之和 特别注意 循环调用 writev() 时需要重新处理 iovec 中的指针 和 长度 该函数不会对这两个成员做任何处理 writev() 的返回值为 已写字节数但这个返回值的实用性不高 因为参数传入的是 iovec 数组计量单位是 iovcnt而不是字节数 还需要通过遍历 iovec 来计算新的基址 另外写入数据的 “结束点” 可能位于一个 iovec 中间的某个位置 因此需要调整临界的 iovec 的 io_base 和 io_len 流程图 浏览器 发出HTTP请求报文服务器接收该报文并调用 process_read() 解析根据解析结果 HTTP_CODE进入相应的逻辑和模块 其中服务器 子线程 完成报文的解析与响应 主线程监测 独写事件调用 read_once 和 http_conn::write 完成数据的 读取与发送 HTTP_CODE  含义(2) 表示 HTTP请求 的处理结果 头文件初始化了 8 种 报文 解析与响应 用到 7 种 NO_REQUEST 请求不完整需要继续读取请求报文数据 跳转主线程继续监测读事件 GET_REQUEST 获得了完整的HTTP请求 调用do_request完成请求资源映射 NO_RESOURCE 请求资源不存在 跳转process_write完成响应报文 BAD_REQUEST HTTP请求报文有语法错误或请求资源为目录 跳转process_write完成响应报文 FORBIDDEN_REQUEST 请求资源禁止访问没有读取权限 跳转process_write完成响应报文 FILE_REQUEST 请求资源可以正常访问 跳转process_write完成响应报文 INTERNAL_ERROR 服务器内部错误该结果在主状态机逻辑switch的default下一般不会触发 代码分析 do_request process_read() 返回值是对请求文件分析后的结果 一部分是语法错误导致的 BAD_REQUEST 一部分是 do_request() 返回的结果 该函数将 网站根目录 和 url文件 拼接再通过 stat 判断该文件属性 另外为了提高访问速度通过 mmap 进行映射将 普通文件 映射到 内存逻辑地址 为了更好的理解请求资源的 访问流程 这里介绍各种 页面跳转机制 浏览器网址栏的字符即 url可以抽象成 ip:prot/xxx xxx 通过 html 文件的 action 属性设置 m_url -- 请求报文中解析出的 请求资源以 / 开头也就是 /xxx TinyWebServer 中解析后的 m_url 有 8 种情况 / GET 请求跳转到 judge.html欢迎页面/0 POST 请求跳转到 register.html注册页面/1 POST 请求跳转到 log.html登陆页面/2CGISQL.cgi POST 请求进行登录校验验证成功 -- 跳转 welcome.html资源请求成功页面验证失败 -- 跳转 logError.html登录失败页面/3CGISQL.cgi POST 请求进行注册校验成功 -- 跳转 log.html登录页面失败 -- 跳转 registerError.html注册失败页面/5 POST 请求跳转 picture.html图片请求页面/6 POST 请求跳转 vedio.html视频请求页面/7 POST 请求跳转 fans.html关注页面 // 网站根目录文件夹内存放 请求资源 和跳转的 html 文件 const char* doc_root /home/qgy/github/ini_tinywebserver/root;http_conn::HTTP_CODE http_conn::do_request() {// 网站根目录doc_root的内容 复制到 m_real_filestrcpy(m_real_file, doc_root);int len strlen(doc_root);// 找到 m_url 中 / 的位置const char *p strrchr(m_url, /);// 实现 登录和注册 校验if (cgi 1 (*(p1) 2 || *(p1) 3) ) {// 根据标志判断 登录 OR 注册 检测// 同步线程登录校验// CGI多进程登录校验}// 请求资源为 /0表示跳转 注册页面if (*(p1) 0) {char *m_url_real (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, /register.html);// 将 网站目录 和 /register.html 拼接// 更新到 m_real_filestrncpy(m_real_file m_url_real, strlen(m_url_real));free(m_url_real);}// 请求资源为 /1表示跳转 登录页面else if (*(p1) 1) {char *m_url_real (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, /log.html);// 网站目录 和 /log.html 拼接// 更新到 m_real_filestrncpy(m_real_file len, m_url_real, strlen(m_url_real));free(m_url_real);}// 既不是登录也不是注册直接将 url 与 网站根目录 拼接// 这里是 welcome 界面请求服务器的一个图片else strncpy(m_real_file len, m_url, FILENAME_LEN - len - 1);// 通过 stat 获取 请求资源文件信息成功 则将信息更新到// m_file_stat 结构体// 失败 返回 NO__RESOURCE 状态表示 资源不存在if (stat(m_real_file, m_file_stat) 0)return NO_RESOURCE;// 判断文件权限是否可读不可读 则返回 FORBIDDEN_REQUEST状态if (!(m_file_stat.st_modeS_IROTH))return FORBIDDEN_REQUEST;// 判断文件类型目录 则返回 BAD_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; } process_write 根据 do_request() 的返回状态服务器子线程调用 process_wirte() 向 m_write_buf  写入响应报文 add_status_line() -- 添加状态行http/1.1  状态码  状态消息add_headers() -- 添加消息报头内部调用 add_content_length() 和 add_linger() 函数 content_length -- 响应报文长度用于 浏览器 判断 服务器 是否发送完数据connection -- 连接状态用于告诉 浏览器 保持长连接add_blank_line() -- 添加空行 上面涉及的 5 个函数内部均调用 add_response() 更新 m_write_idx 指针 和 缓冲区 m_write_buf 的内容 bool http_conn::add_response(const char* format, ...) {// 写入内容超出 m_write_buf 大小就报错if (m_write_idx WRITE_BUFFER_SIZE)return false;// 定义可变参数列表va_list arg_list;// 变量 arg_list 初始化为传入参数va_start(arg_list, format);// 数据 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_dix)) {va_end(arg_list);return false;}// 更新 m_write_idx 位置m_write_idx len;// 清空可变参数列表va_end(arg_list);return true; }//添加 状态行 bool http::connadd_status_line(int status, const char* title) {return add_address(%s %d %s\r\n, HTTP/1.1, status, title);}// 添加 消息报头具体的添加 文本长度连接状态空行 bool http_conn::add_headers(int content_len) {add_content_length(content_len);add_linger();add_blank_line(); }// 添加 Content-Length响应报文长度 bool http_conn::add_content_length(int content_len) {return add_response(Content-Length:%d\r\n, content_len); }// 添加 文本类型这里是 html 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_lingertrue)?keep-alive:close); }// 添加空行 bool http_conn::add_blank_line() {return add_response(%s, \r\n); }// 添加 文本 content bool http_conn::add_content(const char* content) {return add_response(%s, content); } 响应报文分 2 种 一种是 请求文件存在通过 io 向量机制 iovec 声明两个 iovec第一个指向 m_write_buf第二个指向 mmap 的地址 m_file_address 另一种是 请求出错此时只申请一个 iovec指向 m_write_buf  iovec 是一个结构体里面有 2 个元素指针成员 iov_base 指向一个缓冲区这个缓冲区存放 writev 要发送的数据成员 iov_len 表示 实际写入的长度 bool http_conn::process_write(HTTP_CODE ret) {switch(ret){// 内部错误 500case INTERNAL_ERROR:{// 状态行add_status_line(500, error_500_title);// 消息报头add_headers(strlen(error_500_form));if (!add_content(error_500_form))return falsebreak;}// 报文语法有误404case BAD_REQUEST:{add_status_line(404, error_404_tile);add_headers(strlen(error_404_form));if (!add_content(error_404_form))return false;break;}// 资源没有访问权限403case FORBIDDEN_REQUEST:{add_status_line(403, error_403_title);add_headers(strlen(error_403_form));if(!add_content(error_403_form))return false;break;}// 文件存在200case FILE_REQUEST:{add_status_line(200, ok_200_title);// 如果请求的资源存在if (m_file_stat.st_size ! 0){add_headers(m_file_stat.st_size);// 第一个iovec指针指向响应报文缓冲区长度指向m_write_dixm_iv[0].iov_base m_write_buf;m_iv[0].iov_len m_write_idx;// 第二个iovec指针指向mmap返回的文件指针长度指向文件大小m_iv[0].iov_base m_write_buf;m_iv[0].iov_len m_file_stat.st_size;m_iv_count 2;// 发送的全部数据为响应报文头部信息和文件大小bytes_to_send m_write_idx m_file_stat.st_size;return true;}else {// 如果请求的资源大小为 0返回空白 html 文件const char* ok_string htmlbody/body/html;add_headers(strlen(ok_string));if (!add_content(ok_string))return false;}}default:return false;}// 除 FILE_REQUEST 状态外其余状态只有申请一个 iovec// 指向响应报文缓冲区m_iv[0].iov_base m_write_buf;m_iv[0].iov_len m_write_idx;m_iv_count 1;return true; } http_conn::write 服务器子线程调用 process_write() 完成 响应报文随后注册 epollout 事件 服务器主线程监测 写事件并调用 http_conn::write() 函数将响应报文发送给浏览器 具体逻辑 生成响应报文时初始化 byte_to_send包括 头部信息 和 文件数据大小 通过 writev() 函数循环发送响应报文数据根据返回值更新 byte_have_send 和 iovec 结构体指针和长度并判断响应报文整体是否发送成功 若 writev() 单次发送成功更新 byte_to_send 和 byte_have_send 大小 若响应报文整体发送成功则取消 mmap 映射并判断 是否长连接 长连接 -- 重置 http 类实例注册读事件不关闭连接短链接 -- 直接关闭连接 若 writev() 单次发送不成功判断 是否 缓冲区满了 若不是因为缓冲区满了失败取消 mmap 映射关闭连接若 eagain 则缓冲区满了更新 iovec 结构体的指针和长度并注册写事件等待下一次写事件触发 当写缓冲区从不可写变为可写触发 epollout 在这期间无法立即接收同一用户的下一请求但可以保证连接的完整性 bool http_conn::wirte() {int temp 0;int newadd 0;// 若要发送的数据长度为 0// 表示响应报文为空一般不会出现该情况if (bytes_to_send 0){modfd(m_epollfd, m_sockfd, EPOLLIN);init();return true;}while (1){// 将响应报文的状态行消息头空行响应正文// 发送给浏览器temp writev(m_sockfd, m_iv, m_iv_count);// 正常发送temp 为发送的字节数if (temp 0) {// 更新已发送字节bytes_have_send temp;// 偏移文件 iovec 的指针newadd bytes_have_send - m_write_idx;}if (temp -1) {// 判断缓冲区是否满了if (errno EAGAIN) {// 第一个iovec头部信息的数据已发送完发送第二个iovecif (bytes_have_send m_iv[0].iov_len) {// 不再继续发送头部信息m_iv[0].iov_len 0;m_iv[1].iov_base m_file_address newadd;m_iv[1].iov_len bytes_to_send;}// 继续发送第一个iovec头部信息的数据else {m_iv[0].iov_base m_write_buf bytes_to_send;m_iv[0].iov_len m_iv[0].iov_len - bytes_have_send;}// 重新注册写事件modfd(m_epollfd, m_sockfd, EPOLLOUT);return true;}// 发送失败但不是缓冲区问题取消映射unmap();return false;}// 更新已发送 字节数bytes_to_send - temp;// 判断条件数据已全部发送完if (bytes_to_send 0) {ummap();// 在 epoll 树上重置 EPOLLONESHOT 事件modfd(m_epollfd, m_sockfd, EPOLLIN);// 浏览器的请求为 长连接if (m_linger) {// 重新初始化 HTTP 对象init();return true;}elsereturn false;}} } 《Linux高性能服务器》中 http_conn::write() 函数不够严谨这里对其中的 BUG 进行了修复 -- 可以正常传输大文件
http://www.hkea.cn/news/14291289/

相关文章:

  • 做个医院网站多少钱thinkphp购物网站开发视频
  • vue网页模板免费潍坊网站建设优化推广
  • 网站空间流量不够ai可以用来做网站吗
  • 江门专业制作网站制作响应式网站
  • 建设仿优酷视频网站选择做印象绍兴网站的原因
  • 网站的可视化设计广西网站建设哪家不错
  • 成都市住房和城乡建设局网站企业邮箱有哪几种
  • 西安家电商城网站建设三明企业网站建设
  • 永丰县城乡建设局网站最佳网站设计
  • 做蛋糕网站排名优化是什么
  • 与客户沟通网站建设的技巧东莞网站设计如何
  • 做网站一般的尺寸网站群建设指导意见
  • 蛇口网站建设公司医疗器械网站素材
  • 电子商务网站建设与管理第二版短视频推广策划方案
  • 网站建设中图片尺寸网站是先备案还是先做网站
  • 淄博周村网站建设方案西安网站建设云阔
  • 商务网站的规划流程企业网app下载
  • 网站建设 优化班级网站主页设计模板
  • 专业的网站设计建设800元建网站
  • 做一个网站小程序开发公司制作
  • 大连网站建设新图闻山东省建设教育集团网站首页
  • 程序员做游戏还是做网站好0453牡丹江信息网二手房买卖
  • 赤城县城乡建设局网站国内做分销比较好的平台
  • 桥 网站建设茶百道加盟费大概要多少
  • 如何做好品牌网站建设方案福州建设工程协会网站查询系统
  • 新网站怎样做优化.ent做的网站有哪些
  • 中国河北网站网站开发 私活
  • 昆山公司做网站wordpress 什么值得买 我要爆料
  • 建设工程消防网站进入程序优质的网站自助建站
  • php企业公司网站源码上海短视频推广