Unix系統編程(6) - I/O多路複用之select

1. I/O多路複用基本思路

I/O多路複用就是讓應用程序可以同時對多個I/O端口進行監控以判斷其上的操作是否可以進行,達到時間複用的目的。由於I/O多路複用是在單一進程的上下文中的,因此每個邏輯流程都能訪問該進程的全部地址空間,所以開銷比多進程低得多。由於I/O多路複用都是在單一進程中進行的,所以不會出現多線程中的線程不安全的問題。

2. select模型

在man page中給出的select函數原型:

       /* According to POSIX.1-2001 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

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

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

       #include <sys/select.h>

       int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);

函數准許進程指示內核等待多個事件中的任何一個發生,並只在有一個或多個事件發生或經歷一段指定的時間後才喚醒。man page中給出了一個較新的函數pselect,這個函數僅僅是多了一個sigmask參數。sigmask參數指定了在執行pselect函數時屏蔽的信號集合。
select()函數參數:

int nfds,        //監控的文件描述符集裏最大文件描述符加1
fd_set *readfds, //監控有讀數據到達文件描述符集合
fd_set *writefds,//監控有寫數據到達文件描述符集合
fd_set *exceptfds,//監控異常發生達文件描述符集合,如帶外數據到達異常,傳入傳出參數
struct timeval *timeout //定時阻塞監控時間,3種情況
/*
1.NULL,永遠等下去
2.設置timeval,等待固定時間
3.設置timeval裏時間均爲0,檢查描述字後立即返回,輪詢
*/

另外的三個函數功能比較簡單:

int FD_ISSET(int fd, fd_set *set); 測試文件描述符集合裏fd是否置1
void FD_SET(int fd, fd_set *set); 把文件描述符集合裏fd位置1
void FD_ZERO(fd_set *set); 把文件描述符集合裏所有位清0

3. select模型回射服務器

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/select.h>


#define SERV_PORT 8000
#define MAXLINE 1024

int main()
{
    //maxfd:        打開的最大文件描述符標號
    //listenfd:     監聽描述符
    //confd:        鏈接描述符
    //clientaddrlen 客戶端地址長度
    //sockfd:       暫存量
    int maxfd, listenfd, confd, clientaddrlen, sockfd, n, i = 0;
    struct sockaddr_in serveraddr, clientaddr;                      //服務器端地址,客戶端地址
    char str[INET_ADDRSTRLEN];                                      //存放IP地址,點分十進制表示
    fd_set rset, allset;                                            //select 使用
    char buf[MAXLINE];                                              //傳輸數據
    int nReady, client[FD_SETSIZE], maxi;                           //               

    //1. 創建一個socket
    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    //2. 綁定一個端口
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(SERV_PORT);
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));

    //3. 設置監聽
    listen(listenfd, 20);

    maxfd = listenfd;
    maxi = 0;

    for(i=0; i<FD_SETSIZE; ++i);                                    //初始化clinet[]
        client[i] = -1;

    FD_ZERO(&allset);                                               //select監控文件描述符集
    FD_SET(listenfd, &allset);

    for(;;)
    {
        rset = allset;
        nReady = select(maxfd + 1, &rset, NULL, NULL, NULL);

        if(nReady < 0)                                              //select出錯
        {
            perror("select err\n");
            break;
        }

        if(FD_ISSET(listenfd, &rset))                               //新鏈接的客戶端
        {
            clientaddrlen = sizeof(clientaddr);
            confd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);

            printf("ip: %s, port: %d \n", 
                  inet_ntop(AF_INET, &clientaddr.sin_addr, str, sizeof(str)),
                  ntohs(clientaddr.sin_port));

            for(i=0; i<FD_SETSIZE; ++i)
            {
                if(client[i] < 0)
                {
                    client[i] = confd;
                    break;
                }
            }

            if(i == FD_SETSIZE)
            {
                fputs("limited\n", stderr);
                exit(1);
            }

            FD_SET(confd, &allset);

            if(confd > maxfd) maxfd = confd;
            if(i > maxi) maxi = i;
            if(--nReady == 0) continue;
        }

        for(i=0; i<=maxi; ++i)                                      //遍歷看哪個客戶端有數據就緒
        {
            if((sockfd = client[i]) < 0) continue;
            if(FD_ISSET(sockfd, &rset))
            {
                if((n = read(sockfd, buf, MAXLINE)) == 0)
                {
                    close(sockfd);
                    FD_CLR(sockfd, &allset);
                    client[i]=0;
                }
                else
                    write(sockfd, buf, n);
                if(--nReady == 0)
                    break;
            }
        }
    }
    close(listenfd);
    return 0;
}

測試用客戶端代碼,可使用上一篇博文中的客戶端。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章