IO复用的具体理解:感觉有点像剪枝,就是提高了利用率。
可以认为select函数是IO复用的全部内容。
先理解理解fd_set数组变量,该数组是存有0和1的位数组。
既然是位数组,那要对fd_set变量进行操作的话是以位为单位的,这样也就意味着直接操作会比较麻烦,因此我们都通过下面这些宏来对fd_set变量进行操作。
FD_ZERO(fd_set *fdset); 将fd_set变量的所有位初始化为0;
FD_SET(inr fd, fd_set * fdset); 在fdset指向的变量中注册文件描述符fd的信息;
FD_CLR(int fd, fd_set *fdset); 从fdset指向的变量中清除文件描述符fd的信息;
FD_ISSET(int fd, fd_set *fdset);用于验证select函数的调用结果。
关于select的返回值,要是超时则返回零,要是发生错误返回-1,成功则返回发生变化的文件描述符数量。
struct timeval{
long tv_sec; //second;
long tv_usec;//microseconds;
}
最后一个参数是超时时间,要是不想设置超时就传入NULL;
需要注意的点:
-
每次select函数调用完成后,向其中传递的fd_set变量中将发生变化,即原来为1的所有位均会变成0,只有发生变化的文件描述符对于位除外。
(这一点第一遍学这本书的时候是有些没看懂,之前就强行记住了用法,现在又复习一遍的时候竟然直接清楚了,奇怪奇怪。🤭)
只有原来为一的并且发生变化的文件描述符对应位除外,很好的控制成了只有被监视的那些并且发生变化的文件描述符才会被记录。可以理解为被记录了。
因为这一点的特性,用到select的时候,都需要备份一下fd_set; -
每次select函数调用完成后,最后一个超时参数也会发生变化,可以理解为为了方便记录剩余时间,最后一个参数也就是timeval这个结构体里的值会被替换成剩余时间。
所以呢 每次调用select函数之前都需要初始化timeval结构体变量。
最后调用完select之后,可以用FD_ISSET验证select函数的调用结果。
每次验证时都需要遍历所有文件描述符,对于每个文件描述符i,调用if(FD_ISSET( i, &cpy_reads)){};若当前文件描述符是被监视的并发生变化的那么FD_ISSET的返回值才是true;
以上知识都完全搞定了,IO复用的并行式服务器的实现也就很简单了。书上P203页随时可以看。不难。
以上是第十二章的,既然是复习那就直接先把第十七章复习了,第十七章也是实现IO复用的服务器端,只不过用了比select更好的方法,epoll;
epoll是在linux的,windows里的优与select的是IOCP。
其实select速度比较慢。
对于上面知识有两个很明显的速度慢原因:
1、每次调用select时都需要重新向该函数传递监视对象的信息。
2、在调用完select之后。为了用FD_ISSET验证调用结果,需要针对所有文件描述符执行循环语句。
而这两点之中,最大的障碍其实是第一点。看上去好像是第二点的循环更麻烦,实际上是第一点的传递信息的过程。
为什么呢。
因为select函数与文件描述符有关,而文件描述符和套接字有关,套接字是属于操作系统管理的。所以select函数绝对需要借助于操作系统才能完成,而应用程序向操作系统传递数据将对程序造成很大的负担,更可怕的是无法用过优化代码解决,因此会成为性能上致命的弱点。
那针对上述的两个造成select速度慢的原因呢,一个一个解决是最好的办法,即做到以下两点:
1、仅向操作系统传递1次监视对象。
2、监视内容发生变化时只通知发生变化的事项,这样就不需要便利所有文件描述符了。
能完成以上两个优化的就是epoll!
上面说了半天select 的缺点,其实人家也是有优点的:
select的兼容性很强。
所以当满足以下两点的时候用select比epoll更优:
1、服务器端接入者少。
2、要求程序具有兼容性。
传递的size值时给操作系统参考的 epoll例程的大小,目前的系统基本都已经忽略了这个值了,所以不用注意。
epoll例程是归与操作系统管理的。返回值是文件描述符。
epoll例程就是由epoll_create创建的文件描述符保存空间。(注意有些情况下名称不同)
前三个参数都简单,稍微麻烦的在于第四个参数,专业点说法叫监视对象的事件类型。
关于第二个参数目前了解下面三种常量:
1、EPOLL_CTL_ADD;
2、EPOLL_CTL_DEL;
3、EPOLL_CTL_MOD:
关于较麻烦的第四个参数。
struct epoll_event{
__uint32_t events;//存事件类型,目前只会EPOLLIN
epoll_data_t data;
}
第二个参数比较重要,保存发生时间的文件描述符集合的结构体地址。而且需要注意的是第二个参数所指的缓冲需要动态分配。
以上基本可以会使用epoll了。
但是要想真正掌握epoll,需要会正确的区分条件触发和边缘触发。
这个今天不想复习,待补