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

网站建设技术公司排名淮南建设公司网站

网站建设技术公司排名,淮南建设公司网站,网页制作范例,义乌做网站的公司文章目录 一、基础知识1. epoll2. 再谈 I/O 复用3. 触发模式和 EPOLLONESHOT4. HTTP 报文5. HTTP 状态码6. 有限状态机7. 主从状态机8. HTTP_CODE9. HTTP 处理流程 二、代码解析1. HTTP 类2. 读取客户数据2. epoll 事件相关3. 接收 HTTP 请求4. HTTP 报文解析5. HTTP 请求响应 … 文章目录 一、基础知识1. epoll2. 再谈 I/O 复用3. 触发模式和 EPOLLONESHOT4. HTTP 报文5. HTTP 状态码6. 有限状态机7. 主从状态机8. HTTP_CODE9. HTTP 处理流程 二、代码解析1. HTTP 类2. 读取客户数据2. epoll 事件相关3. 接收 HTTP 请求4. HTTP 报文解析5. HTTP 请求响应 参考文献 一、基础知识 1. epoll 创建内核事件表int epoll_create(int size); size 不起作用只是给内核一个提示告诉它事件表需要多大返回值内核事件表的文件描述符 修改内核事件表监控的文件描述符上的事件int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); epfd 内核事件表的文件描述符op 表示三种操作注册EPOLL_CTL_ADD、修改EPOLL_CTL_MOD、删除EPOLL_CTL_DELevent 需要监听的事件 标识符事件类型EPOLLIN可读、对端 socket 关闭EPOLLOUT可写EPOLLPRI带外数据EPOLLERR错误EPOLLHUP文件描述符被挂断EPOLLET边缘触发模式EPOLLONESHOT只监听一次事件每次见听完如需再次监听需重置 返回值成功时返回 0 失败时返回 -1 并设置 errno 。 监听事件发生int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); events 存储内核中发生的事件maxevents events 的容量timeout 超时时间-1 表示阻塞0 表示立即返回非阻塞大于 0 表示毫秒返回值就绪的文件描述符个数超时时返回 0 出错时返回 -1 。 2. 再谈 I/O 复用 select 使用线性表描述文件描述符集合存在上限每次调用需要将所有文件描述符拷贝到内核态需要遍历判断就绪事件适用于少量活跃的 fdpoll 使用链表描述文件描述符集合不存在上限每次调用需要将所有文件描述符拷贝到内核态需要遍历判断就绪事件适用于少量活跃的 fdepoll 使用红黑树描述文件描述符集合存在上限通过 epoll_ctl 将要监听的文件描述符注册到红黑树上会将就绪事件存放在新建的链表中适用于大量不活跃的 fd 3. 触发模式和 EPOLLONESHOT LT 水平触发模式当检测到就绪事件时将其通知给应用程序应用程序可以不立即处理该事件等到下次调用 epoll_wait 时会再次报告该事件ET 边缘触发模式当检测到就绪事件时将其通知给应用程序应用程序必须立即处理并且需要一次性处理完EPOLLONESHOT 当某个 socket 的数据分两次到达时系统可能会唤醒两个不同的线程来进行处理若开启 EPOLLONESHOT 则对于某个 socket 来说只会有一个线程处理其事件其他线程不能插手完成一次处理后需要重置 EPOLLONESHOT 。 4. HTTP 报文 HTTP 报文分为请求报文和响应报文前者由浏览器发送给服务器后者由服务器应答浏览器请求报文又分为 GET 和 POST 两种 GET /562f25980001b1b106000338.jpg HTTP/1.1 Host:img.mukewang.com User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36 Accept:image/webp,image/*,*/*;q0.8 Referer:http://www.imooc.com/ Accept-Encoding:gzip, deflate, sdch Accept-Language:zh-CN,zh;q0.8 空行 请求数据为空第 1 行请求行用来说明请求类型、要访问的资源、所使用的 HTTP 版本第 2 - 8 行请求头部通常包含如下信息 Host 服务器所在的域名User-Agent HTTP 客户端程序的信息由浏览器定义并自动发送Accept 说明用户代理可处理的媒体类型Accept-Encoding 说明用户代理支持的内容编码Accept-Language 说明用户代理能够处理的自然语言集Content-Type 说明实现主体的媒体类型Content-Length说明实现主题的大小Connection 连接管理可以是 Keep-Alive 或 close 第 9 行空行第 10 行请求数据也叫主体可以添加任意其他数据POST / HTTP1.1 Host:www.wrox.com User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022) Content-Type:application/x-www-form-urlencoded Content-Length:40 Connection: Keep-Alive 空行 nameProfessional%20AjaxpublisherWileyGET 的请求数据通常为空POST 则包含要请求的信息。 响应报文主要有四个部分组成状态行、消息报头、空行、响应正文 HTTP/1.1 200 OK Date: Fri, 22 May 2009 06:07:21 GMT Content-Type: text/html; charsetUTF-8 空行 htmlhead/headbody!--body goes here--/body /html第 1 行状态行由 HTTP 协议版本号、状态码、状态消息组成第 2 - 3 行消息报头用来说明客户端需要使用的附加信息 Date 生成响应的日期和时间Content-Type 指定了 MIME 类型的 HTML 编码类型是 UTF-8 第 4 行空行第 5 - 10 行响应正文为 HTML 语言。 5. HTTP 状态码 1xx 指示信息表示请求已接收继续处理2xx 成功表示请求正常处理完毕 200 OK 客户端请求被正常处理206 Partial Content 客户端进行了范围请求 3xx 重定向要完成请求需要进一步操作 301 Moved Permanently 永久重定向返回新的 URL 302 Found 临时重定向返回临时的 URL 4xx 客户端错误 400 Bad Request 语法错误403 Forbidden 请求被服务器拒绝404 Not Found 请求不存在服务器上找不到请求的资源 5xx 服务器端错误 500 Internal Server Error 服务器执行时出错。 6. 有限状态机 有限状态机是一种抽象的理论模型使用选择语句来实现。模型要求代码存在 n 个状态使用当前状态 cur_state 来进行标记每次处理完任务后都对其进行更改以实现状态跳转代码如下 STATE_MACHINE(){State cur_State type_A;while(cur_State ! type_C){Package _pack getNewPackage();switch() {case type_A:process_pkg_state_A(_pack);cur_State type_B;break;case type_B:process_pkg_state_B(_pack);cur_State type_C;break;}} }7. 主从状态机 主从状态机也是一种抽象的理论模型在本项目中从状态机负责读取 HTTP 请求的一行主状态机负责对该行数据进行解析主状态机内部调用从状态机从状态机驱动主状态机流程图如下 流程图 [2] 从模型的角度来看从状态机负责通用的操作处理主状态机负责特定的操作处理主状态机需要使用从状态机提供的数据从状态机需要被主状态机调用每个状态机又是一个有限状态机。本项目中主从状态机各有三种状态 主状态机标识解析位置 CHECK_STATE_REQUESTLINE 解析请求行CHECK_STATE_HEADER 解析请求头CHECK_STATE_CONTENT 解析消息体仅用于解析 POST 请求 从状态机标识解析一行的读取状态 LINE_OK 完整读取一行LINE_BAD 报文语法错误LINE_OPEN 读取的行不完整。 8. HTTP_CODE 标识了 HTTP 请求的处理结果 NO_REQUEST 请求不完整需要继续读取请求报文跳转主程序继续检测可读事件GET_REQUEST 获得了完整的请求调用 do_request 完成请求资源映射NO_RESOURCE 请求资源不存在跳转 process_write 完成响应报文BAD_REQUEST 语法错误或请求资源为目录跳转 process_write 完成响应报文FORBIDDEN_REQUEST 请求资源禁止访问跳转 process_write 完成响应报文FILE_REQUEST 请求资源可以正常访问跳转 process_write 完成响应报文INTERNAL_ERROR 服务器内部错误。 9. HTTP 处理流程 流程图 [3] 浏览器发出 HTTP 连接请求主线程创建 HTTP 对象接收请求并将所有数据读入对应的缓存区主线程将 HTTP 对象插入任务队列工作线程从任务队列中取出一个任务工作线程调用 process_read 函数通过主从状态机解析请求报文解析完成后跳转 do_request 函数生成响应报文通过 process_write 写入缓存区发送数据给浏览器。 二、代码解析 1. HTTP 类 // http连接类 class http_conn { public:static const int FILENAME_LEN 200; // 文件名最大长度static const int READ_BUFFER_SIZE 2048; // 读缓存区大小static const int WRITE_BUFFER_SIZE 1024; // 写缓存区大小// http连接的方法enum METHOD{GET 0, // 申请获得资源POST, // 向服务器提交数据并修改HEAD, // 仅获取头部信息PUT, // 上传某个资源DELETE, // 删除某个资源TRACE, // 要求服务器返回原始HTTP请求的内容可用来查看服务器对HTTP请求的影响OPTIONS, // 查看服务器对某个特定URL都支持哪些请求方法。也可把URL设置为* 从而获得服务器支持的所有请求方法CONNECT, // 用于某些代理服务器能把请求的连接转化为一个安全隧道PATH // 对某个资源做部分修改};// 主状态机状态enum CHECK_STATE{CHECK_STATE_REQUESTLINE 0, // 检查请求行CHECK_STATE_HEADER, // 检查头部状态CHECK_STATE_CONTENT // 检查内容};// HTTP状态码enum HTTP_CODE{NO_REQUEST, // 请求不完整需要继续读取请求报文数据GET_REQUEST, // 获取了完整请求BAD_REQUEST, // HTTP请求报文有语法错误NO_RESOURCE, // 无资源FORBIDDEN_REQUEST, // 禁止请求FILE_REQUEST, // 文件请求INTERNAL_ERROR, // 服务器内部错误CLOSED_CONNECTION // 关闭连接};// 从状态机状态enum LINE_STATUS{LINE_OK 0, // 读取完成LINE_BAD, // 读取有错误LINE_OPEN // 未读完};public:// 构造函数http_conn() {}// 析构函数~http_conn() {}public:// 初始化套接字地址函数内部会调用私有方法initvoid init(int sockfd, const sockaddr_in addr, char *, int, int, string user, string passwd, string sqlname);// 关闭http连接void close_conn(bool real_close true);// 处理HTTP请求的入口函数void process();// 读取浏览器端发来的全部数据bool read_once();// 响应报文写入函数bool write();// 地址sockaddr_in *get_address(){return m_address;}// 同步线程初始化数据库读取表void initmysql_result(connection_pool *connPool);// 计时器标志int timer_flag;int improv;private:void init(); // 初始化HTTP_CODE process_read(); // 从m_read_buf读取并处理请求报文bool process_write(HTTP_CODE ret); // 向m_write_buf写入响应报文数据HTTP_CODE parse_request_line(char *text); // 主状态机解析http请求行HTTP_CODE parse_headers(char *text); // 主状态机解析http请求头HTTP_CODE parse_content(char *text); // 主状态机判断http请求内容HTTP_CODE do_request(); // 生成响应报文char *get_line() { return m_read_buf m_start_line; }; // 用于将指针向后偏移指向未处理的字符LINE_STATUS parse_line(); // 从状态机分析一行的内容返回状态void unmap(); // 关闭内存映射// 根据响应报文格式生成对应8个部分以下函数均由do_request调用bool add_response(const char *format, ...); // responsebool add_content(const char *content); // contentbool add_status_line(int status, const char *title); // status_linebool add_headers(int content_length); // headersbool add_content_type(); // content_typebool add_content_length(int content_length); // content_lengthbool add_linger(); // lingerbool add_blank_line(); // blank_linepublic:static int m_epollfd; // 最大文件描述符个数static int m_user_count; // 当前用户连接数MYSQL *mysql; // 数据库指针int m_state; // 读为0, 写为1private:int m_sockfd; // 当前fdsockaddr_in m_address; // 当前地址char m_read_buf[READ_BUFFER_SIZE]; // 存储读取的请求报文数据long m_read_idx; // 缓冲区中m_read_buf中数据的最后一个字节的下一个位置long m_checked_idx; // m_read_buf读取的位置m_checked_idxint m_start_line; // m_read_buf中已经解析的字符个数char m_write_buf[WRITE_BUFFER_SIZE]; // 存储发出的响应报文数据int m_write_idx; // 指示buffer中的长度CHECK_STATE m_check_state; // 主状态机状态METHOD m_method; // 请求方法// 以下为解析请求报文中对应的6个变量char m_real_file[FILENAME_LEN]; // 存储读取文件的名称char *m_url; // urlchar *m_version; // versionchar *m_host; // hostlong m_content_length; // content_lengthbool m_linger; // lingerchar *m_file_address; // 读取服务器上的文件地址struct stat m_file_stat; // 文件状态struct iovec m_iv[2]; // io向量机制iovec标识两个缓存区int m_iv_count; // 表示缓存区个数int cgi; // 是否启用的POSTchar *m_string; // 存储请求头数据int bytes_to_send; // 待发送字节个数int bytes_have_send; // 已发送字节个数char *doc_root; // 文件根目录mapstring, string m_users; // 用户名密码对int m_TRIGMode; // 触发模式int m_close_log; // 是否关闭logchar sql_user[100]; // 用户名char sql_passwd[100]; // 用户密码char sql_name[100]; // 数据库名 };2. 读取客户数据 // 循环读取客户数据直到无数据可读或对方关闭连接 // 非阻塞ET工作模式下需要一次性将数据读完 bool http_conn::read_once() {// 超出最大读缓存限制if (m_read_idx READ_BUFFER_SIZE){return false;}// 标志有多少字节int bytes_read 0;// LT读取数据if (0 m_TRIGMode){// 接收数据保存到读缓存区bytes_read recv(m_sockfd, m_read_buf m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);// 修改m_read_idx的读取字节数m_read_idx bytes_read;// 未读到数据if (bytes_read 0){return false;}return true;}// ET读数据else{while (true){// 接收数据保存到读缓存区bytes_read recv(m_sockfd, m_read_buf m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);// 读取异常if (bytes_read -1){// 判断errno是否为重试或未发送完残留数据if (errno EAGAIN || errno EWOULDBLOCK)break;// 不是则出错return false;}// 读取为空else if (bytes_read 0){return false;}// 修改m_read_idx的读取字节数m_read_idx bytes_read;}return true;} }2. epoll 事件相关 主要有四个设置非阻塞模式、注册事件、删除事件、重置 EPOLLONESHOT 事件 // 对文件描述符设置非阻塞 int setnonblocking(int fd) {int old_option fcntl(fd, F_GETFL);int new_option old_option | O_NONBLOCK;fcntl(fd, F_SETFL, new_option);return old_option; }// 将内核事件表注册新事件开启ET模式选择开启EPOLLONESHOT void addfd(int epollfd, int fd, bool one_shot, int TRIGMode) {epoll_event event;event.data.fd fd;// 触发组合模式ET模式if (1 TRIGMode)event.events EPOLLIN | EPOLLET | EPOLLRDHUP;// 默认模式LT监听、连接elseevent.events EPOLLIN | EPOLLRDHUP;// one shot模式保证一个socket只有一个线程操作if (one_shot)event.events | EPOLLONESHOT;epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, event);setnonblocking(fd); }// 从内核事件表删除事件 void removefd(int epollfd, int fd) {epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0);close(fd); }// 将事件重置为EPOLLONESHOT事件 void modfd(int epollfd, int fd, int ev, int TRIGMode) {epoll_event event;event.data.fd fd;if (1 TRIGMode)event.events ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;elseevent.events ev | EPOLLONESHOT | EPOLLRDHUP;epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, event); }3. 接收 HTTP 请求 浏览器发出 HTTP 连接主线程创建 HTTP 对象以接受请求并将所有数据读入对应的缓存区然后将该对象插入工作队列工作线程从工作队列中取出一个任务进行处理 //创建MAX_FD个http类对象 http_conn* usersnew http_conn[MAX_FD];//创建内核事件表 epoll_event events[MAX_EVENT_NUMBER]; epollfd epoll_create(5); assert(epollfd ! -1);//将listenfd放在epoll树上 addfd(epollfd, listenfd, false);//将上述epollfd赋值给http类对象的m_epollfd属性 http_conn::m_epollfd epollfd;while (!stop_server) {//等待所监控文件描述符上有事件的产生int number epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);if (number 0 errno ! EINTR){break;}//对所有就绪事件进行处理for (int i 0; i number; i){int sockfd events[i].data.fd;//处理新到的客户连接if (sockfd listenfd){struct sockaddr_in client_address;socklen_t client_addrlength sizeof(client_address); //LT水平触发 #ifdef LTint connfd accept(listenfd, (struct sockaddr *)client_address, client_addrlength);if (connfd 0){continue;}if (http_conn::m_user_count MAX_FD){show_error(connfd, Internal server busy);continue;}users[connfd].init(connfd, client_address); #endif//ET非阻塞边缘触发 #ifdef ET//需要循环接收数据while (1){int connfd accept(listenfd, (struct sockaddr *)client_address, client_addrlength);if (connfd 0){break;}if (http_conn::m_user_count MAX_FD){show_error(connfd, Internal server busy);break;}users[connfd].init(connfd, client_address);}continue; #endif}//处理异常事件else if (events[i].events (EPOLLRDHUP | EPOLLHUP | EPOLLERR)){//服务器端关闭连接}//处理信号else if ((sockfd pipefd[0]) (events[i].events EPOLLIN)){}//处理客户连接上接收到的数据else if (events[i].events EPOLLIN){//读入对应缓冲区if (users[sockfd].read_once()){//若监测到读事件将该事件放入请求队列pool-append(users sockfd);}else{//服务器关闭连接}}} }4. HTTP 报文解析 HTTP 报文解析流程如下 process_read 函数通过 while 循环将主从状态机进行封装对报文的每一行进行处理 将从状态设为 LINE_OK 作为循环的入口条件首先在循环中会解析请求行然后解析请求头若是 GET 请求则到此为止若是 POST 请求则继续解析消息体同时处理从状态防止再次解析循环的判断条件有特殊含义注释中已给出说明 parse_line 函数从状态机负责解析一行数据 在 HTTP 报文中每一行数据由 \r\n 作为结束据此可以判断行每次找到并处理 \r\n 后将其置为 \0\0 读取中若当前字符为 \r 可能出现三种情况 下一个字符为 \n 将 m_checked_idx 指向下一行的开头返回 LINE_OK 读到了缓存区末尾标识还需要继续接收数据返回 LINE_OPEN 其他标识语法错误返回 LINE_BAD 若当前字节为 \n 则意味着中途没接收完整对应上一种情况的第二条此时判断前一个字符是否为 \r 即可若当前字符不为上述两种情况则接收不完整返回 LINE_OPEN 由于已将 \r\n 改为了 \0\0 因此主状态机可以直接进行字符串处理 get_line 函数用于将指针向后偏移指向未处理的字符即处理一行数据parse_request_line 函数主状态机负责解析 HTTP 请求的请求行 初始状态为 CHECK_STATE_REQUESTLINE 从 m_read_buf 中解析 HTTP 请求行获取请求方法、目标 URL 和 HTTP 版本号将状态改为 CHECK_STATE_HEADER parse_headers 函数主状态机负责解析 HTTP 请求的请求头 由于请求头和空行的处理使用的同一个函数因此需要通过根据当前的 text 首位是不是 \0 来判断是空行还是请求头请求头有多行因此需要根据具体含义进行解析如果是 GET 请求则到此为止如果是 POST 请求则还需解析消息体将状态改为 CHECK_STATE_CONTENT parse_content 函数主状态机负责解析 HTTP 请求的消息体 // 解析响应的入口函数 void http_conn::process() {// 报文解析获取状态码HTTP_CODE read_ret process_read();if (read_ret NO_REQUEST){// 注册并监听读事件modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);return;}// 报文响应是否成功bool write_ret process_write(read_ret);if (!write_ret){close_conn();}// 注册并监听写事件modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode); }// 用于将指针向后偏移指向未处理的字符即处理一行数据 char* http_conn::get_line() { return m_read_buf m_start_line; }; // 解析http请求调用了主状态机、从状态机 http_conn::HTTP_CODE http_conn::process_read() {// 初始化从状态机状态、HTTP请求解析结果LINE_STATUS line_status LINE_OK;HTTP_CODE ret NO_REQUEST;char *text 0;// 循环处理由从状态机驱动// 前一部分判断条件用于处理请求数据消息体因为这部分最后面没有\r\n无法用从状态机判断// 前一部分判断条件主要使用主状态机来判断消息体而因为这部分处理完后状态并没有发生改变因此还需要从状态机来标记只处理一次while ((m_check_state CHECK_STATE_CONTENT line_status LINE_OK) || ((line_status parse_line()) LINE_OK)){// 指向读缓存区目标行的位置text get_line();//m_start_line是每一个数据行在m_read_buf中的起始位置//m_checked_idx表示从状态机在m_read_buf中读取的位置m_start_line m_checked_idx;LOG_INFO(%s, text);// 主状态机三种状态转换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;//完整解析GET请求后跳转到报文响应函数else if (ret GET_REQUEST){return do_request();}break;}// 解析消息体case CHECK_STATE_CONTENT:{ret parse_content(text);//完整解析POST请求后跳转到报文响应函数if (ret GET_REQUEST)return do_request();line_status LINE_OPEN;break;}// 都不是则表示服务器错误default:return INTERNAL_ERROR;}}return NO_REQUEST; }// 从状态机用于分析出一行内容 // 返回值为行的读取状态有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_idx 1] \n){// 标记缓存结束返回LINE_OKm_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;}}// 没读到换行符和回车符说明行未读完return LINE_OPEN; }// 主状态机解析http请求行获得请求方法目标url及http版本号 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;}// 返回坏请求标志elsereturn 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_version, \t);//仅支持HTTP/1.1if (strcasecmp(m_version, HTTP/1.1) ! 0)return BAD_REQUEST;//对请求资源前7个字符进行判断//这里主要是有些报文的请求资源中会带有http://这里需要对这种情况进行单独处理if (strncasecmp(m_url, http://, 7) 0){m_url 7;// 记录网站根目录后面的地址包括/符号m_url strchr(m_url, /);}//同样增加https情况if (strncasecmp(m_url, https://, 8) 0){m_url 8;m_url strchr(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; }// 主状态机解析http请求的一个头部信息 http_conn::HTTP_CODE http_conn::parse_headers(char *text) {// 头部信息为空if (text[0] \0){//判断是GET还是POST请求if (m_content_length ! 0){//POST需要跳转到消息体处理状态m_check_state CHECK_STATE_CONTENT;return NO_REQUEST;}// 状态为获取请求return GET_REQUEST;}//解析请求头部连接字段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 15;text strspn(text, \t);// 设置内容长度m_content_length atol(text);}//解析请求头部HOST字段else if (strncasecmp(text, Host:, 5) 0){text 5;text strspn(text, \t);// 保存hostm_host text;}// 意外情况else{LOG_INFO(oop!unknow header: %s, text);}return NO_REQUEST; }// 主状态机处理content字段判断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; }5. HTTP 请求响应 HTTP 报文响应流程如下 do_request 函数对解析后的请求进行分析并对 URL 进行处理返回请求的状态 / GET 请求跳转 judge.html 即欢迎页面/0 POST 请求跳转 register.html 即注册页面/1 POST 请求跳转 log.html 即登录页面/2 POST 请求进行登录校验成功跳转 welcome.html 即资源请求成功页面失败跳转 logError.html 即登陆失败页面/3 POST 请求进行注册校验跳转同上/5 POST 请求跳转 picture.html 即图片请求页面/6 POST 请求跳转 video.html 即视频请求页面/7 POST 请求跳转 fans.html 即关注页面若资源存在且访问正常就将其映射到内存中准备发送 add_response 函数构造响应报文的公共接口被各类消息报头构造函数调用process_write 函数向 m_write_buf 中写入响应报文响应报文分两种 一种是文件存在通过 io 向量机制 iovec 声明 2 个 iovec 第一个指向 m_write_buf 第二个指向 mmap 的地址 m_file_address 第二种是请求出错只申请一个 iovec 注册 EPOLLOUT 事件服务器主线程监测到事件后调用 write 函数 write 函数将响应报文发送给浏览器端 根据已发送数据大小判断发送是否完成根据 EAGAIN 判断缓冲区是否已满每次发送数据后需要更新已发送字数发送完成后需要重置 HTTP 对象并重置 EPOLLONESHOT 事件。 // 响应http请求检验、分配、响应请求所需的资源 http_conn::HTTP_CODE http_conn::do_request() {//将初始化的m_real_file赋值为网站根目录strcpy(m_real_file, doc_root);// 记录文件路径长度int len strlen(doc_root);// printf(m_url:%s\n, m_url);//找到m_url中/的位置const char *p strrchr(m_url, /);// //实现登录和注册校验cgi1启用post// *(p1):0注册POST、1登录POST、2登录校验POST、3注册校验POST、5picture页面POST、6video页面POST、7fans页面POSTif (cgi 1 (*(p 1) 2 || *(p 1) 3)){// 根据标志判断是登录检测还是注册检测即/符号后的第一位char flag m_url[1];// 申请url空间char *m_url_real (char *)malloc(sizeof(char) * 200);// 存入/strcpy(m_url_real, /);// 存入/后的第二位之后的urlstrcat(m_url_real, m_url 2);// 文件存储区在文件路径之后存入真实urlstrncpy(m_real_file len, m_url_real, FILENAME_LEN - len - 1);// 释放真实url存储空间free(m_url_real);// 将用户名和密码提取出来// user123passwd123char 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, ));// 没找到则insert数据if (users.find(name) users.end()){m_lock.lock();int res mysql_query(mysql, sql_insert);users.insert(pairstring, 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);}}//如果请求资源为/0表示跳转注册界面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);}//如果请求资源为/1表示跳转登录界面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);//通过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_mode S_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; }// 构造响应报文的接口 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_idx)){va_end(arg_list);return false;}// 更新m_write_idx位置m_write_idx len;// 清空可变参列表va_end(arg_list);LOG_INFO(request:%s, m_write_buf);return true; }// 添加状态行的接口 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(); }// 添加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_linger true) ? 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); }// 构造响应报文处理好各缓存区的指针为发送数据做准备 bool http_conn::process_write(HTTP_CODE ret) {// 根据HTTP状态码构造响应头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 false;break;}// 报文语法有误404case BAD_REQUEST:{add_status_line(404, error_404_title);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_idxm_iv[0].iov_base m_write_buf;m_iv[0].iov_len m_write_idx;// 第二个iovec指针指向mmap返回的文件指针长度指向文件大小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;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;bytes_to_send m_write_idx;return true; }// 发送数据即写入文件描述符 bool http_conn::write() {int temp 0;// 若要发送的数据长度为0// 表示响应报文为空一般不会出现这种情况if (bytes_to_send 0){// 将事件重置为EPOLLONESHOTmodfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);// 初始化init();return true;}// 循环处理while (1){// 将响应报文的状态行、消息头、空行和响应正文发送给浏览器端temp writev(m_sockfd, m_iv, m_iv_count);if (temp 0){if (errno EAGAIN){// 重新注册写事件modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);return true;}unmap();return false;}// 正常发送temp为发送的字节数bytes_have_send temp;// 更新已发送字节数bytes_to_send - temp;// 第一个iovec头部信息的数据已发送完发送第二个iovec数据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;}// 继续发送第一个iovec头部信息的数据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();// 在epoll树上重置EPOLLONESHOT事件modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);// 浏览器的请求为长连接if (m_linger){// 重新初始化HTTP对象init();return true;}// 不保持else{return false;}}} }参考文献 [1] 最新版Web服务器项目详解 - 04 http连接处理上 [2] 最新版Web服务器项目详解 - 05 http连接处理中 [3] 最新版Web服务器项目详解 - 06 http连接处理下
http://www.hkea.cn/news/14264551/

相关文章:

  • 织梦做网站也是模板吗手机网站底部代码
  • 两学一做纪实评价系统网站咸鱼之王小程序
  • 湘潭网站建设方案费用丹阳网站建设报价
  • 网站流量突然增大wordpress 首页显示分类
  • 网站设计公司网设计网站价格
  • wordpress主题 手机主题搜索引擎优化简称
  • 网站备案系统龙岗注册公司
  • 兼职网网站建设方案蚌埠做网站公司
  • 广州微信网站建设报价表天津业之峰装修公司地址
  • 网站开发前如何配置电脑关于网站开发的参考文献有哪些
  • 投资者网站建设校园网站建设意见表填写
  • 一元购网站的建设京东商城网上购物官网
  • 网站模WordPress添加在线商店
  • 成都网站优化报价抚州网站网站建设
  • 山西自助建站费用低做网站 语言
  • 网站建设asp文件怎么展现网站 优化 关键字
  • 网上做环评立项的网站是哪个深圳市中心在哪个位置
  • 西宁城西区建设局网站亚成成品网站源码
  • 南宁市网站开发wordpress瘦身
  • 免费制作个人网站专业的单位网站建设
  • 电子商务网站建设书籍怎样自己做网站推广
  • 一级做爰A视频免费网站管理网站开发教程
  • 怎么发布自己做的网站淮北市建筑
  • 山西省网站备案小米开放平台
  • 系统优化软件北京seo服务销售
  • 网站的内容做证据观点开发网站需要问什么
  • 多语言网站建设怎样做网络推广
  • 小企业网站建设哪些好办中企动力的网站如何
  • 不同性质网站那里可以做旅游网站的吗
  • 自助建站和速成网站共享门店新增实时收款