做网站一定需要icp么,wordpress怎么设置只显示摘要,如何在网站上做抽奖系统,网络服务公共平台基础知识#xff1a;epoll、http报文格式、状态码和有限状态机 代码#xff1a;对服务端处理http请求的全部流程进行简要介绍#xff0c;然后结合代码对http类及请求接收进行详细分析。
epoll
epoll_create函数
#include sys/epoll.h
int epoll_create(int size)…基础知识epoll、http报文格式、状态码和有限状态机 代码对服务端处理http请求的全部流程进行简要介绍然后结合代码对http类及请求接收进行详细分析。
epoll
epoll_create函数
#include sys/epoll.h
int epoll_create(int size)创建一个指示epoll内核事件表的文件描述符该描述符将用作其他epoll系统调用的第一个参数size不起作用。
epoll_ctl函数
#include sys.epoll.h
int epoll_ctl(int epfd,int op,struct epoll_event *event)该函数用于操作内核事件表监控的文件描述符上的事件注册、修改、删除
epfd为epoll_create的句柄op表示动作用3个宏来表示 EPOLL_CTL_ADD注册新的fd到epfd EPOLL_CTL_MOD修改已经注册的fd的监听事件 EPOLL_CTL_DEL从epfd删除一个fdevent告诉内核需要监听的事件 上述event是epoll_event结构体指针类型表示内核所监听的事件具体定义如下
struct epoll_event{
__unit32_t events;
epoll_data_t data;
};events描述事件类型其中epoll事件类型有以下几种 EPOLLIN表示对应的文件描述符可读包括对端SOCKET正常关闭 EPOLLOUT表示对应的文件描述符可以写 EPOLLPRI表示对应的文件描述符有紧急的数据可读这里应该表示有带外数据到来 EPOLLERR表示对应的文件描述符发生错误 EPOLLHUP表示对应的文件描述符被挂断 EPOLLET将EPOLL设为边缘触发Edge Triggered模式这是相对于水平触发Level Triggered而言的 EPOLLONESHOT只监听一次事件当监听完这次事件之后如果还需要继续监听这个socket的话需要再次把这个socket加入到EPOLL队列里
epoll_wait函数
#include sys/epoll.h
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout)该函数用于等待所监控文件描述符上有事件的产生返回就绪的文件描述符个数
events:用来存内核得到事件的集合maxevents告之内核整个events有多大这个maxevents的值大于创建epoll_create()时的sizetimeout超时时间 -1:阻塞 0:立即返回非阻塞 大于0:指定毫秒返回值成功返回有多少文件描述符就绪事件到时返回0出错返回-1
select/poll/epoll
调用函数 select和poll都是一个函数epoll是一组函数文件描述符数量 select通过线性表描述文件描述符集合文件描述符有上限一般是1024但可以修改源码重新编译内核不推荐 poll是链表描述突破了文件描述符上限最大可以打开文件的数目 epoll通过红黑树描述最大可以打开文件的数目可以通过命令ulimit -n number修改仅对当前终端有效将文件描述符从用户传给内核 select和poll通过将所有文件描述符拷贝到内核态每次调用都需要拷贝 epoll通过epoll_create建立一棵红黑树通过epoll_ctl将要监听的文件描述符注册到红黑树上内核判断就绪的文件描述符 select和poll通过遍历文件描述符集合判断哪个文件描述符上有事件发生 epoll_create时内核除了帮我们在epoll文件系统里建了个红黑树用于存储以后epoll_ctl传来的fd外还会再建立一个list链表用于存储准备就绪的事件当epoll_wait调用时仅仅观察这个list链表里有没有数据即可 epoll是根据每个fd上面的回调函数中断函数判断只有发生了事件的socket才会主动的去调用callback函数其他空闲状态socket则不会若是就绪事件插入list应用程序索引就绪文件描述符 select/poll只返回了事件的文件描述符的个数若知道是哪个事件发生了事件同样需要遍历 epoll返回的发生了事件的个数和结构体数组结构体包含socket的信息因此直接处理返回的数组即可工作模式 select和poll都只能工作在相对低效的LT模式下 epoll则可以工作在ET高效模式并且epoll还支持EPOLLONESHOT事件该事件能进一步减少可读、可写和异常事件被触发的次数。应用场景 当所有的fd都是活跃连接使用epoll需要建立文件系统红黑书和链表对于此来说效率反而不高不如select和poll 当监测的fd数目较小且各个fd都比较活跃建议使用select或者poll 当监测的fd数目非常大成千上万且单位时间只有其中一部分fd处于就绪状态这个时候使用epoll能够明显提升性能。
ET、LT、EPOLLONESHOT
LT水平触发模式 epoll_wait检测到文件描述符有事件发生则将通知给应用程序应用程序可以不立即处理该事件当下一次调用epoll_wait时epoll_wait还会再次向应用程序报告此事件直至被处理。ET边缘触发模式 epoll_wait检测到文件描述符有事件发生则将其通知给应用程序应用程序必须立即处理该事件必须要一次性将数据读取完使用非阻塞I/O读取到出现eagainEPOLLONESHOT 一个线程读取某个socket上的数据后开始处理数据在处理过程中该socket上又有新数据可读此时另一个线程被唤醒读取此时出现两个线程处理同一个socket 我们期望的是一个socket连接在任一时刻都只被一个线程处理通过epoll_ctl对该文件描述符注册epolloneshot事件一个线程处理socket时其他线程无法处理当线程处理完后需要通过epoll_ctl重置epolloneshot事件
HTTP报文格式
HTTP报文分为请求报文和响应报文两种每种报文必须按照特有格式生成才能浏览器端识别。其中浏览器端向服务器发送的为请求报文服务器处理后返回给浏览器端的为响应报文。
请求报文
HTTP请求报文由请求行、请求头部、空行和请求数据四个部分组成。 请求分为GET和POST两种。
GET POST
1 POST / HTTP1.1
2 Host:www.wrox.com
3 User-Agent:Mozilla/4.0 (compatible;MSIE 6.0; Windowa NT 5.1;SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
4 Content-Type:application/x-www-form-urlencoded
5 Content-Length:40
6 Connection:Keep-Alive
7 空行
8 nameProfessional%20AjaxpublisherWiley请求行说明请求类型要访问的资源以及所使用的HTTP版本
GET说明请求类型为GET/562f25980001b1b106000338.jpg(URL)为要访问的资源该行的最后一部分说明使用的是HTTP1.1版本。
请求头部紧接着请求行第一行之后的部分用来说明服务器要使用的附加信息。
HOST给出请求资源所在服务器的域名 User-Agent,HTTP客户端程序的信息该信息由你发出请求使用的浏览器来定义并且在每个请求中自动发送等。 Accept说明用户代理可处理的媒体类型。 Accept-Encoding说明用户代理支持的内容编码。 Accept-Language说明用户代理能够处理的自然语言集 Content-Type说明实现主体的媒体类型 Content-Length说明实现主体的大小 Connection连接管理可以是Keep-Alive或close
空行请求头部后面的空行是必须的即使第四部分请求数据为空也必须有空行请求数据也叫主体可以添加任意的其他数据
响应报文
HTTP响应也由四个部分组成分别是状态行、消息报头、空行和响应正文
状态行由HTTP协议版本号状态码状态消息 三部分组成
第一行为状态行HTTP/1.1表明HTTP版本为1.1版本状态码为200状态消息为OK
消息报头用来说明客户端要使用的一些附加信息
第二行和第三行为消息报头Date生成响应的日期和时间Content-Type指定了MME类型的HTMLtext/html编码类型是UTF-8
空行消息报头后面的空行是必须的响应正文服务器返回给客户端的文本信息。空行后面的html部分为响应正文。
HTTP状态码
1xx指示信息-表示请求已接收继续处理。2xx成功-表示请求正常处理完毕 200 OK客户端请求被正常处理 206 Partial content客户端进行了范围请求3xx重定向-要完成请求必须进行更进一步的操作 301 Moved Permanently永久重定向该资源已被永久移动到新位置将来任何对该资源的访问都要使用本响应返回的若干个URL之一。 302 Found:临时重定向请求的资源现在临时从不同的URI中获得4xx客户端错误-请求有语法错误服务器无法处理请求 400 Bad Request请求报文存在语法错误 403 Forbidden请求被服务器拒绝 404 Not Found请求不存在服务器上找不到请求的资源5xx服务器端错误-服务器处理请求出错 500 Internal Server Error服务器在执行请求时出现错误
有限状态机
有限状态机是一种抽象的理论模型它能够把有限个变量描述的状态变化过程以可构造可验证的方式呈现出来。比如封闭的有向图。 有限状态机可以通过if-elseswitch-case和函数指针来实现从软件工程的角度看主要是为了封装逻辑。 带有状态转移的有限状态机示例代码。
STATE_MACHINE(){State cur_Statetype_A;while(cur_State!type_C){Package _packgetNewPackage();switch(){case type_A:process_pkg_state_A(_pack);cur_Statetype_B;break;case type_B:process_pkg_state_B(_pack);cur_Statetype_C;break;}}
}该状态机包含三种状态type_Atype_B和type_C。其中type_A是初始状态type_C是结束状态。 状态机的当前状态记录在cur_State变量中逻辑处理时状态机先通过getNewPackage获取数据包然后根据当前状态对数据进行处理处理完后状态机通过改变cur_State完成状态转移。 有限状态机一种逻辑单元内部的一种高效编程方法在服务器编程中服务器可以根据不同状态或者消息类型进行相应的处理逻辑使得程序逻辑清晰易懂。
http处理流程
首先对http报文处理的流程进行简要介绍然后具体介绍http类的定义和服务器接收http请求的具体过程。
http报文处理流程
浏览器发出http连接请求主线程创建http对象接收请求并将所有数据读入对应buffer将改对象插入任务队列工作线程从任务队列中取出一个任务进行处理。工作线程取出任务后调用process_read函数通过主、从状态机请求报文进行解析。解析完之后跳转do_request函数生成响应报文通过process_write写入buffer返回给浏览器端。
http类
http_conn.h中主要是http类的定义。
class http_conn{public://设置读取文件的名称m_real_file大小static const int FILENAME_LEN200;//设置读缓冲区m_read_buf大小static const int READ_BUFFER_SIZE2048;//设置写缓冲区m_write_buf大小static const int WRITE_BUFFER_SIZE1024;//报文的请求方法本项目只用到GET和POSTenum METHOD{GET0,POST,HEAD,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATH};//主状态机的状态enum CHECK_STATE{CHEACK_STATE_QEQUESTLINE0,CHECK_STATE_HEADER,CHECK_STATE_CONTENT};//报文解析的结果enum HTTP_CODE{NO_REQUEST,GET_REQUEST,BAD_REQUEST,NO_RESOURCE,FORBIDDEN_REQUEST,FILE_REQUEST,INTERNAL_ERROR,CLOSED_CONNECTION};//从状态机的状态enum LINE_STATUS{LINE_OK0,LINE_BAD,LINE_OPEN};public:http_conn(){}~http_conn(){}public://初始化套接字地址函数内部会调用私有方法initvoid init(int sockfd,const sockaddr_in addr);//关闭http连接void close_conn(bool real_closetrue);void process();//读取浏览器端发来的全部数据bool read_once();//响应报文写入函数bool write();sockaddr_in *get_address(){return m_address;}//同步线程初始化数据库读取表void initmysql_result();//CGI使用线程池初始化数据库表void initresultFile(connection_pool *connPool);private:void init();//从m_read_buf读取并处理请求报文HTTP_CODE process_read();//向m_write_buf写入响应报文数据bool process_write(HTTP_CODE ret);//主状态机解析报文中的请求行数据HTTP_CODE parse_request_line(char* text);//主状态机解析报文中的请求头数据HTTP_CODE parse_headers(char* text);//主状态机解析报文中的请求内容HTTP_CODE parse_content(char* text);//生成响应报文HTTP_CODE do_request();//m_start_line是已经解析的字符//get_line用于将指针向后偏移指向未处理的字符char* get_line(){return m_read_bufm_start_line;};//从状态机读取一行分析是请求报文的哪一部分LINE_STATUS parse_line();void unmap();//根据响应报文格式生成对应8个部分以下函数均由do_request调用bool add_reponse(const char* format,...);bool add_content(const char* content);bool add_status_line(int status,const char* title);bool add_headers(int content_length);bool add_content_type();bool add_content_length(int content_length);bool add_linger();bool add_blank_line();public:static int m_epollfd;static int m_user_count;MYSQL *mysql;private:int m_sockfd;sockaddr_in m_address;//存储读取的请求报文数据char m_read_buf[READ_BUFFER_SIZE];//缓冲区中m_read_buf中数据的最后一个字节的下一个位置int m_read_idx;//m_read_buf读取的位置m_checked_idxint m_checked_idx;//m_read_buf中已经解析的字符个数int m_start_line;//存储发出的响应报文数据char m_write_buf[WRITE_BUFFER_SIZE];//指示buffer中的长度int m_write_idx;//主状态机的状态CHECK_STATE m_check_state;//请求方法METHOD m_method;//以下为解析请求报文中对应的6个变量//存储读取文件的名称char m_real_file[FILENAME_LEN];char *m_url;char *m_version;char *m_host;int m_content_length;bool m_linger;char *m_file_address;//读取服务器上的文件地址struct stat m_file_stat;struct iovec m_iv[2];//io向量机制iovecint m_iv_count;int cgi;//是否启用的POSTchar *m_string;//存储请求头数据int bytes_to_send;//剩余发送字节数int bytes_have_send;//已发送字节数
};
在http请求接收部分会涉及到init和read_once函数但init仅仅是对私有成员变量进行初始化不用过多讲解。 read_once读取浏览器端发送来的请求报文直到无数据可读或对方关闭连接读取到m_read_buffer中并更新m_read_idx。
//循环读取客户数据直到无数据可读或对方关闭连接
bool http_conn::read_once()
{if(m_read_idxREAD_BUFFER_SIZE){return false;}int bytes_read0;while(true){//从套接字接收数据存储在m_read_buf缓冲区bytes_readrecv(m_sockfd,m_read_bufm_readidx,READ_BUFFER_SIZE-m_read_idx,0);if(bytes_read-1){//非阻塞ET模式下需要一次性将数据读完if(errnoEAGAIN||errnoEWOULDBLOCK){break;return false;}}else if(bytes_read0){return false;}//修改m_read_idx的读取字节数1m_read_idxbytes_read;}return true;
}epoll相关代码
项目中epoll相关代码部分包括非阻塞模式、内核事件表注册事件、删除事件、重置EPOLLONESHOT事件四种。
非阻塞模式
//对文件描述符设置非阻塞模式
int setnoblocking(int fd){int old_optionfcntl(fd,F_GETFL);//获取文件fd的当前状态int new_optionold_option|O_NONBLOCK;fcntl(fd,F_SETFL,new_option);return old_option;
}内核事件表注册新事件开启EPOLLONESHOT针对客户端连接的描述符listenfd不用开启
void addfd(int epollfd,int fd,bool one_shot)
{epoll_event event;event.data.fdfd;#ifdef ETevent.eventsEPOLLIN|EPOLLET|EPOLLRDHUP;
#endif#ifdef LTevent.eventsEPOLLIN|EPOLLRDHUP;
#endifif(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){epoll_event event;event.data.fdfd;#ifdef ETevent.eventsev|EPOLLET|EPOLLONESHOT|EPOLLRDHUP;
#endif#ifdef LTevent.eventsev|EPOLLONESHOT|EPOLLRDHUP;
#endifepoll_ctl(epollfd,EPOLL_CTL_MOD,fd,event);
}服务器接收http请求
浏览器端发出http连接请求主线程创建http对象接收请求并所有数据读入对应buffer将该对象插入任务队列工作线程从任务队列取出一个任务进行处理。
//创建MAX_FD个http类对象
http_conn* usernew http_conn[MAX_FD];//创建内核事件表
epoll_event events[MAX_EVENT_NUMBER];
epollfdepoll_create(5);
assert(epollfd!-1);//将listenfd放在epoll数上
addfd(epollfd,listenfd,false);//将上述epollfd赋值给http类对象的m_epollfd属性
http_conn::m_epollfdepollfd;while(!stop_server){//等待所监控文件描述符上有事件的产生int numberepoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);if(number0errno!EINTR){break;}//对所有就绪事件进行处理for(int i0;inumber;i){int sockfdevents[i].data.fd;//处理新到的客户连接if(sockfdlistenfd){struct sockaddr_in client_address;socklen_t client_addrlengthsizeof(client_address);
//LT水平触发
#ifdef LTint connfdaccept(listenfd,(struct sockaddr*)client_address,client_addrlength);if(connfd0){continue;}if(http_conn::m_user_countMAX_FD){show_error(connfd,Internal server busy);continue;}users[connfd].init(connfd,client_address);
#endif//ET非阻塞边缘触发
#ifdef ET//需要循环接收数据while(1){int connfdaccept(listenfd,(struct sockaddr *)client_address,client_addrlength);if(connfd0){break;}if(http_conn::m_user_countMAX_FD){show_error(connfd,Internal server busy);break;}users[connfd].init(connfd,client_address);}continue;
#endif}//处理异常事件else if(events[i].events(EPOLLRDRDHUP|EPOLLHUP|EPOLLERR)){//服务器端关闭连接}//处理信号else if((sockfdpipefd[0])(events[i].eventsEPOLLIN)){}//处理客户连接上接收到的数据else if(events[i].eventsEPOLLIN){//读入对应缓冲区if(users[sockfd].read_once()){//若监测到读事件将该事件放入请求队列pool-append(userssockfd);}else{//服务器关闭连接}}}
}