Fork me on GitHub

对select()的理解

断断续续的看Beej’s Guide to Network Programming这本小册子,最近看了select函数的用法,感觉这个函数挺有用而且听特殊,理解上有点儿难度,因此记录下来关于这个函数的一些东西.

函数定义

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

fd_set

这里的fd_set类型相当于一个file descriptor的集合.
当select()函数返回时,readfs中只含有可以读的file descriptor.writefs包含可写的descriptor,exceptfds包含出错的file descriptor .(此三个参数不必都有) fd_set有一些列的操作函数:

FD_SET(int fd, fd_set *set); //Add fd to the set .
FD_CLR(int fd, fd_set *set); //Remove fd from the set .
FD_ISSET(int fd, fd_set *set); //Return true if fd is in the set .
FD_ZERO(fd_set *set); //Clear all entries from the set .

timeval结构体

struct timeval {
int tv_sec; //seconds
int tv_usec;// microseconds
};

此结构体表示select函数的超时时间.设置为NULL则永远等待. 此函数使用时,numfds应设置为最高的file descriptor的值.

返回值

返回set中最后剩下的的数目.0表示超时,-1表示出错.

此函数如何使用的理解

我们可以在开始的时候使用FD_SET将我们创建的服务端套接字描述符填入一个fd_set中,然后把这个fd_set复制一下传如select的readfds参数中然后用select监听,select有返回值后则遍历所有descriptors

  • 如果这个descriptor在这个新的readfds中(FD_ISSET)则说明可以read
    • 如果这个descriptor正好是我们最初的那个服务端套接字
      • 则可以进行accept操作,然后将读到的这个socksetfd就可以传入我们的fd_set中,判断这个socketfd是否比原有的最大的socketfd大,大则替换select()函数的第一个参数为这个新的socketfd+1.
    • 如果这个descriptor不是最初的那个服务端套接字
      • 则说明是与某个主机的一个连接,则可以调用这个套接字的recv()方法接收数据,如果返回0说明这个套接字已经断开,可调用FD_CLR将其从最初的fd_set中清除出去.否则则进行其他的一些列操作.

一个简单的demo

/*
** ChatServer.cpp - a practise on select()
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>

#define PORT "3456"
#define STDIN 0

void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET)
    {
        return &(((struct sockaddr_in *)sa)->sin_addr);
    }
    else
        return &(((struct sockaddr_in6 *)sa)->sin6_addr);
}

int main(void)
{
    fd_set master;
    fd_set read_fds;
    int fd_max;

    int listener;
    int newfd;
    struct sockaddr_storage remoteaddr;
    socklen_t addrlen;

    char buf[256];
    char mess[256];
    int nbytes;

    char remoteIP[INET6_ADDRSTRLEN];

    int yes = 1;
    int i, j, rv;
    struct addrinfo hints, *ai, *p;

    FD_ZERO(&master);
    FD_ZERO(&read_fds);

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0)
    {
        fprintf(stderr, "selectserver:%s\n", gai_strerror(rv));
        exit(1);
    }

    for (p = ai; p != NULL; p = p->ai_next)
    {
        listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (listener < 0)
        {
            continue;
        }
        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
        if (bind(listener, p->ai_addr, p->ai_addrlen) < 0)
        {
            close(listener);
            continue;
        }
        break;
    }
    if (p == NULL)
    {
        fprintf(stderr, "selectserver: failed to bind\n");
        exit(2);
    }
    freeaddrinfo(ai);
    if (listen(listener, 10) == -1)
    {
        perror("listen");
        exit(3);
    }
    FD_SET(listener, &master);
    FD_SET(STDIN, &master);
    fd_max = listener > STDIN ? listener : STDIN;

    for (;;)
    {
        read_fds = master;
        if (select(fd_max + 1, &read_fds, NULL, NULL, NULL) == -1)
        {
            perror("select");
            exit(4);
        }
        for (i = 0; i <= fd_max; i++)
        {
            if (FD_ISSET(i, &read_fds))
            { //we got one!!
                if (i == STDIN)
                {
                    fgets(buf, sizeof buf, stdin);
                    memset(mess, 0, sizeof mess);
                    sprintf(mess, "server said:%s", buf);
                    for (j = 0; j <= fd_max; j++)
                    {
                        if (FD_ISSET(j, &master))
                        {
                            if (j != listener && j != i && j != STDIN)
                            {
                                if (send(j, mess, strlen(mess), 0) == -1)
                                {
                                    perror("send");
                                }
                            }
                        }
                    }
                }
                else if (i == listener)
                {
                    addrlen = sizeof remoteaddr;
                    newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);
                    if (newfd == -1)
                    {
                        perror("accept");
                    }
                    else
                    {
                        FD_SET(newfd, &master);
                        if (newfd > fd_max)
                        {
                            fd_max = newfd;
                        }
                        printf("selectserver:new connection from %s on socket %d\n",
                               inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr *)&remoteaddr), remoteIP, INET6_ADDRSTRLEN), newfd);
                        sprintf(buf, "hello!!! your socket id is %d\n", newfd);
                        if (send(newfd, buf, strlen(buf), 0) == -1)
                        {
                            perror("send");
                        }
                        sprintf(buf, "socket %d joined chatroom\n", newfd);
                        for (j = 0; j < fd_max; j++)
                        {
                            if (FD_ISSET(j, &master))
                            {
                                if (j != i && j != listener && j != STDIN)
                                {
                                    if (send(j, buf, strlen(buf), 0) == -1)
                                        perror("send");
                                }
                            }
                        }
                    }
                }
                else
                {
                    if ((nbytes = recv(i, buf, sizeof buf, 0)) <= 0)
                    {
                        if (nbytes == 0)
                        {
                            sprintf(buf, "select server: socket %d hung up\n", i);
                            printf("%s", buf);
                            for (j = 0; j <= fd_max; j++)
                            {
                                if (FD_ISSET(j, &master))
                                    if (j != listener && j != i && j != STDIN)
                                    {
                                        if (send(j, buf, strlen(buf), 0) == -1)
                                        {
                                            perror("send");
                                        }
                                    }
                            }
                        }
                        else
                        {
                            perror("recv");
                        }
                        close(i);
                        FD_CLR(i, &master);
                    }
                    else
                    {
                        if (nbytes <= 2)
                            nbytes = 3;
                        memset(mess, 0, sizeof mess);
                        strncpy(mess, buf, nbytes - 2);
                        printf("recv data %s from socket %d\n", mess, i);
                        sprintf(buf, "socket %d says: %s\n", i, mess);
                        for (j = 0; j <= fd_max; j++)
                        {
                            if (FD_ISSET(j, &master))
                            {
                                if (j != listener && j != i && j != STDIN)
                                {
                                    if (send(j, buf, strlen(buf), 0) == -1)
                                    {
                                        perror("send");
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return 0;
}

要特别注意一次select操作后readfs中只剩下了当前可读的fd.所以每次要复制一个副本穿进去.

题外话

还是认真学习认真思考能让人不那么丧…..庆幸自己找到了自己真正的兴趣所在.
不要让一些没意义的东西麻痹了自己. 要时刻提醒自己生存不易.
不高兴了就更更博,耐心看看技术书,让自己静一静.闲了就容易生事.
keep on moving!!!