天天看點

【IO專欄】select函數以及fd_set結構體解析

I/O 多路複用的設計初衷就是解決這樣的場景。我們可以把标準輸入、套接字等都看做 I/O 的一路,多路複用的意思,就是在任何一路 I/O 有“事件”發生的情況下,通知應用程式去處理相應的 I/O 事件,這樣我們的程式就變成了“多面手”,在同一時刻仿佛可以處理多個 I/O 事件。

select 函數就是這樣一種常見的 I/O 多路複用技術,我們将在後面繼續講解其他的多路複用技術。使用 select 函數,通知核心挂起程序,當一個或多個 I/O 事件發生後,控制權返還給應用程式,由應用程式進行 I/O 事件的處理。

select 函數的聲明:

int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

傳回:若有就緒描述符則為其數目,若逾時則為0,若出錯則為-1

在這個函數中,maxfd 表示的是待測試的描述符基數,它的值是待測試的最大描述符加 1。比如現在的 select 待測試的描述符集合是{0,1,5},那麼 maxfd 就是 6,為啥是 6,而不是 5呢? 我會在下面進行解釋。

緊接着的是三個描述符集合,分别是讀描述符集合 readset、寫描述符集合 writeset 和異常描述符集合 exceptset,這三個分别通知核心,在哪些描述符上檢測資料可以讀,可以寫和有異常發生。

以下宏具有重要的作用:

void FD_ZERO(fd_set *fdset);

void FD_SET(int fd, fd_set *fdset);

void FD_CLR(int fd, fd_set *fdset);

int  FD_ISSET(int fd, fd_set *fdset);

FD_ZERO 用來将這個向量的所有元素都設定成 0;

FD_SET 用來把對應套接字 fd 的元素,a[fd]設定成 1;

FD_CLR 用來把對應套接字 fd 的元素,a[fd]設定成 0;

FD_ISSET 對這個向量進行檢測,判斷出對應套接字的元素 a[fd]是 0 還是 1。

具體的可以參考bitmap算法處理,就是拿位來代表具體的資料。大神們都這樣操作。

最後一個參數是 timeval 結構體時間:

struct timeval {

  long   tv_sec;

  long   tv_usec;

};

第一個可能是 倆都 設定成空 (NULL),表示如果沒有 I/O 事件發生,則 select 一直等待下去。

第二個可能是兩個中設定一個非零的值,這個表示等待固定的一段時間後從 select 阻塞調用中傳回,

第三個可能是将 tv_sec 和 tv_usec 都設定成 0,表示根本不等待,檢測完畢立即傳回。這種情況使用得比較少。

直接上示例代碼+注釋

int main(int argc, char **argv) {

    if (argc != 2) {

        error(1, 0, "usage: select01 <IPaddress>");

    }

    int socket_fd = tcp_client(argv[1], SERV_PORT);

    char recv_line[MAXLINE], send_line[MAXLINE];

    int n;

    fd_set readmask;

    fd_set allreads;

    FD_ZERO(&allreads);

    //設定第0位需要檢測

    FD_SET(0, &allreads);

    //設定第socket_fd位需要檢測

    FD_SET(socket_fd, &allreads);

    for (;;) {

         //為readmask指派  莫非是傳說中的 傳值和傳引用 ----?

        readmask = allreads;

       // 開始檢測

        int rc = select(socket_fd + 1, &readmask, NULL, NULL, NULL);

       //如果傳回的結果小于等于0 列印錯誤資訊

        if (rc <= 0) {

            error(1, errno, "select failed");

        }

        //檢測socket_fd位是否有響應的事件發生

        if (FD_ISSET(socket_fd, &readmask)) {

          //處理讀事件

            n = read(socket_fd, recv_line, MAXLINE);

            if (n < 0) {

                error(1, errno, "read error");

            } else if (n == 0) {

                error(1, 0, "server terminated \n");

            }

            recv_line[n] = 0;

            fputs(recv_line, stdout);

            fputs("\n", stdout);

        }

        if (FD_ISSET(STDIN_FILENO, &readmask)) {

            if (fgets(send_line, MAXLINE, stdin) != NULL) {

                int i = strlen(send_line);

                if (send_line[i - 1] == '\n') {

                    send_line[i - 1] = 0;

                }

                printf("now sending %s\n", send_line);

                size_t rt = write(socket_fd, send_line, strlen(send_line));

                if (rt < 0) {

                    error(1, errno, "write failed ");

                }

                printf("send bytes: %zu \n", rt);

            }

        }

    }

}

注意事項:

循環中://為readmask指派

        readmask = allreads;

       每次循環完畢後都要重新指派,因為集合會根據具體的事件來改變。

  參考資料《網絡程式設計實戰》-盛嚴敏

繼續閱讀