linux select用法


select 是linux i/o 復用技術之一

man 2 select

       #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);

nfds是監聽文件描述符的總數。它通常被設置為select監聽的所有文件描述符的最大值加1.

readfds, writefds, exceptfds指向可讀、可寫、異常等事件對應的文件描述符集合。應用程序調用select時,通過這3個參數,傳入自己感興趣的文件描述符。select返回時,內核通過修改它們來通知應用程序哪些文件描述符已經就緒。我們看到,應用程序和內核都修改了這些參數。

fd_set 是結構體,定義如下:

typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#ifdef __USE_XOPEN
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
  } fd_set;

  該結構體只包含一個整形數組。其中,每個元素的一個位,標記一個文件描述符。fd_set 能容納的文件描述符數量由__FD_SETSIZE決定,默認1024.所以select能處理的最大文件描述符個數為1024.

對這個結構體的操作使用位運算。select.h文件中,提供了用來操作的宏

# define __FD_ZERO(set)  \
  do {                                        \
    unsigned int __i;                                 \
    fd_set *__arr = (set);                            \
    for (__i = 0; __i < sizeof (fd_set) / sizeof (__fd_mask); ++__i)          \
      __FDS_BITS (__arr)[__i] = 0;                        \
  } while (0)


#define __FD_SET(d, set) \
  ((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))
#define __FD_CLR(d, set) \
  ((void) (__FDS_BITS (set)[__FD_ELT (d)] &= ~__FD_MASK (d)))
#define __FD_ISSET(d, set) \
  ((__FDS_BITS (set)[__FD_ELT (d)] & __FD_MASK (d)) != 0)

  timeout參數設置超時時間,這是timeval結構體。使用指針是因為內核將修改它來告訴應用程序select等了多久。

struct timeval {
    __kernel_time_t     tv_sec;     /* seconds */
    __kernel_suseconds_t    tv_usec;    /* microseconds */
};

  如果timeout變量結構體成員都是0,則select立即返回。timeout為NULL,select一直阻塞,直到有文件描述符就緒。

select成功時返回就緒文件描述符總數。如果超時時間內沒有就緒的,返回0. select失敗時返回-1 並設置errno。如果select等待期間,程序收到信號,select立即返回-1,並設置errno為EINTR。

例子代碼,服務端程序:

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

#include <set>

using namespace std;

#define BUFSIZE 10

int createSocket()
{
    struct sockaddr_in servaddr;
    int listenfd = -1;

    if (-1 == (listenfd = socket(PF_INET, SOCK_STREAM, 0)))
    {
        fprintf(stderr, "socket: %d, %s\n", errno, strerror(errno));
        exit(1);
    }

    int reuseaddr = 1;
    if (-1 == setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)))
    {
        fprintf(stderr, "setsockopt: %d, %s\n", errno, strerror(errno));
        exit(1);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = PF_INET;
    servaddr.sin_port = htons(8008);
    inet_pton(PF_INET, "0.0.0.0", &servaddr.sin_addr);

    if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)))
    {
        fprintf(stderr, "bind: %d, %s\n", errno, strerror(errno));
        exit(1);
    }

    if (-1 == listen(listenfd, 5))
    {
        fprintf(stderr, "listen: %d, %s\n", errno, strerror(errno));
        exit(1);
    }

    return listenfd;
}

int main(int argc, char const *argv[])
{
    fd_set rfds;
    char buf[BUFSIZE] = {0};
    int listenfd = createSocket();

    // 保存所有的文件描述符
    set<int> fdset;
    fdset.insert(listenfd);

    while (1)
    {
        FD_ZERO(&rfds);
        // 每次都要重新設置rfds.因為select返回時,rfds被內核改變,里面只保存了就緒的文件描述符
        for (int fd : fdset)
        {
            FD_SET(fd, &rfds);
        }
        int ret = select(*fdset.rbegin()+1, &rfds, NULL, NULL, NULL);
        if (ret > 0)
        {
            for (int fd : fdset)
            {
                // 有新的連接
                if (fd == listenfd && FD_ISSET(fd, &rfds))
                {
                    struct sockaddr_in client;
                    socklen_t addrlen = sizeof(client);
                    int cfd = -1;

                    if (-1 == (cfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)))
                    {
                        fprintf(stderr, "accept: %d, %s\n", errno, strerror(errno));
                        exit(1);
                    }
                    // 添加到 fd_set 結構體,並記錄到 set
                    FD_SET(cfd, &rfds);
                    fdset.insert(cfd);
                    printf("get connection fd: %d\n", cfd);
                }
                else if (FD_ISSET(fd, &rfds))
                {
                    int lenrecv = -1;
                    memset(buf, 0, BUFSIZE);

                    lenrecv = recv(fd, buf, BUFSIZE-1, 0);
                    if (lenrecv > 0)
                    {
                        printf("%s\n", buf);
                    }
                    else if (0 == lenrecv)
                    {
                        // 客戶端退出,刪除文件描述符,並關閉
                        fdset.erase(fd);
                        FD_CLR(fd, &rfds);
                        printf("delete connection fd: %d\n", fd);
                        close(fd);
                    }
                    else
                    {
                        fprintf(stderr, "recv: %d, %s\n", errno, strerror(errno));
                        exit(1);
                    }
                    
                }
            }
        }
        else
        {
            fprintf(stderr, "select: %d, %s\n", errno, strerror(errno));
            exit(1);
        }
    }

    close(listenfd);
    
    return 0;
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM