本文共 4102 字,大约阅读时间需要 13 分钟。
5. 异步I/O模型
其中前4种模型是都是同步I/O模型,有的只是API和API调用方式有所区别,只有最后一种是异步I/O模型。这里区分的标准是I/O读写是否由内核,还是由用户进程来完成。
socket |bind |listen |accept |read/write |close客户端流程如下:
socket |connect |write/read |close使用阻塞模式的套接字,开发网络程序比较简单,容易实现。在套接字数量较少的情况下,可以考虑使用阻塞模型来开发。特别是在通讯双方,一问一答的形式下,阻塞模型实现最为简单。 如果需要在通讯的双方实现全双工,也就是每一方都能够同时收发数据,使用阻塞式套接字,意味着程序为每个连接必须存在两个线程。如果线程之间还需要同步,在大量套接字的情况下,阻塞式Socket模型难以维护,难以扩展。
socket |bind |listen |accept |read(null) -> read(null) -> read(null) -> ... read(data) |write(null) -> write(null) -> write(null) -> ... write(data) |close客户端流程如下:
socket |connect |read(null) -> read(null) -> read(null) -> ... read(data) |write(null) -> write(null) -> write(null) -> ... write(data) |close在上述的非阻塞式I/O模型中,用户使用时需要不断的轮询,也就是不断的调用I/O操作去查询I/O上是否存在数据接收或者数据是否可以发送,因此CPU可能会被一直用于轮询,而造成资源的浪费。所以在实际情况下,很少会使用非阻塞式的I/O模型,但是也不排除使用I/O复用模型的局部,使用该种方式。举个例子,为了方便起见,当使用select函数获得某个socket可写的信号时,使用write函数一次写入的数据大小可能小于我们希望写入的数据大小,这时往往可以继续调用write函数,直至写完。而不必要重新进行select等待,直到等到下一个socket可写信号,而进行写入。
socket |bind |listen |select <============= | |accept/read/write ==== |close客户端流程如下:
socket |connect |select <============= | |read/write =========== |closeI/O复用模型的好处是在于它可以同时处理多个connection。
使用信号驱动I/O时,进程预先告知内核,使得当某个socketfd有events(事件)发生时,内核使用信号通知相关进程。具体见下图:
具体步骤如下: 1. 建立SIGIO信号处理函数 sigaction 结构:struct sigaction { void (*sa_handler)(int); //信号处理函数 sigset_t sa_mask; //用来设置在处理该信号时暂时将sa_mask指定的信号搁置 int sa_flags; //用来设置信号处理的其他相关操作 void (*sa_restorer)(void); //这个参数没有使用 } ;sigaction 函数:
int sigaction(int signum, // 所注册的信号,我们这边都设置为SIGIO const struct sigaction *act, // 信号触发所处理的函数 struct sigaction *oldact); // 一般设置为NULL示例:
void do_sigio(int sig) { /* SIGIO处理 */ } struct sigaction sigio_action;memset(&sigio_action, 0, sizeof(sigio_action));sigio_action.sa_flags = 0;sigio_action.sa_handler = do_sigio;sigaction(SIGIO, &sigio_action, NULL);2. 设置该套接口的属主,通常使用fcntl的F_SETOWN命令设置。
int fd;fd = socket(AF_INET, SOCK_DGRAM, 0);......fcntl(fd, F_SETOWN, getpid()); // 设置套接字所有者以接收SIGIO3. 开启该套接口的信号驱动I/O,通常使用fcntl的F_SETFL命令打开O_ASYNC标志完成。
int flags;flags = fcntl(fd, F_GETFL, 0); // 获取Socket原有flag参数flags |= O_ASYNC | O_NONBLOCK;fcntl(fd, F_SETFL, flags); // 设置信号驱动和非阻塞模式信号驱动I/O模型用的比较少,主要使用在UDP套接字上,TCP套接字几乎不使用。这是因为,在UDP编程中使用信号驱动I/O,此时SIGIO信号产生于下面两种情况: 1. 套接字收到一个数据报。 2. 套接字上发生了异步错误。 因此,当应用因为收到一个UDP数据报而产生的SIGIO时,要么可以调用recvfrom读取该数据报,要么得到一个异步错误。 而对于TCP编程,信号驱动I/O就没有太大意义了,因为对于流式套接字而言,有很多情况都可以导致SIGIO产生,而应用又无法区分是什么具体情况导致该信号产生的。例如 : 1. 监听套接字完成了一个连接请求。 2. 收到了一个断连请求。 3. 断连操作完成。 4. 套接字收到数据。 5. 有数据从套接字发出。 对于TCP下应用信号驱动I/O模型,Stevens 指出:我们应该考虑只对“监听(形容词)TCPsocket”(描述符)使用SIGIO,因为对于“监听TCPsocket”产生SIGIO的唯一条件是新 连接完成。也就是说,只有TCP下只有用作listen的端口,才考虑使用信号驱动I/O模型。
(版权所有,转载时请注明作者和出处)