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

重庆手机网站制作优化网站建设seo

重庆手机网站制作,优化网站建设seo,html5 css3网站源码,诸城网站建设哪家好目录 一、select实现多路复用 1.select函数介绍 2.select优缺点 3.select使用示例 二、poll实现多路复用 1.poll函数介绍 2.poll优缺点 3.poll使用示例 三、epoll实现多路复用 1.epoll函数介绍 2.epoll工作原理 3.epoll工作模式 (1)水平触发LT模式 (2)边缘触发ET模…

目录

一、select实现多路复用

1.select函数介绍

2.select优缺点

3.select使用示例

二、poll实现多路复用

1.poll函数介绍

2.poll优缺点

3.poll使用示例

三、epoll实现多路复用

1.epoll函数介绍

2.epoll工作原理

3.epoll工作模式

(1)水平触发LT模式

(2)边缘触发ET模式

(3)LT与ET模式效率对比

4.epoll函数优缺点

5.poll函数使用示例

四、select、poll、select综合对比


一、select实现多路复用

1.select函数介绍

select函数用于在指定时间内,监视多个指定的文件描述符,超时或者没有文件描述符就绪则返回

函数原型:

include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

返回值:成功返回所有集合中就绪文件描述符的总和,失败返回-1并设置错误码。返回值为0表示超时(timeout时间耗尽,没有文件描述符就绪)

int nfds:监视的文件描述符的最大值+1

fd_set *readfds:监视可读事件的文件描述符。输入时包含指定的要监视的可读事件文件描述符,返回时仅包含就绪的文件描述符。

fd_set *writefds:监视可写事件的文件描述符。输入时包含指定的要监视的可写事件文件描述符,返回时仅包含就绪的文件描述符。

fd_set *exceptfds:监视异常事件的文件描述符。输入时包含指定的要监视的异常事件文件描述符,返回时仅包含就绪的文件描述符。

fd_set结构体原型(本质是位图):

typedef struct {unsigned long fds_bits[FD_SET_SIZE];
} fd_set;

fd_set相关操作函数:

void FD_CLR(int fd, fd_set *set); // 用来清除描述词组 set 中相关fd的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组 set 中相关fd的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组 set 中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组 set 的全部位

struct timeval *timeout:nullptr表示永久阻塞,一直等到有文件描述符就绪;{0,0}表示非阻塞等待,立即返回;{n,m}表示阻塞等待n秒+m毫秒,等待时间内有文件描述符就绪时timeout会被修改为剩余等待时间,等待时间结束立即返回。

struct timeval {long tv_sec;  // 秒long tv_usec; // 微秒
};
2.select优缺点

缺点:

  • 每次调用select之前都要手动设置fd_set集合
  • select支持的文件描述符数量太少
  • 每次调用select都需要将用户空间的fd_set集合拷贝到内核空间,开销大,效率低
  • 每次调用select内核都需要遍历拷贝进来的fd_set集合,开销大,效率低
3.select使用示例

使用select函数通常要配合一个合法文件描述符数组,将所有合法的文件描述符先存放在数组中,再让select函数监视这些合法文件描述符,当这些合法文件描述符就绪并处理完毕后,有些合法文件描述符可能要关闭,将它们从数组中移除,再将重新将合法文件描述符从数组中拷贝到fd_set集合中让select函数监视。

#pragma once
#include <iostream>
#include <memory>
#include <sys/socket.h>//提供select函数
#include "Socket.hpp"using namespace socket_ns;
using namespace log_ns;//基于多路复用IO的服务器
class SelectServer
{static const int gNum=sizeof(fd_set)*8;//一个文件描述符集合中可以容纳的文件描述符数量(看fd_set有多少字节,一个字节等于8位)
static const int gDefaultFd=-1;//private:uint16_t _port;//端口号std::shared_ptr<Socket> _listensock;//Socket对象,包含监听套接字int fdArray[gNum];//存放所有合法的文件描述符(用到的文件描述符),文件描述符按出现的顺序存放在数组中,fdArray[i]=-1表示该位置没有文件描述符
public://构造函数SelectServer(uint16_t port):_port(port),_listensock(std::make_shared<TcpSocket>())//父类对象Socket接收子类对象TcpSocket{_listensock->StartListening(_port);//开始监听//初始化合法文件描述符数组for(int i=0;i<gNum;i++){fdArray[i]=gDefaultFd;}fdArray[0]=_listensock->GetSockfd();//监听套接字的文件描述符是第一个合法的文件描述符,要添加到fdArray中}//析构函数~SelectServer(){}
public://处理就绪的文件描述符void HandleEvent(fd_set& rfds){//遍历合法的文件描述符数组,对就绪的文件描述符作出处理(事件派发)for(int i=0;i<gNum;i++){if(fdArray[i]!=gDefaultFd)//筛选出合法的文件描述符{if(FD_ISSET(fdArray[i],&rfds))//筛选出就绪的文件描述符{if(fdArray[i]==_listensock->GetSockfd())//是监听套接字的文件描述符{HandleListenSockfd();}else//是读写套接字的文件描述符{HandleReadWriteSockfd(i);}}}}}//处理就绪的监听套接字文件描述符void HandleListenSockfd(){InetAddr clientAddr;//保存连接到的客户端地址信息int readWriteSockfd=_listensock->Accept(&clientAddr);//服务端接收连接,并获取到读写文件描述符if(readWriteSockfd>0){LOG(INFO,"Get a new link, client ip:%s port:%d\n",clientAddr.Ip().c_str(),clientAddr.Port());bool flag=false;//标志位,看合法文件描述符集合中有没有空位置for(int i=0;i<gNum;i++){if(fdArray[i]==gDefaultFd)//在合法文件描述符集合中找到一个空位置,将新获得的读写文件描述符添加到合法文件描述符集合中{fdArray[i]=readWriteSockfd;flag=true;LOG(INFO,"Add %d to fdArray success!\n",readWriteSockfd);break;}}if(flag==false)//合法文件描述符集合没有空位置{LOG(WARNING,"FdArray is full, Server is Full");close(readWriteSockfd);//关闭该读写文件描述符}}}//处理就绪的读写套接字文件描述符void HandleReadWriteSockfd(int i){char buffer[1024];int n=read(fdArray[i],buffer,sizeof(buffer)-1);if(n>0)//读取成功{//输出读取到的数据buffer[n]=0;std::cout<<"Client message: "<<buffer<<std::endl;//给客户端返回数据std::string content = "<html><body><h1>hello GJJ</h1></body></html>";std::string echo_str = "HTTP/1.1 200 OK\r\n";echo_str += "Content-Type: text/html\r\n";echo_str += "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";echo_str += content;send(fdArray[i], echo_str.c_str(), echo_str.size(), 0); // 临时方案}else if(n==0)//客户端退出{LOG(INFO,"Client quit!\n");close(fdArray[i]);//关闭该读写文件描述符fdArray[i]=gDefaultFd;//将该读写文件描述符从合法文件描述符集合中移除,这样下一次循环时其就不会被添加到rfds集合中被select函数监视}else//读取错误{LOG(ERROR,"Read error\n");close(fdArray[i]);//关闭该读写文件描述符fdArray[i]=gDefaultFd;//将该读写文件描述符从合法文件描述符集合中移除,这样下一次循环时其就不会被添加到rfds集合中被select函数监视}}//服务器循环执行void Loop(){while(true){//普通IO(阻塞式IO):直接使用监听套接字接收客户端的连接// InetAddr clientaddr;//存储连接到的客户端地址信息// std::shared_ptr<Socket> readwritesock=_listensock->Accept(&clientaddr);// if(readwritesock==nullptr)// {//     continue;// }//多路复用IO:将获取新连接的过程也看作是IO//客户端发送连接请求相当于数据就绪,服务端接收客户端的连接相当于读取数据(拷贝数据)//利用多路复用IO,让select等待客户端发送连接请求//创建并初始化读事件文件描述符集合fd_set rfds;//读事件的文件描述符集合FD_ZERO(&rfds);//清空集合int maxFd=gDefaultFd;//最大的文件描述符(+1后作为select函数的参数)for(int i=0;i<gNum;i++)//将所有合法的文件描述符添加到读事件文件描述符集合,让select监视{if(fdArray[i]!=gDefaultFd){FD_SET(fdArray[i],&rfds);if(fdArray[i]>maxFd)//更新最大的文件描述符{maxFd=fdArray[i];}}}//调用select函数监视读事件文件描述符集合中的文件描述符(这些文件描述符可能是监听套接字的,也可能是读写套接字的)struct timeval timeout={5,0};//设置定时阻塞5秒int n=select(maxFd+1,&rfds,nullptr,nullptr,&timeout/*nullptr*/);if(n==0)//超时{LOG(DEBUG,"Time out, %d.%d\n",timeout.tv_sec,timeout.tv_usec);}else if(n==-1)//select错误{LOG(ERROR,"Select error\n");}else//成功,有事件就绪{LOG(DEBUG,"Left time is %d.%d\n",timeout.tv_sec,timeout.tv_usec);LOG(INFO,"%d event ready\n",n);//如果文件描述符就绪但是未处理,该文件描述符会一直在集合中//处理就绪的文件描述符HandleEvent(rfds);}}}
};

二、poll实现多路复用

1.poll函数介绍

函数原型:

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

返回值:成功返回就绪的文件描述符个数,失败返回-1并设置错误码。返回值为0表示超时(timeout时间耗尽,没有文件描述符就绪)

struct pollfd *fds:struct pollfd结构体数组,存放struct pollfd结构体,每个结构体中都包含一个文件描述符

struct pollfd结构体原型:

struct pollfd {int fd;       // 文件描述符short events;   // 要监控的事件(输入)short revents;  // 实际发生的事件(输出)
};

short events:指定监视的文件描述符的事件,16位短整型,每一位为1时表示一个事件。使用 | 组合多个要监视的事件,例如 POLLIN | POLLOUT | POLLERR,监视可读、可写和错误事件,二进制表示为0000 0000 0000 1101

short revents:监视的文件描述符实际发生的事件,16位短整型,每一位位、为1时表示一个事件。使用 & 检测特定事件,例如 revents & POLLIN检测可读事件是否发生

events/revents事件类型:

nfds_t nfds:指定struct pollfd *fds结构体数组的长度,即监控的文件描述符最大数量

int timeout:单位是毫秒。timeout>0表示阻塞等待timeout毫秒;timeout=0表示非阻塞等待,立即返回;timeout=-1表示永久阻塞,一直等到有文件描述符就绪。(poll的timeout不会像select中的timeout返回剩余的时间)

2.poll优缺点

优点:

  • 支持无限的文件描述符数量,struct pollfd结构体数组可以动态扩容
  • 支持更多的事件类型
  • 监视事件events和就绪事件revents分离,内核不会修改events,只需要重置revents即可重复使用

缺点:

  • 每次调用poll都要将struct pollfd结构体数组拷贝到内核,开销大,效率低
  • 每次调用poll都要遍历struct pollfd结构体数组,开销大,效率低
3.poll使用示例

poll也要配合一个合法文件描述符数组,该数组是struct pollfd类型的结构体数组,struct pollfd结构体包含文件描述符以及监视的事件以及实际就绪的事件,相当于把slelect函数中的fd_set *readfds, fd_set *writefds, fd_set *exceptfds都合并到struct pollfd中了,并且poll支持监视的事件类型更多。当就绪的文件描述符处理完毕后,有些文件描述符可能要关闭,就会将他们从struct pollfd结构体数组中移除,由于struct pollfd是全局变量并且直接传递给poll函数,因此无需像select那样将合法文件描述符传递给fd_set,再传给select,而且每次fd_set还需要重置。

#pragma once
#include <iostream>
#include <memory>
#include <poll.h>//提供poll函数
#include "Socket.hpp"using namespace socket_ns;
using namespace log_ns;//基于多路复用IO的服务器
class PollServer
{static const int gNum=1024;//支持的文件描述符数量
static const int gDefaultFd=-1;//private:uint16_t _port;//端口号std::shared_ptr<Socket> _listensock;//Socket对象,包含监听套接字//int fdArray[gNum];//存放所有合法的文件描述符(用到的文件描述符),文件描述符按出现的顺序存放在数组中,fdArray[i]=-1表示该位置没有文件描述符struct pollfd fdEvents[gNum];//存放所有的合法文件描述符,文件描述符由struct pollfd结构体管理,存放在struct pollfd结构体数组中
public://构造函数PollServer(uint16_t port):_port(port),_listensock(std::make_shared<TcpSocket>())//父类对象Socket接收子类对象TcpSocket{_listensock->StartListening(_port);//开始监听//初始化合法文件描述符数组for(int i=0;i<gNum;i++){fdEvents[i].fd=gDefaultFd;fdEvents[i].events=0;fdEvents[i].revents=0;}//fdArray[0]=_listensock->GetSockfd();//监听套接字的文件描述符是第一个合法的文件描述符,要添加到fdArray中fdEvents[0].fd=_listensock->GetSockfd();//监听文件描述符是第一个合法的文件描述符,添加到fdEvents数组中fdEvents[0].events=POLLIN;//监听事件为数据可读}//析构函数~PollServer(){}
public://处理就绪的文件描述符void HandleEvent(){//遍历合法的文件描述符数组,对就绪的文件描述符作出处理(事件派发)for(int i=0;i<gNum;i++){if(fdEvents[i].fd!=gDefaultFd)//筛选出合法的文件描述符{if(fdEvents[i].revents&POLLIN)//筛选出读事件就绪的文件描述符{if(fdEvents[i].fd==_listensock->GetSockfd())//是监听套接字的文件描述符{HandleListenSockfd();}else//是读写套接字的文件描述符{HandleReadWriteSockfd(i);}}}}}//处理就绪的监听套接字文件描述符void HandleListenSockfd(){InetAddr clientAddr;//保存连接到的客户端地址信息int readWriteSockfd=_listensock->Accept(&clientAddr);//服务端接收连接,并获取到读写文件描述符if(readWriteSockfd>0){LOG(INFO,"Get a new link, client ip:%s port:%d\n",clientAddr.Ip().c_str(),clientAddr.Port());bool flag=false;//标志位,看合法文件描述符集合中有没有空位置for(int i=0;i<gNum;i++){if(fdEvents[i].fd==gDefaultFd)//在合法文件描述符集合中找到一个空位置,将新获得的读写文件描述符添加到合法文件描述符集合中{fdEvents[i].fd=readWriteSockfd;//将新获得的读写文件描述符添加到pollfd结构体数组中fdEvents[i].events=POLL_IN;//对新获得的读写文件描述符监视读事件flag=true;LOG(INFO,"Add %d to fdArray success!\n",readWriteSockfd);break;}}if(flag==false)//合法文件描述符集合没有空位置{LOG(WARNING,"FdArray is full, Server is Full");close(readWriteSockfd);//关闭该读写文件描述符//扩容...再添加}}}//处理就绪的读写套接字文件描述符void HandleReadWriteSockfd(int i){char buffer[1024];int n=read(fdEvents[i].fd,buffer,sizeof(buffer)-1);if(n>0)//读取成功{//输出读取到的数据buffer[n]=0;std::cout<<"Client message: "<<buffer<<std::endl;//给客户端返回数据std::string content = "<html><body><h1>hello GJJ</h1></body></html>";std::string echo_str = "HTTP/1.1 200 OK\r\n";echo_str += "Content-Type: text/html\r\n";echo_str += "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";echo_str += content;send(fdEvents[i].fd, echo_str.c_str(), echo_str.size(), 0); // 临时方案}else if(n==0)//客户端退出{LOG(INFO,"Client quit!\n");close(fdEvents[i].fd);//关闭该读写文件描述符fdEvents[i].fd=gDefaultFd;//将该读写文件描述符从合法文件描述符集合中移除,这样下一次循环时其就不会被添加到rfds集合中被poll函数监视fdEvents[i].events=0;fdEvents[i].revents=0;}else//读取错误{LOG(ERROR,"Read error\n");close(fdEvents[i].fd);//关闭该读写文件描述符fdEvents[i].fd=gDefaultFd;//将该读写文件描述符从合法文件描述符集合中移除,这样下一次循环时其就不会被添加到rfds集合中被poll函数监视fdEvents[i].events=0;fdEvents[i].revents=0;}}//服务器循环执行void Loop(){while(true){//普通IO(阻塞式IO):直接使用监听套接字接收客户端的连接// InetAddr clientaddr;//存储连接到的客户端地址信息// std::shared_ptr<Socket> readwritesock=_listensock->Accept(&clientaddr);// if(readwritesock==nullptr)// {//     continue;// }//多路复用IO:将获取新连接的过程也看作是IO//客户端发送连接请求相当于数据就绪,服务端接收客户端的连接相当于读取数据(拷贝数据)//利用多路复用IO,让poll等待客户端发送连接请求//调用poll函数监视所有的文件描述符的指定事件int timeout=3000;//毫秒int n=poll(fdEvents,gNum,timeout);if(n==0)//超时{LOG(DEBUG,"Time out\n");}else if(n==-1)//Poll错误{LOG(ERROR,"Poll error\n");}else//成功,有事件就绪{LOG(INFO,"%d event ready\n",n);//如果文件描述符就绪但是未处理,该文件描述符会一直在集合中//处理就绪的文件描述符HandleEvent();}}}
};

三、epoll实现多路复用

1.epoll函数介绍

epoll实现多路复用有三个函数

#include <sys/epoll.h>

int epoll_create(int size);

用途:创建一个epoll实例,操作系统内核会为每个epoll实例分配红黑树和就绪链表

返回值:成功返回epoll实例的文件描述符,失败返回-1并设置错误码

int size:已弃用,原表示预期监控的文件描述符数量,现在内核可以动态调整,设置为大于0即可


int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

用途:为epoll实例添加、修改、删除要监控的文件描述符及事件

返回值:成功返回0,失败返回-1并设置错误码

int epfd:epoll实例的文件描述符

int op:操作epoll实例中的文件描述符,EPOLL_CTL_ADD是添加文件描述符;EPOLL_CTL_MOD是修改文件描述符;EPOLL_CTL_DEL是删除文件描述符

int fd:要操作的文件描述符

struct epoll_event* event:struct epoll_event结构体指针,包含操作的文件描述符监控的事件类型和用户数据

struct epoll_event结构体:

uint32_t events:监控的事件类型

epoll_data_t data:用户数据,可以携带一些额外数据当某个文件描述符的某个事件就绪返回给epoll_wait函数的struct epoll_event *events结构体数组中时,也带上该数据(通常是使用int fd带上就绪事件对应的文件描述符,这样在事件就绪时就能知道是哪一个文件描述符的事件)

struct epoll_event {uint32_t     events;  // 监控的事件类型(位掩码)epoll_data_t data;    // 用户数据(可携带额外信息)
};typedef union epoll_data {void    *ptr;   // 自定义指针(如对象指针)int      fd;    // 文件描述符uint32_t u32;   // 32位整数uint64_t u64;   // 64位整数
} epoll_data_t;

uint32_t events监控事件类型:


int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

用途:等待epoll实例中监控的文件描述符对应的事件就绪,返回就绪的事件列表

返回值:成功返回就绪事件的数量,失败返回-1并设置错误码。返回值为0表示超时

int epfd:epoll实例的文件描述符

struct epoll_event* events:struct epoll_event结构体数组,存放就绪事件的数组。输入输出型参数,由用户定义传入再获取数据。当有n个事件就绪时,epoll_wait函数会将这n个事件按照顺序放入struct epoll_event结构体数组中。

int maxevents:struct epoll_event结构体数组的最大容量

int timeout:单位是毫秒。timeout>0表示阻塞等待timeout毫秒;timeout=0表示非阻塞等待,立即返回;timeout=-1表示永久阻塞,一直等到有文件描述符就绪。(epoll的timeout不会像select中的timeout返回剩余的时间)

2.epoll工作原理

每次调用epoll_create函数时,内核就会创建一个eventpoll结构体,该结构体中主要包含一个红黑树和一个就绪链表。红黑树和就绪链表的节点都是epitem结构体,管理的是事件。红黑树存储的是通过epoll_ctl添加的文件描述符及其监控的事件,可以快速对监控的文件描述符及其事件进行增删查改。就绪链表存储的是已经就绪的文件描述符的监控事件,每当监控的文件描述符IO状态发生变化,内核调用回调函数将就绪的事件添加到就绪链表中。

所有添加到 epoll 中的事件都会与设备(网卡)驱动程序建立回调关系,当响应的事件发生时会调用这个回调方法,这个回调方法在内核中叫 ep_poll_callback,它会将发生的事件添加到就绪链表中

3.epoll工作模式

epoll有两个工作模式:水平触发LT模式、边缘触发ET模式(epoll默认是LT模式)

LT和ET模式是为文件描述符设置的,不同的文件描述符模式可以不同

(1)水平触发LT模式

只要所监控的文件描述符的某个事件处于就绪状态,但未被处理则持续通知

(2)边缘触发ET模式

当监控的文件描述符的某个事件就绪时,仅通知一次,除非该文件描述符又有新的事件就绪,才会再次通知。

通过在events中加上EPOLLET将文件描述符设置为ET模式

如果采用ET模式,那么所有添加到epoll实例中的文件描述符必须使用fcntl函数设置为非阻塞模式。因为ET模式下当事件就绪时仅通知一次,例如读事件就绪,那么应用层就必须一次性把接收缓冲区的数据全部读完,否则就没有下次机会再次读取了,因为epoll_wait函数不会通知第二次。那么就要考虑如何一次性将接收缓冲区的数据全部读取完,要采用循环读取的方式,直到读取不到数据,就说明数据全部读取完了。如果文件描述符采用的是阻塞模式,那么当应用层循环读取,读取不到数据时,就会被阻塞住,导致线程无法处理其他文件描述符。

(3)LT与ET模式效率对比

ET模式比LT模式效率更高,因为

  • ET模式不会持续通知,仅通知一次
  • ET模式下应用层必须一次性将接收缓冲区的数据读取完,返回的TCP窗口更大 
4.epoll函数优缺点

优点

  • 监控的文件描述符数量没有上限
  • 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪,时间复杂度为O(1)
  • 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而 select/poll 都是每次循环都要进行拷贝)
5.poll函数使用示例

LT模式

epoll要配合一个struct epoll_event结构体数组使用,将需要监视文件描述符及其事件添加到epoll实例中,当epoll实例中的事件就绪后,内核调用回调方法将这些就绪事件添加到就绪链表中,并且返回就绪事件到struct epoll_event结构体数组中。当某个文件描述符处理完毕需要关闭时,要将其仓epoll实例中删除,并关闭该文件描述符

#pragma once
#include <iostream>
#include <memory>
#include <sys/epoll.h>//提供epoll相关函数
#include "Socket.hpp"
#include "Log.hpp"using namespace socket_ns;
using namespace log_ns;//基于Epoll实现的多路复用服务器
class EpollServer
{static const int num=128;//struct epoll结构体数组的最大容量private:uint16_t _port;//端口号std::shared_ptr<Socket> _listenSock;//Socket对象,包含监听套接字int _epfd;//epoll实例的文件描述符struct epoll_event _readyEvents[num];//作为参数传递给epoll_wait函数,存放就绪的事件public://构造函数EpollServer(uint16_t port):_port(port),_listenSock(std::make_shared<TcpSocket>()){//服务器开始监听_listenSock->StartListening(_port);//创建epoll实例(内核为epoll实例分配红黑树和就绪链表)_epfd=epoll_create(1);if(_epfd<0){perror("why? ");LOG(FATAL,"Epoll create error!\n");exit(1);}LOG(INFO,"Epoll create success!\n");//初始化:将监听文件描述符添加到epoll实例中struct epoll_event ev;ev.events=EPOLLIN;//监听事件为数据可读(即有新连接到来)ev.data.fd=_listenSock->GetSockfd();int n=epoll_ctl(_epfd,EPOLL_CTL_ADD,_listenSock->GetSockfd(),&ev);if(n==0){LOG(INFO,"Epoll ctl success, add listensockfd: %d to epoll.\n",_listenSock->GetSockfd());}else{LOG(FATAL,"Epoll ctl error!\n");exit(2);}}//析构函数~EpollServer(){if(_epfd>=0){close(_epfd);//关闭epoll实例文件描述符}_listenSock->CloseSocket();//关闭监听套接字文件描述符}
public://服务器循环执行void Loop(){int timeout=5000;//5000毫秒while(true){int n=epoll_wait(_epfd,_readyEvents,num,timeout);if(n>0)//有事件就绪{LOG(INFO,"%d event is ready!\n",n);HandleEvent(n);//处理就绪的事件}else if(n==0)//超时{LOG(INFO,"Time out!\n");}else//epoll_wait错误{LOG(ERROR,"Epoll wait error!\n");}}}//处理就绪的事件void HandleEvent(int n){//有n个事件就绪,在struct epoll_event结构体数组中for(int i=0;i<n;i++){//此处暂时只处理读事件if(_readyEvents[i].events&EPOLLIN){if (_readyEvents[i].data.fd == _listenSock->GetSockfd()) //处理监听文件描述符的事件(有新连接){HandleListenSockfd();}else //读写文件描述符的事件(发送和接收数据){HandleReadWriteSockfd(i);}}}}//处理监听文件描述符的事件void HandleListenSockfd(){InetAddr clientAddr;//存储客户端的地址信息int readWriteSockfd=_listenSock->Accept(&clientAddr);//服务端接收客户端的连接if(readWriteSockfd<0){LOG(ERROR,"Get link error!\n");return;}else{//将新连接的读写文件描述符添加到epoll实例中struct epoll_event ev;ev.events=EPOLLIN;ev.data.fd=readWriteSockfd;int n=epoll_ctl(_epfd,EPOLL_CTL_ADD,readWriteSockfd,&ev);if(n==0){LOG(INFO,"Epoll ctl success, add readWriteSockfd: %d to epoll.\n",readWriteSockfd);}else{LOG(ERROR,"Epoll ctl error!\n");return;}}}//处理读写文件描述符的事件void HandleReadWriteSockfd(int i){//此处还未引入协议,暂时认为读取到的数据都是一个完整的报文char buffer[2048];//存储读取到的数据int n=read(_readyEvents[i].data.fd,buffer,sizeof(buffer)-1);if(n>0)//读取数据成功{//输出读取到的数据buffer[n]=0;std::cout<<"Client message: "<<buffer<<std::endl;//给客户端返回数据std::string content = "<html><body><h1>hello GJJ</h1></body></html>";std::string echo_str = "HTTP/1.1 200 OK\r\n";echo_str += "Content-Type: text/html\r\n";echo_str += "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";echo_str += content;send(_readyEvents[i].data.fd, echo_str.c_str(), echo_str.size(), 0); // 临时方案}else if(n==0)//客户端退出{LOG(INFO,"Client quit!\n");epoll_ctl(_epfd,EPOLL_CTL_DEL,_readyEvents[i].data.fd,nullptr);//从epoll实例中删除该文件描述符close(_readyEvents[i].data.fd);//注意:从epoll实例中移除的文件描述符必须是合法的,所以要先移除再关闭文件描述符}else//读取错误{LOG(ERROR,"Read error\n");epoll_ctl(_epfd,EPOLL_CTL_DEL,_readyEvents[i].data.fd,nullptr);//从epoll实例中删除该文件描述符close(_readyEvents[i].data.fd);//注意:从epoll实例中移除的文件描述符必须是合法的,所以要先移除再关闭文件描述符}}
};

四、select、poll、select综合对比

总结:

  • select:每一次调用select都要将fd_set集合中fd从用户空间拷贝到内核空间,内核再遍历所有文件描述符检测是否有事件就绪,再将所有的有事件就绪的文件描述符拷贝到用户空间,返回到fd_set集合中(就绪的文件描述符对应的事件有标记)
  • poll:每一次调用poll都要将struct pollfd* fds结构体数组中的fd从用户空间拷贝到内核空间,内核再遍历所有文件描述符检测是否有事件就绪,再将所有的有事件就绪的文件描述符拷贝到用户空间,返回到struct pollfd* fds结构体数组中(就绪的文件描述符对应的事件有标记)
  • epoll:只需要一次,将需要监控的文件描述符及其事件添加到epoll实例中(红黑树),无需遍历所有的文件描述符检测事件是否就绪,当事件就绪时内核会通过回调机制触发事件,将就绪的事件添加到就绪链表中,再将就绪链表中的fd返回到struct epoll_event *events结构体数组中

 

http://www.dtcms.com/wzjs/280913.html

相关文章:

  • 网站建设有关图片关键词搜索量排名
  • 阿里妈妈网站推广提交百度网站排名怎么提高
  • 深圳网站关键词山东公司网站推广优化
  • 用html做网站的步骤信息流广告投放流程
  • 美国人 免费seo快速排名工具
  • 做网站用的hu软件自己的app如何接广告
  • 网站建设微信运营销售google网站
  • 网站怎么做视频个人seo怎么赚钱
  • wordpress图片视频分享聊城seo优化
  • dedecms 调用网站内部搜索十大电商代运营公司
  • 网站建设的方案实施包括百度指数官网入口
  • 网站界面设计考试如何做网站优化seo
  • 网页链接生成二维码如何优化推广中的关键词
  • 长岛网站建设费用外贸网站如何推广优化
  • 如何制作h5做网站舆情分析报告案例
  • 汕头企业自助建站会计培训班要多少钱一般要学多久
  • 如何加速wordpress反应速度seo优化培训机构
  • 优速网站建设今日国际军事新闻头条
  • 泉州政府网站的建设现状 存在问题免费行情网站
  • 罗湖区网站公司软文网站推广
  • 宁波专业网站制作设计长沙seo 优化选智投未来no1
  • 反馈网站制作seo优化是怎么优化的
  • 做网站签订合同某个产品营销推广方案
  • 重庆企业网站制作公司seo网络培训班
  • 家庭宽带做私人网站湖南省人民政府
  • 西安做网站的公司有哪些营销战略
  • 乡镇网站个人做可以不seo培训网
  • 番禺网站建设外包我要登录百度
  • 广州做网站的价格推广赚佣金的平台
  • 图片制作的软件有哪些广东seo推广方案