精华内容
下载资源
问答
  • 提出一种IO复用方法,将设计的IO复用电路应用到拥有多个功能模块的芯片中,使多个模块共享同一组IO,通过减少芯片中IO的数量来节省芯片面积。仿真结果表明,静态输入IO复用电路和动态输出IO复用电路均具有较好的性能,其...
  • 使用epoll实现io复用,多个服务器同时与多个客户端连接。
  • IO复用学习笔记

    千次阅读 2020-07-13 17:52:20
    IO即为网络I/O,多路即为多个TCP连接,复用即为共用一个线程或者进程,模型最大的优势是系统开销小,不必创建也不必维护过多的线程或进程。 2--IO两个阶段IO过程分两阶段: 数据准备阶段。从设备读取数据到内核...

    1--概念:

    IO即为网络I/O,多路即为多个TCP连接,复用即为共用一个线程或者进程,模型最大的优势是系统开销小,不必创建也不必维护过多的线程或进程。

    2--IO两个阶段
    IO过程分两阶段
           数据准备阶段。从设备读取数据到内核空间的缓冲区
    内核空间复制回用户空间进程缓冲区阶段
    发生IO的时候:
           内核从IO设备读数据(例如:淘米,把米放在饭锅里煮饭)进程从内核复制数据(盛饭,从内核这个饭锅里把饭装到碗里来);

          系统调用----read函数。

           IO多路复用,也称为:Event-driven IO所谓IO多路复用,就是同时监控多个IO,有一个准备好了,就不需要继续等待其他IO,可以开始处理,提交了同时处理IO的能力。

                          

    select:几乎所有操作系统平台都支持;

    poll是对select的升级;
    epoll,Linux系统内核2.5+开始支持,对select和poll的增强,在监视的基础上,增加回调机制。BSD、Mac平台有kqueue,windows server有iocp。

           以select为例,将关注的IO操作告诉select函数并调用,进程阻塞,内核“监视”select关注的文件描述符fd,被关注的任何一个fd对应的IO准备好了数据,select返回。再使用read将数据复制到用户进程。
           select举例:食堂供应很多菜(众多的IO),你需要吃某三菜一汤,大师傅(操作系统)所要现做,需要等待,你只好等待大师傅叫。其中一样菜好了,大师傅叫你,说你点的菜有好的了,你得自己遍历找找看哪一样才好了,请服务员把做好的菜打给你。
    epoll是有菜准备好了,大师傅叫你去几号窗口直接打菜,不用自己找菜了。
           一般情况下,select最多能监听1024个fd(文件描述符,监听数量可以修改,但不建议修改),但是由于select采用轮询的方式,当管理的IO多了,每次都要遍历全部fd,效率低下
    epoll没有管理的fd的上限,且是回调机制,不需遍历,效率很高

    IO多路复用的核心是可以同时处理多个连接请求,为此使用了两个系统调用,分别是:

       (1)select/poll/epoll--模型机制:可以监视多个描述符(fd),一旦某个描述符就绪(读/写/异常)就能通知程序进行相应的读写操作。读写操作都是自己负责的,也即是阻塞的,所以本质上都是同步(堵塞)IO。Redis支持这三种机制,默认使用epoll机制。

      (2)recvfrom--接收数据。而blocking IO只调用了recvfrom,所以在连接数不高的情况下,blocking IO的性能不一定比IO多路复用差。

     

    3--我们来看一下IO多路复用模型的三种实现机制:

    3.1、select机制:

    select原理:

    当client连接到server后,server会创建一个该连接的描述符fd,fd有三种状态,分别是读、写、异常,存放在三个fd_set(其实就是三个long型数组)中。

    #include <sys/select.h>
    
    //select接口,调用时会堵塞
    
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    
    而select()的本质就是通过设置和检查fd的三个fd_set来进行下一步处理。

    select大体执行步骤:

    1) 使用copy_from_user从用户空间拷贝fd_set到内核空间

    2) 内核遍历[0,nfds)范围内的每个fd,调用fd所对应的设备的驱动poll函数,poll函数可以检测fd的可用流(读流、写流、异常流)。

    3) 检查是否有流发生,如果有发生,把流设置对应的类别,并执行4,如果没有流发生,执行5。或者timeout=0,执行4。

    4) select返回。

    5)select阻塞进程,等待被流对应的设备唤醒时执行2,或timeout到期,执行4。

    select的局限性:

          维护一个存放大量描述符的数组:每次调用select()都需要将fd_set从用户态拷贝到内核态,然后由内核遍历fd_set,如果fd_set很大,那么拷贝和遍历的开销会很大,为了减少性能损坏,内核对fd_set的大小做了限制,并通过宏定义控制,无法改变(1024)。单进程监听的描述符数量有限制:单进程能打开的最大连接数。轮询遍历数组,效率低下:select机制只知道有IO发生,但是不知道是哪几个描述符,每次唤醒,都需要遍历一次,复杂度为O(n)。

     

    2、poll机制:

    poll原理:

    poll的实现和select非常相似,轮询+遍历+根据描述符的状态处理,只是fd_set换成了pollfd,而且去掉了最大描述符数量限制,其他的局限性同样存在。

    #include <poll.h>
    
    int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
    
    struct pollfd {
    
    int fd; /* 文件描述符 */
    
    short events; /* 等待的事件 */
    
    short revents; /* 实际发生了的事件 */
    
    } ;

     poll 使用流程:

     

    在linux网络编程中:tcp 的11种庄态:

                               

     

    简单解释:

    1 CLOSED:初始状态,表示TCP连接是“关闭着的”或“未打开的”。

    2  LISTEN :表示服务器端的某个SOCKET处于监听状态,可以接受客户端的连接。

    3 SYN_RCVD :表示服务器接收到了来自客户端请求连接的SYN报文。在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat很难看到这种状态,除非故意写一个监测程序,将三次TCP握手过程中最后一个ACK报文不予发送。当TCP连接处于此状态时,再收到客户端的ACK报文,它就会进入到ESTABLISHED 状态。

    4  SYN_SENT :这个状态与SYN_RCVD 状态相呼应,当客户端SOCKET执行connect()进行连接时,它首先发送SYN报文,然后随即进入到SYN_SENT 状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT 状态表示客户端已发送SYN报文。

    5 ESTABLISHED :表示TCP连接已经成功建立。

    6  FIN_WAIT_1 :这个状态得好好解释一下,其实FIN_WAIT_1 和FIN_WAIT_2 两种状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET进入到FIN_WAIT_1 状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2 状态。当然在实际的正常情况下,无论对方处于任何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1 状态一般是比较难见到的,而FIN_WAIT_2 状态有时仍可以用netstat看到。

    7 FIN_WAIT_2 :上面已经解释了这种状态的由来,实际上FIN_WAIT_2状态下的SOCKET表示半连接,即有一方调用close()主动要求关闭连接。注意:FIN_WAIT_2 是没有超时的(不像TIME_WAIT 状态),这种状态下如果对方不关闭(不配合完成4次挥手过程),那这个 FIN_WAIT_2 状态将一直保持到系统重启,越来越多的FIN_WAIT_2 状态会导致内核crash。

    8  TIME_WAIT :表示收到了对方的FIN报文,并发送出了ACK报文。 TIME_WAIT状态下的TCP连接会等待2*MSL(Max Segment Lifetime,最大分段生存期,指一个TCP报文在Internet上的最长生存时间。每个具体的TCP协议实现都必须选择一个确定的MSL值,RFC 1122建议是2分钟,但BSD传统实现采用了30秒,Linux可以cat /proc/sys/net/ipv4/tcp_fin_timeout看到本机的这个值),然后即可回到CLOSED 可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(这种情况应该就是四次挥手变成三次挥手的那种情况)

    CLOSING :这种状态在实际情况中应该很少见,属于一种比较罕见的例外状态。正常情况下,当一方发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING 状态表示一方发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?那就是当双方几乎在同时close()一个SOCKET的话,就出现了双方同时发送FIN报文的情况,这是就会出现CLOSING 状态,表示双方都正在关闭SOCKET连接。

    10  CLOSE_WAIT :表示正在等待关闭。怎么理解呢?当对方close()一个SOCKET后发送FIN报文给自己,你的系统毫无疑问地将会回应一个ACK报文给对方,此时TCP连接则进入到CLOSE_WAIT状态。接下来呢,你需要检查自己是否还有数据要发送给对方,如果没有的话,那你也就可以close()这个SOCKET并发送FIN报文给对方,即关闭自己到对方这个方向的连接。有数据的话则看程序的策略,继续发送或丢弃。简单地说,当你处于CLOSE_WAIT 状态下,需要完成的事情是等待你去关闭连接。

    11  LAST_ACK :当被动关闭的一方在发送FIN报文后,等待对方的ACK报文的时候,就处于LAST_ACK 状态。当收到对方的ACK报文后,也就可以进入到CLOSED 可用状态了

    如果客户端关闭套接字close,而服务器调用了一次write,服务会接收到RST segment (TCP传输层)。若服务器再次调用write,这个时候就会产生SIGPIPE信号。

    TIME_WAIT转态对大并发服务器的影响,应尽可能的在服务器端避免TIME_WAIT 状态;如果服务器主动的断开连接,服务端就会进入到TIME_WAIT。

    在协议设计上,应该让客户端主动断开连接,这样就把TIME_WAIT状态就会分散到大量的客户端上,如果客户端不活跃了,一些客户不断开连接,这样就会占用服务器的连接资源。服务器端应该要有个机制来踢掉不活跃的close.

    3、epoll机制:epoll对select和poll进行了改进,避免了上述三个局限性。

     

    epoll原理:

    与select和poll只提供一个接口函数不同的是,epoll提供了三个接口函数及一些结构体:

    /*建一个epoll句柄*/
    
    int epoll_create(int size);
    
    /*向epoll句柄中添加需要监听的fd和时间event*/
    
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    
    /*返回发生事件的队列*/
    
    int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

    struct eventpoll

    {

    ...  

    /*红黑树的根节点,这棵树中存储着所有添加到epoll中的事件,也就是这个epoll_ctl监控的事件*/  

    struct rb_root rbr;

    /*双向链表rdllist保存着将要通过epoll_wait返回给用户的、满足条件的事件*/  

    struct list_head rdllist;

    ...

    调用epoll_create:linux内核会在epoll文件系统创建一个file节点,同时创建一个eventpoll结构体,结构体中有两个重要的成员:rbr是一棵红黑树,用于存放epoll_ctl注册的socket和事件;rdllist是一条双向链表,用于存放准备就绪的事件供epoll_wait调用。调用epoll_ctl:会检测rbr中是否已经存在节点,有就返回,没有则新增,同时会向内核注册回调函数ep_poll_callback,当有事件中断来临时,调用回调函数向rdllist中插入数据,epoll_ctl也可以增删改事件。调用epoll_wait:返回或者判断rdllist中的数据即可。

    epoll两种工作模式:LT--水平触发 ET--边缘触发

    Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!

    Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!

     

    epoll总结:

    使用红黑树而不是数组存放描述符和事件,增删改查非常高效,轻易可处理大量并发连接。红黑树及双向链表都在内核cache中,避免拷贝开销。采用回调机制,事件的发生只需关注rdllist双向链表即可。

                     

     

    展开全文
  • IO复用

    2019-09-18 05:36:35
    IO复用:MariaDBPerconaDBweb :Nginx , LNMP, Memcached ,tomcat ,varnishwww.nginx.org 官方站点 Nginx pronounced engine-xHTTP服务器反向代理 :reverse proxy反向代理的协议 httpmailnetcraft 网站 web市场占有...

    IO复用:
    MariaDB
    PerconaDB
    web :Nginx , LNMP, Memcached ,tomcat ,varnish
    www.nginx.org 官方站点

    Nginx pronounced engine-x
    HTTP服务器
    反向代理 :reverse proxy
    反向代理的协议
    http
    mail
    IO复用
    netcraft 网站 web市场占有率

    IO复用

    Nginx优势 :
    高性能
    稳定性
    丰富的特性
    简单配置
    低资源消耗

    多进程模型
    进程切换
    阻塞状态 不可中断睡眠
    DMA 直接内存访问

    C10K
    单进程 :阻塞
    多进程 :每个进程响应一个请求
    进程量大。进程切换次数过多
    每个进程的地址空间是独立,很多空间是重复的数据,所以内存使用效率较低
    线程 :thread, Light Weight Process, LWP
    每个线程响应一个请求
    线程依然需要切换,切换较之进程轻量级
    同一个进程的线程可以共享进程的诸多资源,比如打开的文件
    对内存的需求较之进程略有下降
    快速切换时会带来线程抖动
    忙等 :自旋锁 Spin lock
    闲等

    多进程多线程模型:
    多线程:n个请求
    一个线程响应多个请求
    select(1024)
    AIO :异步IO

    同步通信
    异步通信

    多路IO,IO复用
    IO复用
    IO Models :
    IO复用
    阻塞IO
    非阻塞IO
    IO复用
    信号驱动IO
    异步IO

    同步IO和异步IO
    IO复用
    异步阻塞IO 又称为IO复用
    阻塞IO具体模型详解:
    IO复用
    非阻塞IO具体模型详解:
    IO复用

    IO复用具体模型详解:
    IO复用

    事件驱动IO:水平触发,边缘触发
    数据准备完成后向进程通知
    5种模型对比 :
    IO复用
    epoll
    /dev/poll
    kqueue

    mmap:内存映射
    异步IO 实现起来比较复杂

    IO:网络IO和磁盘IO
    httpd :
    MPM
    prefork :一个进程响应一个请求 ,1024
    worker :一个线程响应一个请求,多线程,一个进程生成多个线程
    event :基于事件驱动

    Nginx 做反向代理
    程序有局部性,数据有局部性
    CDN 内容分发网络 依赖于智能DNS 内容路由 缓存路由
    hadoop 大数据分析 异步结构
    NoSQL 大量写操作时使用
    模块化 架构师 拓展起来

    展开全文
  • IO复用之select

    千次阅读 2019-07-23 23:10:12
    select()函数和pselect()函数都用于用于IO复用,它们监视多个文件描述符的集合,判断是否有符合条件的事件发生。 select()函数与recv()函数和send()函数不同的是,recv()函数和send()函数可直接操作文件的描述符。...

             select()函数和pselect()函数都用于用于IO复用,它们监视多个文件描述符的集合,判断是否有符合条件的事件发生。

             select()函数与recv()函数和send()函数不同的是,recv()函数和send()函数可直接操作文件的描述符。但是使用select()函数时,需要先对所要操作的文件描述符进行查询,查看目标文件的描述符是否可以进行读、写、或者错误操作,然后当文件的描述符满足操作的条件时才进行真正的IO操作,即读和写操作。

             函数select()的原型为:

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

             各参数含义为:

             nfds:整型变量,它比所有文件描述符集合中的最大值大1。使用select的时候必须计算最大值的文件描述符的值,将值通过nfds传入。

             readfds:这个文件描述符集合监视文件集合中的任何文件是否有数据可读,当select()函数返回的时候,readfds将清除其中不可读的文件描述符,只留下可读的文件描述符,即可以被recv()函数、read()函数等进行读数据的操作。

             writefds:这个文件描述符集合监视文件集合中的任何文件是否有数据可写,当select()函数返回的时候,writefds将清除其中不可写的文件描述符,只留下可写的文件描述符,即可以被send()函数、write()函数等进行写数据的操作。

             exceptfds:这个文件描述符集合监视文件集合中的任何文件是否发生错误,其实它可以用于其他的用途,例如,监视带外数据OOB,带外数据使用MSG_OOB标志发送到套接字上。当select()函数返回的时候,readfds将清除其中的其他文件描述符,只留下可读OOB数据。

             timeout:用来描述等待描述符就绪需要的事件。设置在select()函数所坚实的文件集合中的事件没有发生时,最长的等待时间,当超过此时间时,函数会返回。当超时时间为NULL时,表示阻塞操作,会一直等待,直到某个监视的文件集合中的某个文件描述符符合返回条件。当timeout的值为0时,select()会立即返回。timeout告知系统内核等待指定描述符中的任何一个就绪可花费多少时间。其timeval结构体用于指定这段时间的秒数和微秒数。

    struct timeval
    {
        time_t tv_sec;             //秒
        long tv_usec;              //微秒
    };

             这个结构体参数有三种可能的结果:

             (1)永远等待下去:仅在有一个描述符准备好I/O时才返回,为此,可把该参数设置为空指针NULL。

             (2)等待一段固定时间:在有一个描述符准备好I/O时返回,但是不超过由该参数所指定的秒数或微秒数。

             (3)不等待:检查描述符后立即返回,这种方法称为轮询,为此,该参数必须指向一个timeval结构,而且其定时器值须为0。

             select()函数有3个可能的返回值:成功执行时返回就绪描述符的数目;经过了timeout时长后仍无设备准备好,即超时,返回0;如果出错,返回-1并设置相应的errno,如果select()执行过程中被某个信号中断,返回-1并设置errno为EINTR。

             errorno的取值及含义:

             EBADF:文件描述符无效或该文件已被关闭

             EINVAL:传递了不合法参数

             EINTR:接收到中断信号

             ENOMEM:没有足够内存

             readset,writeset,exceptset都是值-结果参数,即传入指针进去,函数根据指针可以修改对应fd_set。

             通常,操作系统通过宏FD_SETSIZE来声明一个进程中select所能操作的文件描述符的最大数目。在/usr/include/linux/posix_types.h中关于FD_SETSIZE是这样定义的:

    #undef _FD_SETSIZE

    #define _FD_SETSIZE 1024

             除此之外,还有4个宏可以用来操作文件描述符的集合:

             FD_ZERO(fd_set* fdset):将指定的文件描述符集合清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于系统在分配内存空间后通常不作清空处理,所以结果是不可知的。

             FD_SET(fd_set* fdset):用于在文件描述符集合中增加一个新的文件描述符

             FD_CLR(fd_set* fdset):用于在文件描述符集合中删除一个文件描述符

             FD_ISSET(int fd,fd_set* fdset):用于检测指定的文件描述符是否在该文件描述符集合中

             在内核中,socket对应struct socket结构,但在返回给用户空间之前,内核做了一个关联,调用get_unused_fd_flags从当前进程中获取一个可用的文件描述符fd,将struct socket结构关联到该fd,并返回fd给用户空间。所以在用户空间中,socket为文件描述符。另外,进程可以打开的文件数是有限的,为1024,所以socket的取值要小于1024。

    select()函数使用流程:

             select()函数监视的文件描述符可分为3类,分别是readfds,writefds,exceptfds。调用后,select()函数会阻塞,直到有描述符就绪(有数据、可读、可写或者有错误)时,或者超时时才返回,当select()函数返回后,可以通过便利fdset来找到就绪的描述符。需要注意的是,当声明了一个文件描述符集合后,必须用FD_ZERO()函数来将所有位置为0。

    fd_set rset;              //声明描述符集合
    int fd;                   //定义文件描述符
    FD_ZERO(&rset);           //将描述符集合置为0
    FD_SET(fd,&rset);
    FD_SET(stdin,&rset);

             然后调用select()函数,拥塞等待文件描述符事件的到来,如果超过设定的时间,则不再等待,继续往下执行。

    select(fd + 1,&rset,NULL,NULL,NULL);

             select()返回后,用FD_ISSET()函数检测所指定的描述符是否置位。
     

    if(FD_ISSET(fd,&rset))
    {
             ...
    }
    

             select()函数的第一个参数是nfds,是所有加入集合的句柄值的最大的那个值还要加1。比如我们的描述符集合位为1、4、5,那么nfds就为6。当调用select()函数时,描述符从0开始,在描述符集合里匹配我们指定的描述符,当函数返回时,表示那些描述符已经准备好了。那么,怎么判断这个描述符是否准备好呢?我们把这些条件称为socket就绪条件,一下几种情况说明了socket就绪的条件:

             以下四个条件当中的任何一个满足时,套接口准备好读,称为读就绪:

             (1)套接口内核接收缓冲区的数据字节数大于等于套接口接收缓冲区低潮限度的当前值,可以通过SO_REVILOAT来设置此低潮限度,此时可以无阻塞地读该socket,并且读操作返回地字节数大于0。

             (2)连接的读这一半关闭,也就是接收了FIN的TCP连接。

             (3)套接口是一个监听套接口且已完成的连接数为非0。

             (4)有一个套接口错误待处理。

             以下四个条件当中的任何一个满足时,套接口准备好写,称为写就绪:

             (1)套接口接收缓冲区的空间字节数大于等于套接口接收缓冲区低潮限度的当前值,且套接口已连接或者套接口不要求连接,可以通过SO_REVILOAT来设置此低潮限度。

             (2)连接的写这一半关闭,对这样的套接口写操作将产生信号SIGPIEP

             (3)socket使用非阻塞connect连接成功或者失败(超时)之后。

             (4)有一个套接口错误待处理。

             select()能处理的异常情况只有一种:socket上接收到带外数据。

     

             select()函数的优缺点:

             优点:select()目前几乎在所有的平台上支持,其良好的跨平台支持也是它的一个优点。

             缺点:select最大的缺陷就是单个进程所打开的fd的数量是有一定限制的,它由FD_SETSIZE设置,默认值是1024。一般来说,这个数目和系统的内存关系很大,32位机默认是1024,64位机默认是2048;对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低。当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个socket来完成调度,不管哪个socket是活跃的,都得遍历一遍,这会在无形中浪费很多CPU时间,如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询;需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。

    select()函数的使用:

    1.用select()函数实现定时器

    timer.c

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/time.h>
    #include <sys/select.h>
    #include <errno.h>
    //秒级定时器
    void second_timer(unsigned seconds)
    {
        struct timeval tv;
        tv.tv_sec = seconds;
        tv.tv_usec = 0;
        int err;
        do{
            err = select(0,NULL,NULL,NULL,&tv);
        }while(err < 0 && errno == EINTR);
    }
    //毫秒级定时器
    void msecond_timer(unsigned long mseconds)
    {
        struct timeval tv;
        tv.tv_sec = mseconds / 1000;
        tv.tv_usec = (mseconds % 1000) * 1000;
        int err;
        do{
            err = select(0,NULL,NULL,NULL,&tv);
        }while(err < 0 && errno == EINTR);
    }
    
    //微秒级定时器
    void usecond_timer(unsigned long useconds)
    {
        struct timeval tv;
        tv.tv_sec = useconds / 1000000;
        tv.tv_usec = useconds % 1000000;
        int err;
        do{
            err = select(0,NULL,NULL,NULL,&tv);
        }while(err < 0 && errno == EINTR);
    }
    int main()
    {
        int i;
        for(i = 0;i < 10;i++)
        {
            printf("%d\n",i);
            second_timer(1);
            //msecond_timer(1500);
            //usecond_timer(1900000);
        }
        return 0;
    }

    2.检查标准输入

    checkstdin.c

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/select.h>
    
    int main()
    {
        //描述符的集合
        fd_set rset;
        //清空描述符集合
        FD_ZERO(&rset);
        //将标准输入加入到描述符集合中
        FD_SET(0,&rset);
        while(1)
        {
            fflush(stdout);
            //检测描述符,等待事件发生
            int nselect = select(1,&rset,NULL,NULL,NULL);
            if(nselect < 0)
            {
                perror("select:");
                continue;
            }
            if(FD_ISSET(0,&rset))
            {
                char buffer[1024] = {0};
                read(0,buffer,sizeof(buffer) - 1);
                printf("input:%s",buffer);
            }
            else
            {
                printf("error!Invaild fd!\n");
                continue;
            }
            //清空描述符集合
            FD_ZERO(&rset);
            //将0加入到描述符集合中
            FD_SET(0,&rset);
        }
        return 0;
    }
    

     

    3.用select()函数实现回射服务器。客户端从标准输入读入一行,发送到服务器,服务器从网络读取一行,然后输出到客户端,客户端收到服务器的响应,输出这一行到标准输出,通俗来说就是客户端把输入的东西再输出。

    Select_Server.c

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <error.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/select.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    void handle(int clientfds[],int max_fd,fd_set* rset,fd_set* allset)
    {
        int nread;
        int i;
        char buffer[1024];
        for(i = 0;i < max_fd;i++)
        {
            if(clientfds[i] != -1)
            {
                if(FD_ISSET(clientfds[i],rset))
                {
                    //读取客户端socket流
                    nread = read(clientfds[i],buffer,1024);
                    if(nread < 0)
                    {
                        perror("read:");
                        close(clientfds[i]);
                        FD_CLR(clientfds[i],allset);
                        clientfds[i] = -1;
                        continue;
                    }
                    if(nread == 0)
                    {
                        printf("客户端关闭了连接!\n");
                        close(clientfds[i]);
                        FD_CLR(clientfds[i],allset);
                        clientfds[i] = -1;
                        continue;
                    }
                    write(clientfds[i],buffer,nread);
                }
            }
        }
    }
    
    int main()
    {
        //设置服务器的端口号为6888
        short s_port = 6888;
        //设置默认监听队列的长度为1024
        int backlog = 1024;
        //地址结构
        struct sockaddr_in clientaddr;          //客户端地址
        struct sockaddr_in serveraddr;           //服务器地址
        //存放客户端通信描述符的数组
        int clientfds[FD_SETSIZE];
        //所监听的描述符
        int listen_fd;
        //描述符的集合
        fd_set allset,rset;
        //用来记录select()函数的返回值
        int nselect;
        //用来记录最大的描述符
        int max_fd;
        //用来记录客户端的socket描述符
        int client_fd;
        //缓冲区长度
        char buffer[1024];
        //地址结构的长度
        int socketlength = sizeof(struct sockaddr_in);
    
        //创建TCPsocket
        listen_fd = socket(PF_INET,SOCK_STREAM,0);
        //如果创建失败
        if(listen_fd < 0)
        {
            perror("socket:");
            return -1;
        }
    
        int opt = 1;
        //将监听的端口设置为可以复用的
        if(setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)) < 0)
        {
            perror("setsockopt:");
        }
        
        //socket地址结构
        bzero(&serveraddr,sizeof(serveraddr));
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
        serveraddr.sin_port = htons(s_port);
    
        //绑定
        int nbind = bind(listen_fd,(struct sockaddr*)&serveraddr,sizeof(struct sockaddr_in));
        if(nbind == -1)
        {
            perror("bind:");
            return -1;
        }
    
        //监听
        int nlisten = listen(listen_fd,backlog);
        if(nlisten < 0)
        {
            perror("listen:");
            return -1;
        }
    
        //初始化存放客户端通信描述符的数组
        int i = 0;
        for(;i < FD_SETSIZE;i++)
        {
            clientfds[i] = -1;
        }
        
        //清理描述符集合
        FD_ZERO(&allset);
        
        //将监听的socket描述符加入集合
        FD_SET(listen_fd,&allset);
        max_fd = listen_fd;
        printf("服务器正在监听端口%d......\n",s_port);
        
        while(1)
        {
            rset = allset;
            //等待select事件发生
            nselect = select(max_fd+1,&rset,NULL,NULL,NULL);
            if(nselect < 0)
            {
                perror("select:");
                return -1;
            }
            
            //处理客户端的连接
            if(FD_ISSET(listen_fd,&rset))        //检测监听的描述符是否存在于描述符集合中
            {
                client_fd = accept(listen_fd,(struct sockaddr*)&clientaddr,&socketlength);
                if(client_fd < 0)
                {
                    perror("accept:");
                    continue;
                }
                sprintf(buffer,"接受来自%s:%d\n",inet_ntoa(clientaddr.sin_addr),clientaddr.sin_port);
                printf(buffer);
    
                //将客户端的socket描述符加入数组中
                for(i = 0;i < FD_SETSIZE;i++)
                {
                    if(clientfds[i] == -1)
                    {
                        clientfds[i] = client_fd;
                        break;
                    }
                }
                //如果达到了最大连接数
                if(i == FD_SETSIZE)
                {
                    printf("已达到最大连接数!\n");
                    close(client_fd);
                }
    
                if(client_fd > max_fd)
                {
                    max_fd = client_fd;
                }
                    
                //将socket加入集合中
                 FD_SET(client_fd,&allset);
                if(--nselect <= 0)
                {
                    continue;
                }
            }
        //处理客户端收据的收发
        handle(clientfds,max_fd,&rset,&allset);
        }
        return 0;
    }
    

    Select_Client.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/select.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    
    #define max(a,b) ((a) > (b) ? (a) : (b))
    
    void handle(int client_fd)
    {
        FILE* fp = stdin;
        //发送队列和接收队列,设置为1024
        char sendqueue[1024],receivequeue[1024];
        fd_set rset;
        FD_ZERO(&rset);
        int max_fd = max(fileno(fp),client_fd) + 1;
        for(;;)
        {
            FD_SET(fileno(fp),&rset);
            FD_SET(client_fd,&rset);
    
            int nselect = select(max_fd,&rset,NULL,NULL,NULL);
            if(nselect == -1)
            {
                perror("select:");
                continue;
            }
            if(FD_ISSET(client_fd,&rset))
            {
                //接收到服务器的响应
                int nread = read(client_fd,receivequeue,1024);
                if(nread == 0)
                {
                    printf("服务器关闭了连接!\n");
                    break;
                }
                else if(nread == -1)
                {
                    perror("read:");
                    break;
                }
                else
                {
                    write(STDOUT_FILENO,receivequeue,nread);
                }
            }
            if(FD_ISSET(fileno(fp),&rset))
            {
                //标准输入可读
                if(fgets(sendqueue,1024,fp) == NULL)
                {
                    break;
                }
                else
                {
                    write(client_fd,sendqueue,strlen(sendqueue));
                }
            }
        }
    }
    
    int main()
    {
        char* s_iaddr = "127.0.0.1";
        int s_port = 6888;
        int client_fd;
        char buffer[1024];
        struct sockaddr_in serveraddr;
    
        client_fd = socket(AF_INET,SOCK_STREAM,0);
        bzero(&serveraddr,sizeof(serveraddr));
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_port = htons(s_port);
        inet_pton(AF_INET,s_iaddr,&serveraddr.sin_addr);
    
        //建立连接
        if(connect(client_fd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)) < 0)
        {
            perror("connect:");
            return -1;
        }
        printf("---回射服务器的客户端---\n");
        handle(client_fd);
        close(client_fd);
        printf("exit\n");
        exit(0);
    }
    

    运行结果:

    展开全文
  • Linux下的IO复用机制

    2019-06-17 20:50:56
    所谓IO复用(IO multiplexing),就是使用一个线程来同时管理多个IO流。我们“复用”的是线程。 为了实现上述功能,我们要先构造一张我们感兴趣的描述符列表,然后调用一个函数,直到这些描述符中的一个已经准备好...

    所谓IO复用(IO multiplexing),就是使用一个线程来同时管理多个IO流。我们“复用”的是线程。
    为了实现上述功能,我们要先构造一张我们感兴趣的描述符列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行IO时,该函数才返回。Linux提供了select()、poll()、epoll()三个函数来供我们进行IO复用。

    (UNP上给出的I/O复用模型)

    select

    该函数允许进程指示内核等待多个事件中的任何一个发生,并且在有一个或多个事件发生或经历过一段指定的时间后才唤醒他。

    #include <sys/select.h>
    #include <sys/time.h>
    
    int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
    //返回:若有描述符就绪则返回已就绪的描述符个数,若超时则返回0,若出错则返回-1
    

    参数
    (1) timeout:等待时间,timeval结构用于指定这段时间的秒数和微秒数,struct timeval { long tv_sec; long tv_usec; };
    这个参数有三种可能:1)永远等待下去(仅在有一个描述符准备好IO时才返回):把该参数置为NULL;2)等待一段固定时间(仅在有一个描述符准备好IO时才返回,但不超过该参数所指定的时间):在timeval结构中指定秒数和微秒数;3)不等待 (检查描述符后立即返回,这称为轮询):该参数必须指向一个timeval结构,其中的定时器值设为0;
    => 前两种情形的等待通常会被进程在等待期间捕获的信号中断,并调用相应的信号处理函数。
    => 尽管timeval结构允许我们指定一个微秒级的分辨率,但内核支持的分辨率往往粗糙的多,Linux内核会把超时值向上舍入成10ms的整数倍。
    (2)readset,writeset,exceptset:指定我们要让内核测试读、写和异常条件的描述符。
    fd_set数据类型用来表示描述符集,通常是一个整数数组,其中每个整数的每一位对应一个描述符。例如:采用32位整数,那么该数组的第一个元素对应于描述符0-31,第二个元素对应与描述符32-63,依次类推。
    有配套的四个宏帮助我们处理fd_set数据类型:

    #include <sys/select.h>
    void FD_ZERO(fd_set *fdset);             //清除fdset中的每一位,常用于文件描述符集设置前对其进行初始化
    void FD_SET(int fd, fd_set *fdset);      //打开fdset中fd描述符对应位
    void FD_CLR(int fd, fd_set *fdset);      //关闭fdset中fd描述符对应位
    int  FD_ISSET(int fd, fd_set *fdset);    //select函数返回后,检测fdset中对应描述符是否准备好,当描述符fd在描述符集fdset中则返回非零值,否则返回零
    

    => 描述符集的初始化非常重要,因为作为自动变量分配的一个描述符集如果没有初始化,那么可能发生不可预料的后果。
    => select()函数的中间三个参数readset,writeset,execptset中,如果我们对某一个条件不感兴趣,就可以把它置为空指针。事实上,如果这三个指针都为空,我们就有了一个比Unix的sleep()函数更精确的定时器(sleep睡眠以秒为最小单位)。
    => select()函数修改由指针readset,writeset,exceptset指向的描述符集,这三个参数都是值-结果参数。调用select()函数时,我们指定所关心的描述符的值,select()函数返回时,结果将显示哪些描述符已就绪。而且描述符集内任何与未就绪描述符对应的位在返回时均被清0,因此,每次重新调用select()函数时,我们都需要再次把描述符集中所有关心的位置为1。
    (3)maxfdp1:指定待测描述符的个数,它的值是待测试的最大描述符值加1(因此描述符是从0开始的),描述符0,1,2 … maxfd1-1均将被测试。例如:打开描述符1、4、5,则其maxfd1的值为6。
    => 设置这个参数是出于效率方面的考虑,内核不会复制描述符集中超过maxfdp1的部分,从而减少每次测试的描述符个数,提高效率。
    => select的最大描述符数:头文件<sys/select.h>中定义的FD_SETSIZE常值是数据类型fd_set中描述符的总数。该值通常为1024,表示select最多可同时监听1024个fd,我们可以通过修改该头文件然后重新编译内核来扩大这个数目(使用起来很不方便)。

    poll

    poll提供的功能与select类似,但它避免了select描述符有限的问题。

    #include <poll.h>
    
    int poll(struct pollfd fdarray[], unsigned long nfds, int timeout);
    //返回:若有描述符就绪则返回已就绪描述符个数,若超时则返回0,若出错则返回-1
    

    参数
    (1)fdarray:该参数是指向一个结构数组第一个元素的指针。每个数组元素都是一个pollfd结构,用于指定测试某个给定描述符fd的条件。

    struct pollfd {
        int fd;
        short events;
        short revents;
    };
    

    => 如果我们不关心某个特定的描述符,可以把与他对应的pollfd结构的fd成员设置成一个负值。poll()函数将忽略这样的pollfd结构的events成员,并在返回时将它的revents成员的值置为0
    => 要测试的条件由events成员指定,函数在相应的revents成员中返回该描述符的状态。(每个描述符都有两个变量,一个为调用值,另一个为返回值,从而避免使用值-结果参数)
    events和revents的取值是一些指定的常值。(前四行是可读性,中间三行是可写性,最后三行是异常条件)

    (图片摘录自UNP)
    (2)nfds:结构数组中元素的个数(采用unsigened long类型似乎过于大了,新标准中为该参数定义了新的nfds_t数据类型)
    (3)timeout:指定poll()函数返回前等待多长时间。单位是毫秒。
    => timeout的取值:INFTIM(永远等待)、0(立即返回,不阻塞进程)、>0(等待指定的毫秒数)

    补充:select()与poll()的区别
    select()的fd_set是一个位掩码(bit mask),因此fd_set有固定的长度,该长度在内核编译时由FD_SETSIZE指定。
    然而,使用者在调用poll()时需要自定义pollfd结构体数组并指定数组的大小,因此poll()函数的描述符个数没有限制。

    epoll

    新的Linux内核提供了epoll机制,相对于select和poll,epoll最大的好处在于它不会随着要监控的fd数目增长而降低效率。(在内核中select/poll的实现是采用轮询的方法检测就绪事件,轮询的fd数目越多,自然耗时越多,效率也就越低)
    不同于select和poll,epoll提供了3个接口函数:

    #include <sys/epoll.h>
    
    int epoll_create(int size);
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    

    函数说明:
    (1)epoll_create()
    功能:生成epoll专用的文件描述符epfd,他其实是在内核申请了一块空间,用来存放你关注的socket fd上所发生的事件。
    参数:size 表示生成的epoll fd上能关注的最大socket fd数(注意:它与select()第一个参数maxfd1不同,它不是待测最大描述符值加一)
    返回值:文件描述符epfd(注意:epfd会占用一个fd值,所以在使用完epoll()后,必须调用close()关闭,否则可能导致fd被耗尽)
    (2)epoll_ctl()
    功能:epoll的事件注册函数,用于控制epoll文件描述符上的事件,可以注册事件、修改事件或删除事件。(注意:不同于select()是在监听事件时才告诉内核要监听什么类型的事件,epoll要先注册想监听的事件类型)
    参数
    epfd epoll_create()的返回值
    op 指定操作类型,用三个宏来表示:
    EPOLL_CTL_ADD:注册新的fd到epfd中
    EPOLL_CTL_MOD:修改已注册的fd上的监听事件
    EPOLL_CTL_DEL:从epfd中删除一个fd
    fd 要操作的socket文件描述符
    event 要监听的事件,它是epoll_event结构指针类型,epoll_event结构如下:

    struct epoll_event {
        _uint_32_t events;    //epoll事件
        epoll_data_t data;    //用户数据
    };
    
    typedef union epoll_data {
        void *ptr;     //指向与fd相关的用户数据
        int fd;        //指定事件所从属的目标文件描述符
        _uint32_t u32;
        _uint64_t u64;
    } epoll_data_t;
    

    结构体成员events表示事件类型,取值与poll()中events的取值基本相同。两个额外事件:EPOLLET和EPOLLONESHOT,它们是高效运作的关键
    EPOLLET:将EPOLL设为边沿触发方式(Edge Trigger),这是相对于水平触发(Level Trigger)来说的。
    EPOLLONESHOT:只监听一次事件,当监听完这个事件后,如果还要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
    返回值:成功返回0,失败返回-1

    补充:epoll事件有两种模型
    Edge Trigger(ET) 高速工作方式,错误率较大,只支持no_block socket,在这种方式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你,然后它会假设你已经知道文件描述符已就绪,并且不会为那个描述符发送更多的通知,直到你做了某些操作导致那个文件描述符变为不再就绪状态。
    Level Trigger(LT) 默认工作方式,错误率较小,支持block socket和no_block socket,在这种方式下,内核告诉你一个文件描述符就绪了,然后你就可以对这个就绪的fd进行IO操作。如果你不做任何操作,内核还是会继续通知你。因此,这种方式出错的可能性较小。

    (3)epoll_wait()
    功能:等待事件触发,如果在规定时间内没有事件触发,则超时。类似于select()调用。
    参数
    events 检测到的事件 (参数events用来从内核得到事件集合,epoll_wait()函数将所有就绪的事件从内核事件表复制到本参数所指向的数组中)
    maxevents 指定最多监听多少个事件(告知内核events有多大)
    timeout 指定epoll的超时时间(当timeout为-1,epoll_wait调用将永远阻塞,直到某个事件发生;timeout为0,epoll_wait调用将立即返回)
    返回值:成功返回就绪文件描述符的个数,失败返回-1并设置errno

    (图片来源https://www.cnblogs.com/lojunren/p/3856290.html)

    补充:epoll()与select()的区别:

    1. select的可用描述符数目受限。在linux/posix_types.h头文件中有这样的声明:#define __FD_SETSIZE 1024 表示select最多可用1024个fd。而epoll没有这个限制,它的限制是最大打开文件描述符个数。
    2. epoll的最大好处是不会随着fd数目的增长而降低效率,在select中采用轮询处理,其中的数据结构类似一个数组。而epoll则是维护一个队列,epoll只会对活跃的socket进行操作,这是因为在内核实现中epoll是根据每个fd上面的callback()函数实现的,只有活跃的socket才会主动去调用callback()函数(把相应描述符加入队列),不活跃的socket则不会调用。注意,如果大部分的socket都是活跃的,则epoll效率不一定比select高。
    3. epoll使用mmap加速内核与用户空间的消息传递。epoll将用户空间的一块地址和内核空间的一块地址映射到相同的一块物理内存地址,使得这块物理内存对内核和用户均可见,减少用户态和内核态之间的数据交换,提高效率。

    参考:
    《UNIX网络编程卷1:套接字联网API》 第3版
    Linux epoll机制 - HenryLiuY - 博客园 https://www.cnblogs.com/henryliublog/p/9645562.html
    Linux下的I/O复用与epoll详解 - junren - 博客园 https://www.cnblogs.com/lojunren/p/3856290.html

    展开全文
  • 转载:IO复用\阻塞IO\非阻塞IO\同步IO\异步IO 一、 什么是IO复用? 它是内核提供的一种同时监控多个文件描述符状态改变的一种能力;例如当进程需要操作多个IO相关描述符时(例如服务器程序要同时查看监听socket和...
  • 常见的IO复用场景

    2020-12-06 15:03:30
    在网络程序中比较常见的要使用到IO复用的场景如下: 客户端程序要同时处理多个socket,如非阻塞connect技术 客户端程序要同时处理用户输入和网络连接。比如聊天室程序。 TCP服务器要同时处理监听socket和连接socket...
  • IO复用的总结及一些问题

    千次阅读 2018-07-04 12:33:18
    在前面的文章中,分别介绍了常用的三种实现IO多路复用的函数:select、poll、epoll今天对主要是对这三个函数的总结,以及对一些io复用的问题的总结。 总结 系统调用 select poll epoll 事件集合 ...
  • 三种io复用的总结

    2019-08-09 19:46:07
    io复用的作用是能使程序同时监听多个文件描述符; 一般在下面几种情况下要用到io复用: 客户端程序要同时处理多个socket。 客户端程序要同时处理用户输人和网络连接。 TCP服务器要同时处理监听socket和连接...
  • 一般这种情况出现,可首先排查 IO 引脚是否被复用。 在 RK 的 Android 平台,默认有包含 io 工具(源码位置:external\io),linux 系统平台 默认没有此源码,如果想要使用,可以将 Android 平台此源码打包过去...
  • 多路复用指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll...就是说多路IO复用起到一个监视的效果,就绪后,Redis直接执行,不需要等待 串行与采用多线程+锁(Memcached)与单线程+...
  • IO复用select+多线程.rar

    2021-04-27 21:52:40
    TCP网络编程IO复用服务器例子,使用select+多线程。运行方法,linux下,先执行server,再执行client,客户端即可获得服务端的当前时间。
  • 当数据达到socket缓冲区时,可能会因为一些原因被内核丢弃,比如,校验和错误,这时IO复用唤醒线程对socket读并不能读到数据,如果是阻塞IO就会被阻塞住。 这个达到缓冲区的数据不一定被丢弃,但是也有可能被别人取...
  • IO复用的三种模型

    千次阅读 2018-08-22 19:43:36
    I/O复用(select,poll,epoll) 信号驱动式(SIGIO):内核在描述符就绪时发送SIGIO通知进程 异步I/O(POSIX的aio_系列函数):不会阻塞。内核完成后整个操作,通知进程。 同步I/O:真正的IO操作进程会阻塞,直到I/...
  • 2.4 IO复用模型 2.5 小结 3 异步IO模型 4 五种IO模型对比 1 什么是I/O 程序是由数据+指令构成的,运行程序的过程可以分成下面这几步: 1.将代码加载到内存中,逐条运行内存中的代码 2.在运行代码的过程中...
  • 文章目录 select API 文件描述符就绪条件 程序示例 程序示例一:读取键盘(标准输入) 程序示例二:改进的多客户/服务器 IO复用技术使得程序能够同时监听多个文件描述符,这对提高程序的性能至关重要。 Linux下实现IO...
  • Netty的非阻塞IO是基于IO复用模型来实现的, Netty 的 IO 线程 NioEventLoop 由于聚合了多路复用器 Selector,可以同时并发处理成百上千个客户端连接 。 一个线程在空闲时,可以做IO的读取操作,它不需要时时刻刻...
  • 1、BIO(Blocking IO) BIO,同步阻塞式IO,简单理解:一个线程处理一个连接,发起和处理IO请求都是同步的,进程会一直阻塞,直到数据拷贝完成 2、NIO(new IO)  NIO,同步非阻塞IO,简单理解:一个线程处理...
  • 主要介绍了Java IO复用的相关知识,非常不错,具有参考借鉴价值,需要的的朋友参考下吧
  • IO复用以及五种IO模型的理解

    千次阅读 2018-07-19 14:44:57
    阻塞IO 当系统调用没有拿到想要的数据时,它就会一直在等待,不会做其他事情,直到拿到了想要的数据或者资源,它才会返回调用成功的结果。 非阻塞IO 当系统调用产生,但是对于想要的数据来说它还没有被内核处理好...
  • IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合: (1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。 (2)当一个客户...
  • 另外多路IO复用技术类似于拨开关,多个任务通过拨开关的方式共用一条线程,哪个任务需要了开关就拨到哪个任务,避免了CPU在不同的线程中切换,提高效率。 I/O复用模型 1.select/poll 老李去火车站买票,委托黄牛...
  • IO复用 信号驱动式I/O 异步I/O select, poll, epoll  都是IO多路复用的机制。IO多路复用就是通过一种机制,一个进程可以监控多个描述符, 一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序...
  • ps:最近在学习nginx,为防止一些定义混淆或是遗忘,也是为了方便未来应对相关业务场景时的翻阅,于是...1、IO多路复用 什么是IO多路复用呢?我们先假设这样一个场景,假设你现在有一个网站,某一天有一大群人突...
  • 在一个IO操作过程中,以read为例,会涉及到两个过程: 1.等待数据准备好; 2.将数据从内核拷贝到进程中 这两个阶段是否发生阻塞,将产生不同的效果。 堵塞IO: 进程在请求read阻塞io的数据时,操作需要彻底完成后才...
  • io多路复用解析

    2016-12-07 11:04:25
    io多路复用解析
  • select(服务器代码)//同步非阻塞io(模型) 阻塞仅仅在selete上 缺点每次使用都需要吧外部的fds拷贝到内核中浪费时间并且不知道具体是哪个fd有数据只知道有几个查找浪费效率(服务器代码) #include #include #...
  • 《Linux网络编程 | IO复用 : select系统调用详解(实现改进的多客户/服务器)》 《Linux网络编程 | IO复用 : poll系统调用详解》 《Linux网络编程 | IO复用 : epoll系列系统调用详解》 这3组系统调用都能同时监听多...
  • MC9328MX1部分 第32章 GPIO和IO复用;GPIO和I/O复用;GPIO和I/O复用;GPIO和I/O复用;GPIO和I/O复用;6;7;8;9;10;11;12;13;14;15;16;17;18;19;20;21;22;23;24;25;26;27

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 143,059
精华内容 57,223
关键字:

io复用