【IO复用之select】

select

承接上篇socket基础编程模板
基础的socket,accept方式一次只能监听一个socket,为了监听多个链接的socket,最直观的方法就是把要监听的socket组成数组进行遍历。
select用fd_set来表示监听的socket集合。fd_set是一个数组,该数组的每个元素的每个bit位表示一个文件描述符,即位图。数组的大小取决于内核支持的打开文件描述符的最大数量FD_SIZE,通常是1024。

函数原型:

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

1.nfds指定监听文件描述的总数,通常被设置成文件描述中的最大值加1,因为文件描述是从0开始计数的。
2.readfds、writefds、exceptfds分别表示可读、可写、异常事件对应的文件描述符集合。注意:这个三个参数既是入参,也是出参。在select函数返回时,内核会修改这三个参数,将就绪的文件描述符对应的bit位置位。因此,每次调用select都要重新设置这三个值。
3. timeout时用来设置select系统调用的超时时间。

struct timeval
{
      long tv_sec;  /* 秒数 */
      long tv_usec; /* 微秒 */
};

如果tv_sec和tv_usec都传入0,select将立即返回。如果timeout传入NULL,select将一直阻塞,直到描述符集合中有就绪的文件描述符。

位操作过于繁琐,可以使用一下宏定义操作:

#include <sys/select.h>
FD_ZERO(fd_set *fdset);                  /* 清除fdset的所有位 */
FD_SET(int fd, fd_set *fdset);           /* 设置fdset的位fd */
FD_CLR(int fd, fd_set *fdset);           /* 清除fdset的位fd */
int FD_ISSET(int fd, fd_set *fdset);     /* 测试fdset的位fd是否设置 */

编程实例(代码来自《Linux高性能服务器编程》)

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    if (argc <= 2)
    {
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char *ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    // address.sin_addr.s_addr = inet_addr(ip);
    address.sin_port = htons(port);

    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);

    int ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(listenfd, 5);
    assert(ret != -1);

    struct sockaddr_in client_address;
    socklen_t client_addrlength = sizeof(client_address);
    int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength);
    if (connfd < 0)
    {
        printf("errno is %d\n", errno);
        close(listenfd);
    }

    char buf[1024];
    fd_set read_fds;
    fd_set exception_fds;
    FD_ZERO(&read_fds);
    FD_ZERO(&exception_fds);

    while (1)
    {
        memset(buf, '\0', sizeof(buf));
        FD_SET(connfd, &read_fds);
        FD_SET(connfd, &exception_fds);
        ret = select(connfd + 1, &read_fds, NULL, &exception_fds, NULL);
        if (ret < 0)
        {
            printf("selection failure\n");
            break;
        }
        if (FD_ISSET(connfd, &read_fds))
        {
            ret = recv(connfd, buf, sizeof(buf) - 1, 0);
            if (ret <= 0)
            {
                break;
            }
            printf("get %d bytes of normal data: %s\n", ret, buf);
        }
        else if (FD_ISSET(connfd, &exception_fds))
        {
            ret = recv(connfd, buf, sizeof(buf) - 1, MSG_OOB);
            if (ret <= 0)
            {
                break;
            }
            printf("get %d bytes of oob data: %s\n", ret, buf);
        }
    }
    close(connfd);
    close(listenfd);
    return 0;
}
posted @ 2020-08-28 23:56  细雨徐行  阅读(154)  评论(0)    收藏  举报