凡科做网站类型应该做哪个,钦州建设局网站,浙江制造品牌建设网站,重庆网捷网站建设技术有限公司碌碌无为#xff0c;则余生太长#xff1b; 欲有所为#xff0c;则人生苦短。 --- 中岛敦 《山月记》--- 从零开始使用多路转接IO 1 前言1 poll接口介绍3 代码编写4 总结 1 前言
上一篇文章我们学习了多路转接中的Select#xff0c;其操作很简单#xff0c;但有一些缺… 碌碌无为则余生太长 欲有所为则人生苦短。 --- 中岛敦 《山月记》--- 从零开始使用多路转接IO 1 前言1 poll接口介绍3 代码编写4 总结 1 前言
上一篇文章我们学习了多路转接中的Select其操作很简单但有一些缺陷
每次调用 select都需要手动设置 fd 集合 从接口使用角度来说也非常不便。每次调用 select 都需要把 fd 集合从用户态拷贝到内核态 这个开销在 fd 很多时会很大。这个是多路转接IO无法避免的问题同时每次调用 select 都需要在内核遍历传递进来的所有 fd这个开销在 fd 很多时很大。select 支持的文件描述符数量太小虽然操作系统中文件描述符也有限制但是这是操作系统的缺陷。同样select也是缺点
而poll方案可以解决其中的两个缺点
select支持的文件描述符少poll理论上可以支持无限个文件描述符。select每次调用接口都需要手动设置fd集合poll不需要
那么接下来我们就来看poll是怎样实现的。
1 poll接口介绍
首先poll的作用与select一模一样等待多个文件描述符只负责等待
我们来看看poll接口:
OLL(2) Linux Programmers Manual POLL(2)NAMEpoll, ppoll - wait for some event on a file descriptorSYNOPSIS#include poll.hint poll(struct pollfd *fds, nfds_t nfds, int timeout);#define _GNU_SOURCE /* See feature_test_macros(7) */#include signal.h#include poll.hint ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask);
poll接口中只有三个参数
struct pollfd *fds:这时一个文件描述符数组其中每个元素是一个结构体其中包含文件描述符需要处理的事件类型。nfds_t nfds:表示文件描述符的数量timeout:输入性参数这里直接采用的是毫秒不使用结构体等于0时是非阻塞IO等于-1时是阻塞IO返回值表示是否成功大于0 即有n个就绪了等于0表示超时了小于0就是poll出错了
我们来看看struct pollfd内部是怎么样的
struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */};我们对比一下selectselect需要传入三个事件集输入输出性参数每次都会发生改变所以才需要每次调用都要进行初始化。而poll使用一个结构体对于这个文件描述符有两种事件requested events 与 returned events输入输出并不互相干扰那么就解决了select需要不断初始化的问题。
那么事件类型有哪些呢
宏定义描述POLLIN普通或优先级带数据可读POLLRDNORM同POLLINPOLLRDBAND数据可读优先级带数据POLLPRI高优先级数据可读POLLOUT普通数据可写POLLWRNORM同POLLOUTPOLLWRBAND数据可写优先级带数据POLLERR发生错误POLLHUP挂起对方关闭连接POLLNVAL描述字不是一个打开的文件
这些都是宏定义short events;是一个16位位图可以通过宏定义进行匹配设置我们想要查看哪些事件或者有哪些事件就绪了就都可以通过位运算进行判断就可以了
通过结构体的两个位图
用户就可以告诉内核需要帮我们对fd的哪些事件进行等待了内核也可以通过位图告诉用户fd的哪些事件就绪了
3 代码编写
我们仅仅需要对select的代码做出一些修改即可
首先poll需要一个struct pollfd数组这里储存需要处理的fd。初始化事遍历进行将对应fd设置为-1事件设置为0将listen套接字加入就可以 void Initserver(){// 对数组进行初始化for (int i 0; i gnum; i){fd_array[i].fd gdefault;fd_array[i].events 0;fd_array[i].revents 0;}// 加入监听套接字fd_array[0].fd _listensock-GetSockfd();fd_array[0].events POLLIN;}//...// pollstruct pollfd fd_array[gnum];然后对Loop函数进行修改我们不在需要对数据遍历更新rfds了这样代码看起来就整洁了许多
void Loop(){// 进入服务while (true){// 创建timeoutint timeout 1000;// 进行selectint n ::poll(fd_array, gnum, timeout);switch (n){case 0:// 超时LOG(DEBUG, timeout \n);break;case -1:// 出错了LOG(ERROR, select error\n);break;default:// 正常LOG(INFO, have event ready: n %d\n, n);// 处理事件HandlerEvent();PrintDebug();break;}}}接下来就是HandlerEvent函数进行判断的策略依然是遍历这里只关心读事件
void HandlerEvent(){// 遍历fd_array判断是否有就绪的新事件for (int i 0; i gnum; i){if (fd_array[i].fd gdefault)continue;// 如果有新事件if (fd_array[i].revents POLLIN){// 进行判断是scokfd 还是普通fdif (fd_array[i].fd _listensock-GetSockfd()){Accepter();}// 普通fd 进行正常读写else{HandlerIO(fd_array[i]);}}}}然后就是对于普通套接字和监听套接字的处理针对数组进行稍微修改即可
void Accepter(){// 连接事件就绪InetAddr addr;int sockfd _listensock-Accepter(addr); // 已经就绪 ,不会阻塞// 这时会得到一个新连接if (sockfd 0){LOG(DEBUG, get a new link , client info %s:%d\n, addr.Ip().c_str(), addr.Port());// 将新获取的fd加入到数组中LOG(INFO, get new fd :%d\n, sockfd);bool flag false;for (int i 0; i gnum; i){if (fd_array[i].fd gdefault){flag true;fd_array[i].fd sockfd;fd_array[i].events POLLIN;break;}elsecontinue;}if (flag false){LOG(WARNING, fd_array have fill!\n);return;// 可以进行扩容}}}void HandlerIO(struct pollfd sp){char buffer[1024];int n ::recv(sp.fd, buffer, sizeof(buffer) - 1, 0);if (n 0){// 读取到了数据buffer[n] 0;std::string echo_str [client say]#;echo_str buffer;std::cout echo_str std::endl;// 返回一个报文std::string content htmlbodyh1hello bite/h1/body/html;std::string ret_str HTTP/1.0 200 OK\r\n;ret_str Content-Type: text/html\r\n;ret_str Content-Length: std::to_string(content.size()) \r\n\r\n;ret_str content;// echo_str buffer;::send(sp.fd, ret_str.c_str(), ret_str.size(), 0); // 临时方案}else if (n 0){// 此时fd退出了LOG(INFO, fd:%d quit!\n, sp.fd);::close(sp.fd);sp.fd gdefault;sp.events 0;sp.revents 0;}else{LOG(ERROR, recv error! errno:%d\n, errno);::close(sp.fd);sp.fd gdefault;}}来看效果 很好的实现了我们的需求代码也比select更加的简单了
4 总结
Poll的底层其实也是遍历对我们传入的数据进行遍历这样的效率其实比select并不能高出太多也就是说poll依然有这样的缺点
每次调用 select 都需要把 fd 集合从用户态拷贝到内核态 这个开销在 fd 很多时会很大。这个是多路转接IO无法避免的问题同时每次调用 select 都需要在内核遍历传递进来的所有 fd这个开销在 fd 很多时很大。
这样poll 的处境就很尴尬没有select资历早适配性不如select。性能又比不过epoll! 下一篇文章我们来学习epoll!