9在我们介绍I/O复用之前,先来看一个小例子:
...
while(fgets(sendline, MAXLINE, fp) != NULL){
write(sockfd, sendline, 1)
...
...
}
粗略地描述一下上述代码:
第一行表示从文件fp中读数据到sendline中,第二行表示将sendline中的数据写入套接字描述符sockfd。
现在我们来思考一个问题,假如说当我们正在通过fgets()读数据的时候,套接字描述符已经失效(例如,客户端的服务器挂了),那我们一直阻塞在fgets()那里也不知道,等我们执行到write()时才知道sockfd失去意义时,可能已经过了很长时间了。这样就很没有效率了,我们当然希望只要sockfd一发生变化就能通知我们,通过I/O复用就能起到这个作用。
概念:
I/O复用是最常用的I/O通知机制。应用程序通过I/O复用函数向内核注册一组事件,内核通过I/O复用函数把其中就绪的事件通知给应用程序。既,I/O复用使得程序能同时监听多个文件描述符。
I/O复用典型使用(下列网络应用场合):
a. 客户端要同时处理用户输入和网络连接(文章开头的例子)
b. 客户端程序要同时处理多个socket
c. TCP服务器要同时处理监听socket和连接socket,这是I/O复用使用最多的场合
d. 服务器要同时处理TCP和UDP请求
f. 服务器要同时监听多个端口,或同时处理多种服务
当然,I/O复用并非只限于网络编程,许多重要的应用程序也需要使用这项技术
一个输入操作通常包含两个不同的阶段:
内核等待数据准备好
从内核向进程复制数据
对于一个套接字上的输入操作,第一步通常是内核等待数据从网络中到达,当所等分组到达时,它被复制到内核中的某个缓冲区。然后第二步就是数据从内核缓冲区复制到应用进程缓冲区。相关图如下图(1)及图(2):
select 系统调用:
select 系统调用的用途是:
在指定的一段时间内,监听用户感兴趣的文件描述符
上的可读、可写、和异常等事件。该函数允许进程指示内核等待多个事件中的任何一个事件发生,并且只在有一个或多个事件发生或者经历一段指定的时间后才唤醒它。
例如,我们调用select函数,告知内核仅在下列情况发生时才返回:
集合{
1,4,5}中的任何描述符准备好读
集合{
2,7}中的任何描述符准备好写
集合{
1,4}中的任何描述符有异常条件待处理
已经经历了20.2秒
也就是说我们调用select 函数告诉内核对哪些描述符感兴趣