LunarRoom

数据的IO与复用

字数统计: 1.8k阅读时长: 7 min
2020/04/11 Share

數據的IO和複用


IO函數

  1. recv()接收數據

    1
    ssize_t recv(int s, void *buf, size_t len, int flags);

    flags用於設置接收數據的方式。

    recv()函數的返回值是接收到的字節數,有錯誤發生時,可以查看errno錯誤碼。

  2. send()函數發送數據

    1
    ssize_t send(int s, const void* buf, size_t len, int flags);

    flags的參數含義與recv()一致

  3. readv()接收數據

    readv用於接收多個緩衝區數據。

    1
    2
    3
    4
    5
    6
    #include <sys/uio.h>
    ssize_t readv(int s, const struct iovec* vector, int count);
    struct iovec {
    void* iov_base; //向量的緩衝區地址
    size_t iov_len; //緩衝區大小
    }

    readv()從套接字描述符中接收count個數據塊放到vector中。

  4. writev()寫數據

    1
    ssize_t writev(int fd, const struct iovec* vector, int count);
  5. 各io函數的比較


IO模型

IO的模型有阻塞IO、非阻塞IO、IO複用、信號驅動、異步IO等。

  1. 阻塞IO

    使用這種模型進行接收時,在數據沒有到來之前程序會一直等待。

  2. 非阻塞IO

    若把套接字設為非阻塞的IO,則對每次請求,內核不會阻塞,會立即返回;當沒有數據時,會返回一個錯誤。

  3. IO複用

    使用IO複用模型可以設置等待的時間,時間沒有到等待時間時,內核會阻塞。當超過了指定等待的時間還沒有數據到達時內核會返回。

  4. 信號驅動IO模型

    信號驅動的IO模型在進程開始的時候註冊一個信號處理的回調函數,進程繼續執行,當信號發生時,利用註冊的回調函數將到來的數據用recvfroom()接收。

  5. 異步IO模型

    異步IO模型與信號驅動IO類似,但是是在數據到來後內核先將數據複製到用戶空間再發送信號通知信號處理函數。


select()函數

當我们进行IO复用时,如果有许多请求需要监视,那么就需要创建这么多数量的线程或进程取监视这些请求,但是不知道什么时候请求有数据读取,所以会造成资源的浪费,select()的出现解决了这个问题。

這個函數用於IO複用,它們監視多個文件描述符的集合,判斷是否有符合條件的事件發生。如果有,就分配一个线程去接收,从而做到了有需才有分配,而不是实现分配大量线程进行等待。

select()會先對需要操作的文件描述符進行查詢,查看文件描述符是否可以進行讀、寫操作或者錯誤操作。

1
2
3
4
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
int select(int nfds, fd_set* readfds, fd_set* write_fds, fd_set* exceptfds, struct timeval* timeout);

select()函数监视readfds中的文件是否可读,即判断对此文件进行读操作是否阻塞;监视writefds中的文件是否可写;监视exceptfds中的文件是否发生意外。

fd_set(它比较重要所以先介绍一下)是一组文件描述字(fd)的集合,它用一位来表示一个fd(下面会仔细介绍),对于fd_set类型通过下面四个宏来操作:

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

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

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

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

Tip:select()采用轮询的方式进行监视,十分浪费CPU资源,所以最后不要采用此种方法。


pselect()函数

与select()函数基本一致,除了精度增加了


poll()函數

poll()函數等待某個文件描述符上的某個事件發生

1
2
3
4
5
6
7
#include <poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; //文件描述符
short events; //請求事件
short revents; //返回的事件
}

poll()函數監視在fds數組指明的一組文件描述符上發生的動作,當滿足條件或者超時會退出。


epoll()函数

epoll()旨在取代现有的select()和poll()函数,让拥有大量文件描述符的程序发挥更优秀的性能。epoll通过红黑树遍历其所监视的文件描述符。

  • int epoll_create(int size):在内核中创建一个监视size数量的文件描述符的epoll实例,返回一个epoll文件描述符。当epoll中监视的文件描述符超过了size时,内核会自动扩容。
  • int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event):向 epfd 对应的内核epoll 实例添加、修改或删除对 fd 上事件 event 的监听。op 可以为 EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL 分别对应的是添加新的事件,修改文件描述符上监听的事件类型,从实例上删除一个事件。如果 event 的 events 属性设置了 EPOLLET flag,那么监听该事件的方式是边缘触发。
  • int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout):当 timeout 为 0 时,epoll_wait 永远会立即返回。而 timeout 为 -1 时,epoll_wait 会一直阻塞直到任一已注册的事件变为就绪。当 timeout 为一正整数时,epoll 会阻塞直到计时 timeout 毫秒终了或已注册的事件变为就绪。因为内核调度延迟,阻塞的时间可能会略微超过 timeout 毫秒。

非阻塞編程

  1. 文件描述符控制函數fcntl介紹。

    首先介紹如何將文件設為非阻塞狀態。

    1
    fcntl(s, F_SETFL, O_NONBLOCK);

    fcntl是文件描述符的控制函數,其原型為

    1
    2
    3
    4
    5
    int fcntl(int fd, int cmd);

    int fcntl(int fd, int cmd, long arg);

    int fcntl(int fd, int cmd, struct flock *lock);

    F_SETFL表示改變文件描述符的狀態,O_NONBLOCK是F_SETFL的參數,表示將該文件描述符設為非阻塞狀態。

    同樣還有F_GETFL可以獲取文件的狀態,沒有參數。

    關於非阻塞的一個測試代碼

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <string.h>

    int main()
    {
    char buf[10] = {0};
    int ret;
    int flags;

    //使用非阻塞io
    if(flags = fcntl(STDIN_FILENO, F_GETFL, 0) < 0)
    {
    perror("fcntl");
    return -1;
    }
    flags |= O_NONBLOCK;
    if(fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
    {
    perror("fcntl");
    return -1;
    }

    while(1)
    {
    sleep(2);
    ret = read(STDIN_FILENO, buf, 9);
    if(ret == 0)
    {
    perror("read--no");
    }
    else
    {
    printf("read = %d\n", ret);
    }

    write(STDOUT_FILENO, buf, 10);
    memset(buf, 0, 10);
    }

    return 0;
    }

    在上面的代碼中,當你運行程序後,雖然在while循環內有一個read函數,但是終端並不會停在那裡一直等待輸入,而是每隔2秒就輸出一次”read = x”,當你來不及輸入時就輸出”read = -1”,這說明這個進程是非阻塞的。

CATALOG
  1. 1. 數據的IO和複用
    1. 1.1. IO函數
    2. 1.2. IO模型
    3. 1.3. select()函數
    4. 1.4. poll()函數
    5. 1.5. 非阻塞編程