• Socket I/O multiplexing


    网络应用中I/O多路复用的应用场景

    1. 客户端处理多个描述符(通常是交替输入+网络套接字,可以及时获取服务端发送的FIN包)
    2. 客户端同时处理多个套接字
    3. TCP服务端处理监听套接字和连接套接字
    4. 服务端同时处理TCP和UDP
    5. 服务端处理多个服务以及多种协议

    I/O模型

    阻塞I/O

    最常见的I/O模型。默认情况下,socket是阻塞的。使用UDP数据包套接字为例,在数据就绪之前,recvfrom一直处于等待状态。
    a

    非阻塞I/O

    当I/O操作不能完成时,内核不会让进程睡眠,而是返回错误。进程采用轮询方式对非阻塞描述符循环采取操作。
    a

    I/O多路复用

    使用select/poll/epoll系统调用,当描述符准备就绪时,系统调用返回,并执行I/O操作。
    a

    信号驱动I/O

    进程设置了信号处理程序后立即返回,当描述符就绪时,可以执行I/O操作时,内核发送SIGIO信号通知进程。
    a

    异步I/O

    POSIX标准中定义了异步I/O。通常的做法是通知内核开始执行I/O操作,全部完成后发送通知。aio_或lio_设置通知的方式。
    a
    五种模型的比较
    a
    同步I/O:进程在I/O完成前会被阻塞
    异步I/O:进程不会阻塞
    因此,前四种模型属于同步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);
    

    最后一个参数timeout指定了等待时间。总共有三种方式,永久等待(直到有描述符就绪);等待(不超过)一段时间;不等待。如果进程捕捉到信号,那么前两种等待会被信号处理程序中断,会返回EINTR。有些系统的select调用返回时,会改变timeout参数的值,表示剩余的等待时间,为了可移植性,POSIX将timeout设为const。当select被中断时,为了获取剩余等待时间,只能在调用前和返回后两次获取系统时间,但是时间可能被更改。
    中间的三个参数readset, writeset, exceptset指定了描述符集合,分别对read,write,excpetion进行测试。select调用修改了三个描述符集合,那些已经就绪的描述符在描述符集合中被置位。每次调用select前都要重新设置描述符集合。第一个缺点是为了检测哪些描述符已经就绪,每次需要遍历fd_set。

    void FD_ZERO(fd_set *fdset); /* clear all bits in fdset */ 
    void FD_SET(int fd, fd_set *fdset); /* turn on the bit for fd in fdset */
    void FD_CLR(int fd, fd_set *fdset); /* turn off the bit for fd in fdset */
    int FD_ISSET(int fd, fd_set *fdset); /* is the bit for fd on in fdset ? */
    

    maxfdp1参数指定了测试描述符的数量,它的值是被测试的最大描述符的值加1,相当于可接受从0开始的描述符个数。FD_SETSIZE是fd_set结构中描述符的数量,一般为1024,即select调用能够测试的最大描述符的值是1024,这也是select调用的第二个缺点。普通应用程序不会使用如此多数量的描述符,maxfdp1这个参数的存在可以使内核不从进程拷贝不必要的那部分fd_set,相当于提高效率。每次调用都需要拷贝描述符集合到内核空间是select的第三个缺点。
    函数返回值表示所有被置位的数量,如果一个就绪的描述符在超过一个集合中被关注,计相应的次数而不是1次。0代表没有描述符就绪,-1代表错误,通常是被捕获的信号中断。

    描述符就绪条件

    1. 读就绪:
      • 套接字接收缓冲区中数据字节数在低水位线(默认为1)之上。此时read返回值大于0
      • 读方向连接关闭(如TCP收到FIN包)。此时read返回0
      • 对于监听套接字,已完成的连接数非0。此时accept不会阻塞
      • 套接字发生错误。read返回-1
    2. 写就绪:
      • 套接字发送缓冲区可用空间大小在低水位线之上(默认2048)并且套接字已经建立连接(TCP)或不需要连接(UDP)
      • 写方向套接字关闭,write操作会产生SIGPIPE信号
      • 非阻塞的connect完成连接或失败
      • 套接字产生错误,write返回-1
      • 当套接字产生错误时,同时标记为可读/可写
    3. 套接字异常:带外数据

    shutdown()

    客户端发送FIN包仅仅表示不会再发送数据,而读数据可能还没结束。所以需要shutsown函数。

    1. close()减少描述符的引用计数,只有引用计数为0才真正关闭套接字。shutdown()直接开始四次挥手
    2. close()关闭套接字的读写方向
    #include <sys/socket.h>
    int shutdown(int sockfd, int howto);
    /* SHUT_RD(0):关闭读方向
     * SHUT_WR(1):关闭写方向
     * SHUT_RDWR(2):关闭两个方向,相当于调用两次shutdown()
     */
    
  • 相关阅读:
    平衡二叉树
    二叉树的深度
    数字在升序数组中出现的次数
    美国最受雇主欢迎的十大编程语言
    重学数据结构(五、串)
    重学数据结构(四、数组和广义表)
    100个高频Spring面试题
    重学数据结构(三、队列)
    重学数据结构(二、栈)
    Java学习之jackson篇
  • 原文地址:https://www.cnblogs.com/zyfgs2012/p/4154240.html
Copyright © 2020-2023  润新知