采集来自于

https://blog.csdn.net/hguisu/article/details/7453390

https://blog.csdn.net/limo120621/article/details/52757390

 https://blog.csdn.net/tennysonsky/article/details/45671215

 

 

综上,在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点。

1、表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。

2、select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善

同步/异步与阻塞/非阻塞经常看到是成对出现:

同步阻塞,异步非阻塞,同步非阻塞

 

 

 

 

 

 

同步与异步

同步和异步的关注点在于消息通信机制:对于同步而言,是调用者主动等待调用结果,不得到结果不返回。而对于异步,调用在发出后不等结果便返回了,而后被调用者通过状态、通知来告知调用者,或通过回调函数进行处理。

《Unix网络编程:卷一》6.2.7节中如此描述同步和异步IO:

  • 同步I/O操作 (synchronous I/O operation) :导致请求进程阻塞,直到I/O操作完成;
  • 异步I/O操作 (asynchronous I/O operation):不导致请求进程阻塞。

根据这个定义,阻塞式IO,非阻塞IO和IO复用这三个模型均属于同步IO,因为在内核数据准备好时,进程调用的IO操作(read、recv)会将数据拷贝至用户空间,此时应用进程将被阻塞。而对比此时的异步IO,当进程发起IO操作调用后直接返回,直到1.数据准备就绪 2.数据由内核拷贝至用户空间 这两个操作完成后被告知IO操作已经完成,在此过程中应用进程完全没有被阻塞。所以,阻塞和非阻塞是针对同步IO而言,对于异步IO而言没有所谓阻塞和非阻塞之分。

总的而言,在处理IO操作时,阻塞和非阻塞都是同步IO,只有使用特殊 API 时才属于异步IO。

这里写图片描述

补充:对于IO复用模型中的epoll调用,因为epoll函数采用 mmap的机制, 使得内核的套接字缓冲区和用户空间中的缓冲区共享了,从而省去了将数据拷贝至用户空间这一步骤,这也意味着, 当epoll回调上层的回调函数来处理套接字数据时, 数据已经从内核层 “自动” 到了用户空间,所以epoll通知应用进程时,数据已经到达了用户空间,这时的read/recv等调用只用读取用户空间中的缓冲区了,而不用进行拷贝操作了。所以epoll这个调用很多时候会被误理解为异步IO,但是epoll在告知应用进程前还是被阻塞了,只不过将阻塞点由IO系统调用转移到了epoll这个系统调用上,所以IO复用模型中的epoll虽然在业务逻辑上有异步,但是从IO操作层面上而言还是属于同步IO的。

 


 

 

 

而poll与select的主要区别在于,select需要为读、写、异常事件分配创建一个描述符集合,最后轮询的时候,需要分别轮询这三个集合。而poll只需要一个集合,在每个描述符对应的结构上分别设置读、写、异常事件,最后轮询的时候,可以同时检查三种事件。poll与select在处理思想上是同一个层次,当然poll相对于select又优化,而epoll,则是完全不同的机制,有本质上的区别。

 

对于select模型,大多都是说他的缺点,实际上我的观点有点不一样,select模型的跨平台性是比较好的,开发也比较简单,只有当个进程连接数的限制,以及其性能随着连接数增长下降的问题,实际上都得根据项目的实际情况而定的。在内部分布式通讯中,几乎所有的连接都是活跃的情况下,select模型并不比epoll的性能差,只是在一些应用中大部分连接都不活跃的情况,epoll的使用效果要高,可以使单台服务器的承载量大大增加,实际上游戏服务器中玩家绝大部发是活跃的,因此实际性能也还不错。

 

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。


 

设置为非阻塞的方法有:

    (1)创建socket的时候,指定socket是异步的,在type的参数中设置SOCK_NONBLOCK标志即可。

  1. int socket(int domain, int type, int protocol);  
  2. int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);  

    (2)使用fcntl函数: 

 
  1. fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);  

    (3)使用ioctl函数:

  1. ioctl(sockfd, FIONBIO, 1);  //1:非阻塞 0:阻塞