I/O复用解决的问题:当多个客户端与服务端通讯时,当服务器阻塞于其中一个客户端sockfd时,其他客户端传来数据时服务端无法对其进行处理,这就会出现问题。
I/O复用:有n个客户连接,sockfd1,sockfd2,sockfd3…sockfdn同时监听这n个客户,当其中有一个发来消息时就从select的阻塞中返回,然后就调用read读取收到消息的sockfd,然后又循环回select阻塞;这样就不会因为阻塞在其中一个上而不能处理另一个客户的消息。
三种IO模型:
1.阻塞型I/O
最广泛的模型是阻塞I/O模型,默认情况下,所有套接口都是阻塞的。 进程调用recvfrom系统调用,整个过程是阻塞的,直到数据复制到进程缓冲区时才返回(当然,系统调用被中断也会返回)。
阻塞I/O即只有读取到数据之后,程序才会继续往下走,否则就会一直卡在数据读取处。
2.非阻塞I/O模型
一个套接口设置为非阻塞时,当请求的I/O操作无法完成时,返回一个错误。程序可以在返回值为错误的情况下做其他事情。当请求的数据数据已经存在时,将数据复制到进程缓冲区中。这其中有一个操作时轮询(polling)。
3.I/O复用模型
此模型用到select和poll及epoll,这些个函数也会使进程阻塞。
select先阻塞,有活动套接字才返回,但是和阻塞I/O不同的是,这两个函数可以同时阻塞多个I/O操作,而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写(就是监听多个socket)。select被调用后,进程会被阻塞,内核监视所有select负责的socket,当有任何一个socket的数据准备好了,select就会返回套接字可读,我们就可以调用recvfrom处理数据。select在执行前,需要拷贝一份包含所有sockfd的数据,因为select需要对此数据进行修改,所以要使用值传递,对原数据进行保护。
缺点:
(1)单进程可以打开fd有限制;
(2)对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低;
(3)用户空间和内核空间的复制非常消耗资源;
poll与select稍有不同,poll执行时不需要对sockfd进行拷贝,