• 系统编程--高级IO


    1.非阻塞I/O
    非阻塞I/O使我们可以调用不会永远阻塞的I/O操作,例如open,read和write。如果这种操作不能完成,则立即出错返回,表示该操作如继续执行将继续阻塞下去。对于一个给定的描述符有两种方法对其指定非阻塞I / O:
    (1) 如果是调用open以获得该描述符,则可指定O_NONBLOCK标志
    (2) 对于已经打开的一个描述符,则可调用fcntl打开O)NONBLOCK文件状态标志
     
     
    2.I/O多路转接
    select
    select函数使我们可以执行I/O多路转接。传向select的参数告诉内核:我们所关注的描述符;对于每个描述符我们所关心的状态,以及我们愿意等待的时间。从select返回时,内核告诉我们:以准备好的描述符的数量,对于读、写或异常这三个状态中的每一个,那些描述符已经准备好。
    #include <sys/select.h>
    int select (int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, 
                fd_set *restrict exceptfds, struct timeval *restrict tvptr);
    //返回准备好的描述符的计数,超时返回0,错误返回-1。
      第一个参数maxfdp1的意思是“最大描述符加1”。也可将第一个参数设置为FD_SETSIZE,这是<sys/select.h>中的一个常数,它说明了最大的描述符数(经常是1024)。如果将第三个参数设置为我们所关注的最大描述符编号值加一,内核就只需在此范围内寻找打开的位,而不必在三个描述符集中的数百位内搜索。中间的三个参数readfds、writefds和exceptfds是指向描述符集的指针。这三个描述符集说明了我们关心的可读(readfds)、可写(writefd)或处于异常条件(wxcepfds)的各个描述符。每个描述符集存放在一个fd_set数据类型中。这种结构相当于一个描述符的数组,它为每个可能的描述符设置1位。
    select的中间三个参数中的任意一个或全部都可以是空指针,这表示对相应状态不关系。如果所有三个指针都是空指针,则select提供了较sleep更精确的计时器。其等待时间可以小于1秒。
    tvptr指定最后等待的时间,它的结构是:
    struct timeval {
      long tv_sec;  /* seconds */
      long tv_usec;  /* and microseconds */
    };
    a. tvptr==NULL:永远等待。如果捕捉到一个信号则中断此无限等待。当所指定的描述符中的一个已经准备好或捕捉到一个信号则返回。如果捕捉到一个信号,则select返回-1,errno设置为EINTR.
    b. tvptr->tv_sec==0&&tvptr_usec==0 完全不等待。测试所有的描述符并立即返回。这是得到多个描述符的状态而不阻塞select函数的轮询方法。
    c. tvptr->tv_sec!=0||tvptr_usec!=0 等待指定的秒数或微秒数。当指定的描述符之一已准备好,或当指定的时间值已超过时立即返回。如果在超时还没有一个描述符准备好,则返回值是0
     
    select有三个可能的返回值。
    a. 返回值-1表示出错。这是可能发生的,例如在所指定的描述符都没有准备好时捕捉到一个信号。
    b. 返回值0表示没有描述符准备好。若指定的描述符都没有准备好,而且指定的时间已经超过,则发生这种情况。
    c. 返回一个正值说明了已经准备好的描述符数,在这种情况下,三个描述符集中仍旧打开的位是对应于已准备好的描述符位。
     
    对fgset数据类型可以进行的处理是: (a)分配一个这种类型的变量, (b)将这种类型的一个变量赋与同类型的另一个变量,或(c)对于这种类型的变量使用下列四个宏:
    #include <sys/select.h>
    int FD_ISSET(int fd, fd_set *fdset);
    //如果fd被设置返回非0,否则返回0。
    void FD_CLR(int fd, fd_set *fdset);
    void FD_SET(int fd, fd_set *fdset);
    void FD_ZERO(fd_set *fdset);
    调用FD_ZERO将一个fd_set变量的所有位置为0位。调用FD_SET设置一个fd_set变量的指定位,调用FD_CLR则将一指定位清除。最后,调用FD_ISSET测试一指定位是否设置
     
    poll函数
    poll函数类似于select,但是其调用形式则有所不同
    include <poll.h>
    int poll(struct poolfd fdarray[], nfds_t nfds, int timeout);
    //返回准备好的描述符的计数,到时返回0,错误返回-1。
    与select不同,poll不是为每个条件构造一个描述符集,而是构造一个pollfd结构数组,每个数组元素指定一个描述符编号以及对其所关心的条件。
    struct pollfd {
      int  fd;             /* file descriptor to check, or <0 to ignore */
      short events;        /* event of interest on fd */
      short  revents;      /* event that occurred on fd */
    };
    在fdarray数组里的元素数由nfds指定。
    poll的最后参数指明我们想要等待多久。和select一样,有三种情况
    a.timeout == -1:永远等待     b.timeout == 0:不等待     c.timeout > 0
     
    常量 说明
    POLLIN 普通或优先级带数据可读
    POLLRDNORM 普通数据可读
    POLLRDBAND 优先级带数据可读
    POLLPRI 高优先级数据可读
    POLLOUT 普通数据可写
    POLLWRNORM 普通数据可写
    POLLWRBAND 优先级带数据可写
    POLLERR 发生错误
    POLLHUP 发生挂起
    POLLNVAL 描述字不是一个打开的文件
     
    epoll
    1. int epoll_create(int size);
    创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
     
    2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
    第一个参数是epoll_create()的返回值。
    第二个参数表示动作,用三个宏来表示:
      EPOLL_CTL_ADD:注册新的fd到epfd中;
      EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
      EPOLL_CTL_DEL:从epfd中删除一个fd;
    第三个参数是需要监听的fd。
    第四个参数是
    告诉内核需要监听什么事,struct epoll_event结构如下
    //保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)
    typedef union epoll_data {  
        void *ptr;  
        int fd;  
        __uint32_t u32;  
        __uint64_t u64;  
    } epoll_data_t;  
     //感兴趣的事件和被触发的事件  
    struct epoll_event {  
        __uint32_t events; /* Epoll events */  
        epoll_data_t data; /* User data variable */  
    };  
    events可以是以下几个宏的集合:
    EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
    EPOLLOUT:表示对应的文件描述符可以写;
    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
    EPOLLERR:表示对应的文件描述符发生错误;
    EPOLLHUP:表示对应的文件描述符被挂断;
    EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
    EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
     
    3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。
     
     
    效率:select <poll< epoll
    主要却别在于内核实现上,select采用数组形式进行用户空间与内核空间的数组交互;poll在内核使用链表队列形式对监听的事件进行处理;epoll则是在内核实现了一个微型的文件系统,负责事件的管理。有兴趣的可以研究下,主要集中在select.c这个文件中。
     
    3.存储映射I/O
      存储映射I/O使一个磁盘文件与存储空间中的一个缓存相映射。于是当从缓存中取数据,就相当于读文件中的相应字节。与其类似,将数据存入缓存,则相应字节就自动地写入文件。为了使用这种功能,应首先告诉内核将一个给定的文件映射到一个存储区域中。这是由mmap函数实现的
    #include <sys/mman.h>
    void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);
    //成功返回被映射区域的开始地址,错误返回MAP_FAILED。
    addr参数用于指定映射存储区的起始地址,len是映射的字节数,filedes指定要被映射文件的描述符,off是要映射字节在文件中的起始位移量,prot参数指定映射区域的保护,此函数的返回地址是:该映射区的起始地址。
    prot参数取值:
    调用mprotect来改变一个已有映射上的权限。
    #include <sys/mman.h>
    int mprotect(void *addr, size_t len, int prot);
    //成功返回0,错误返回-1。
    调用msync来冲洗对被映射文件的改变
    #include <sys/mman.h>
    int msync(void *addr, size_t len, int flags);
    //成功返回0,错误返回-1。
    调用了munmap之后,存储映射区就被自动去除。
    #include <sys/mman.h>
    int munmap(caddr_t addr, size_t len);
    //成功返回0,错误返回-1
  • 相关阅读:
    重定向请求
    json处理
    post请求
    get请求
    提交cookie登录
    进击的Python【第三章】:Python基础(三)
    进击的Python【第二章】:Python基础(二)
    进击的Python【第一章】:Python背景初探与Python基础(一)
    java 内存分析
    java--循环练习
  • 原文地址:https://www.cnblogs.com/tla001/p/6551493.html
Copyright © 2020-2023  润新知