专栏首页开发与安全linux网络编程之socket(八):五种I/O模型和select函数简介

linux网络编程之socket(八):五种I/O模型和select函数简介

一、五种I/O模型

1、阻塞I/O

我们在前面所说的I/O模型都是阻塞I/O,即调用recv系统调用,如果没有数据则阻塞等待,当数据到来则将数据从内核空间(套接口缓冲区)拷贝到用户空间(recv函数提供的buf),然后recv返回,进行数据处理。

2、非阻塞I/O

我们可以使用 fcntl(fd, F_SETFL, flag | O_NONBLOCK); 将套接字标志变成非阻塞,调用recv,如果设备暂时没有数据可读就返回-1,同时置errno为EWOULDBLOCK(或者EAGAIN,这两个宏定义的值相同),表示本来应该阻塞在这里(would block,虚拟语气),事实上并没有阻塞而是直接返回错误,调用者应该试着再读一次(again)。这种行为方式称为轮询(Poll),调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备:

while(1) 

非阻塞read(设备1);  if(设备1有数据到达) 

处理数据; 

非阻塞read(设备2);  if(设备2有数据到达) 

处理数据; 

..............................

}

如果read(设备1)是阻塞的,那么只要设备1没有数据到达就会一直阻塞在设备1的read调用上,即使设备2有数据到达也不能处理,使用非阻塞I/O就可以避免设备2得不到及时处理。

非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者需要反复查询做无用功,如果阻塞在那里,操作系统可以调度别的进程执行,就不会做无用功了,在实际应用中非阻塞I/O模型经常与IO multiplexing 一起使用。

3、I/O复用

用select来管理多个I/O,当没有数据时select阻塞,如果在超时时间内数据到来则select返回,再调用recv进行数据的复制,recv返回后处理数据。

4、信号驱动I/O

先注册SIGIO信号的处理函数,进程继续执行其他操作,当数据到来时会发送SIGIO信号给进程,然后可以在信号处理函数中调用recv进行数据的复制,然后recv返回进行数据处理。

5、异步I/O

aio_read 函数也会提供一个buf,系统调用进入内核,如果没有数据则立即返回,进程继续执行其他操作,所以叫异步I/O,当数据到来时内核自动复制数据,然后推送给用户空间,通过在aio_read中指定的信号通知进程,让其处理数据。异步I/O跟信号驱动I/O的不同之处在于,它不用调用recv进行数据的复制,如果将后者比做”拉pull“,则前者可以认为是”push推“,push的效率会高点,其实异步I/O跟windows下面的完成端口差不多,但aio_read的实现或多或少存在问题,用得也比较少。实践中用得比较多的如boost 库的asio 也是异步IO。

脚注:同步和异步的区别在于是不是要求处理消息者自己来完成将数据从内核缓冲区复制回进程缓冲区的过程。消息者阻塞和非阻塞应该是发生在消息的处理的时刻。阻塞其实就是等待,发出通知,等待结果完成。非阻塞属于发出通知,立即返回结果,没有等待过程。

对unix来讲:阻塞式I/O(默认),非阻塞式I/O(nonblock),I/O复用(select/poll/epoll),信号驱动IO都属于同步I/O,因为它们在数据由内核空间复制回进程缓冲区时都是阻塞的(不能干别的事)。只有异步I/O模型(AIO)是符合异步I/O操作的含义的,即在1数据准备完成、2由内核空间拷贝回缓冲区后 通知进程,在等待通知的这段时间里可以干别的事。

POSIX defines these two terms as follows:

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes. An asynchronous I/O operation does not cause the requesting process to be blocked. Using these definitions, the first four I/O models—blocking, nonblocking, I/O multiplexing, and signal-driven I/O—are all synchronous because the actual I/O operation (recvfrom) blocks the process. Only the asynchronous I/O model matches the asynchronous I/O definition.

二、select函数简介

/* According to POSIX.1-2001 */        #include <sys/select.h>        /* According to earlier standards */        #include <sys/time.h>        #include <sys/types.h>        #include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数1:读写异常集合中的文件描述符的最大值加1;

参数2:读集合,关心可读事件;

套接口缓冲区有数据可读 对等连接的写一半关闭。即接收到FIN段,读操作将返回0 如果是监听套接口,已完成连接队列不为空时。 套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取。

参数3:写集合,关心可写事件;

套接口发送缓冲区有空间容纳数据。(连接一旦建立就可写) 对等连接的读一半关闭。即收到RST段之后,再次调用write操作。 套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取。

参数4:异常集合,关心异常事件;

套接口存在带外数据(TCP头部 URG标志,16位紧急指针字段)

参数5:超时时间结构体

对于参数2,3,4来说,如果不关心对应事件则设置为NULL即可。注意5个参数都是输入输出参数,即select返回时可能对其进行了修改,比如集合被修改以便标记哪些套接口发生了事件,时间结构体的传出参数是剩余的时间,如果设置为NULL表示永不超时。用select管理多个I/O,select阻塞等待,一旦其中的一个或多个I/O检测到我们所感兴趣的事件,select函数返回,返回值为检测到的事件个数,并且返回哪些I/O发送了事件,遍历这些事件,进而处理事件。注意当select阻塞返回后,此时调用accept 接收连接是不会阻塞的,直接返回已连接套接字,可以认为是select 提前阻塞了。但此时调用write 还是可能阻塞的,因为需要写入的空间大小可能缓冲区还不满足。

下面是4个可以对集合进行操作的宏: void FD_CLR(int fd, fd_set *set); // 清除出集合 int  FD_ISSET(int fd, fd_set *set); // 判断是否在集合中 void FD_SET(int fd, fd_set *set); // 添加进集合中 void FD_ZERO(fd_set *set); // 将集合清零

RETURN VALUE      On success, select() return the number of file descriptors contained in the three returned descriptor sets (that is,        the total number of bits that are set in readfds, writefds, exceptfds) which may be zero if the timeout  expires  before  anything        interesting  happens.   On error, -1 is returned, and errno is set appropriately; the sets and timeout become undefined, so do not        rely on their contents after an error.

select 函数的举例应用看这里

参考:

《Linux C 编程一站式学习》

《TCP/IP详解 卷一》

《UNP》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • CentOS6.5系统yum安装LAMP环境

    采用Linux下二进制模式来安装LAMP,适合初学者,测试使用。

    刘远飞
  • linux、pthread、qemu 的一次 pthread create 失败的分析

    qemu 因为 pthread create 失败而发生了 crash 。这种类型的问题比较少见,这里记录一下问题的分析过程以及解决思路。

    皮振伟
  • 吴锦华 / 明鑫 : 用户态文件系统 ( FUSE ) 框架分析和实战

    用户态文件系统(filesystem in userspace, 简称 FUSE),它能使用户在无需编辑和编译内核代码的情况下,创建用户自定义的文件系统。

    Linuxer
  • 宋宝华:LEP ( Linux 易用剖析器 ) 是什么,为什么以及怎么办 ( 1 )

    LEP是 Linuxer 之 LEP 项目组(Barry Song,Mac Xu,陈松等以及陈莉君老师)正在致力于打造的一个开源项目,本文是 LEP 文档《 L...

    Linuxer
  • Linux Kernel 4.11 发布

    4月30日,Linus Torvalds 在内核邮件列表上宣布释出 Linux Kernel 4.11。

    云加社区专栏
  • linux 根分区的空间去哪里了 ?记一次根分区满的服务故障排查记录

    linux 根分区的空间去哪里了 ?记一次根分区满的服务故障排查记录。我的排查思路是先找占用没有占用,找占用的文件句柄。

    莫韵
  • linux 内存管理初探

    本文主要介绍 linux 内存组织结构和页面布局,内存碎片产生原因和优化算法,linux 内核几种内存管理的方法,内存使用场景以及内存使用的那些坑。

    郑剑
  • 手把手教你如何优化linux服务器

    服务器的优化是我们最小化安装系统后应该做的事情,下面是一些常见的基本的优化服务器的方法。关闭不需要的服务。列出需要启动的的服务crond、network、ssh...

    潘嘉兴
  • Linux 的进程间通信:文件和文件锁

    我们首先引入文件进行 IPC ,试图先使用文件进行通信引入一个竞争条件的概念,然后使用文件锁解决这个问题,从而先从文件的角度来管中窥豹的看一下后续相关 IPC ...

    邹立巍
  • 【ES私房菜】收集 Linux 系统日志

    ES环境已经就绪,我们需要先调通一类简单日志作为数据上报全链路的试运行。恰好Linux系统日志就是这样一个角色,随处可见,风险可控。这里,我们选择了2种Linu...

    张戈

扫码关注云+社区

领取腾讯云代金券