linux select 学习

一、select介绍

函数原型:

#include <sys/select.h>
int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr);
// 返回值:准备就绪的描述符数,若超时返回0,若出错返回-1

参数说明:

maxfd:是需要监视的最大的文件描述符值+1;readfds/writefds/exceptfds分别对应需要检测的可读文件描述符的集合、可写描述符集合以及异常描述符集合。

tvptr:说明愿意等待多久。

有三种情况:

tvptr=NULL 永远等待。

tvptr->tv_sec==0 && tvptr->tv_usec=0 完全不等待。

tvptr->tv_sec!=0 || tvptr->tv_usec != 0 等待指定时间。若超时,则返回0。

POSIX允许实现中修改tvptr的值,所以在每次select开始时都需要重新设置该值。

对fd_set类型的处理有四个专有函数:

#include <sys/select.h>
int FD_ISSET(int fd, fd_set *fdset); // 测试某一个描述符 返回值:若fd在描述符集中则返回非0,否则返回0
void FD_CLR(int fd, fd_set *fdset); // 删除某一个描述符
void FD_SET(int fd, fd_set *fdset); // 设置某一个描述符
void FD_ZERO(fd_sete *fdset); // 清空

返回值:

select有三个可能的返回值:

返回-1表示出错。

返回0表示没有描述符准备好。

返回正值表示已经准备好的描述符数。该值是三个描述符集中已准备好的描述符的和。

select函数的中间三个参数的任意一个或全部都可以为NULL。如果三个都是NULL,则select提供一个高精度的计时器。

二、select使用

示例1:回显服务器

/*******************************************************************************
* File Name        : select.cpp
* Author        : zjw
* Email            : emp3XzA3MjJAMTYzLmNvbQo= (base64 encode)
* Create Time    : 2015年07月15日 星期三 11时52分07秒
*******************************************************************************/

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

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;

const int SERVER_PORT = 8080;
const int FD_SIZE = 1024;
const int RECV_SIZE = 1024;
const int SEND_SIZE = 1040;

int main(int argc, char **argv)
{
    int server = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr_server;
    addr_server.sin_family = AF_INET;
    addr_server.sin_port = htons(SERVER_PORT);
    addr_server.sin_addr.s_addr = INADDR_ANY;
    memset(addr_server.sin_zero, 0, sizeof(addr_server.sin_zero));

    if (bind(server, (struct sockaddr*)&addr_server, sizeof(addr_server)))
    {
        perror("bind failed!");
        return -1;
    }

    listen(server, 5);

    fd_set fdsr;
    int fd[FD_SIZE] = { 0 };
    int maxsock = server;
    char recvBuf[RECV_SIZE + 1];
    char sendBuf[SEND_SIZE + 1];
    map<int, sockaddr_in> mapClients;

    while (1)
    {
        FD_ZERO(&fdsr);
        FD_SET(server, &fdsr);

        struct timeval tv;
        tv.tv_sec = 5;
        tv.tv_usec = 0;
    
        for (int i = 0; i < 1024; i++)
        {
            if (fd[i] != 0)
            {
                FD_SET(fd[i], &fdsr);
            }
        }

        int ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);
        if (ret == -1)
        {
            perror("select error!");
            return -1;
        }
        else if (ret == 0)
        {
            cout << "timeout!" << endl;
            continue;
        }
        
        // check every fd in the fdset
        for (int i = 0; i < FD_SIZE; i++)
        {
            if (FD_ISSET(fd[i], &fdsr))
            {
                memset(recvBuf, 0, RECV_SIZE + 1);
                memset(sendBuf, 0, SEND_SIZE + 1);
                ret = recv(fd[i], recvBuf, RECV_SIZE, 0);
                sprintf(sendBuf, "Your said:%s", recvBuf);
                send(fd[i], sendBuf, SEND_SIZE, 0);
                if (ret <= 0)
                {
                    cout << "client " << fd[i] << " close" << endl;
                    mapClients.erase(fd[i]);
                    close(fd[i]);
                    FD_CLR(fd[i], &fdsr);
                    fd[i] = 0;
                }
                else
                {
                    cout << "client " << fd[i] << " ip[" << inet_ntoa(mapClients[fd[i]].sin_addr) << "]" << " said:" << recvBuf << endl;
                }
            }
        }

        // check server socket
        if (FD_ISSET(server, &fdsr))
        {
            int client;
            sockaddr_in addr_client;
            socklen_t len;
            client = accept(server, (struct sockaddr*)&addr_client, &len);
            if (client == -1)
            {
                perror("accept error!");
                return -1;
            }

            for (int i = 0; i < FD_SIZE; i++)
            {
                if (fd[i] == 0)
                {
                    fd[i] = client;
                    mapClients.insert(make_pair<int, sockaddr_in>(client, addr_client));
                    break;
                }
            }
        }
        maxsock = server;
        for (int i = 0; i < FD_SIZE; i++)
        {
            if (fd[i] > maxsock)
            {
                maxsock = fd[i];
            }
        }
    }
    
    return 0;
}

Makefile:

echo: select.cpp
    g++ -o $@ $<

clean:
    rm -rf echo

执行结果:

server端:echo

client端:telnet

提示:使用telnet测试非常方便,但是退出telnet有点麻烦,输出ctrl+],然后输入quit即可退出。

示例2:定时器

/*******************************************************************************
* File Name        : timer.cpp
* Author        : zjw
* Email            : emp3XzA3MjJAMTYzLmNvbQo= (base64 encode)
* Create Time    : 2015年07月21日 星期二 16时45分16秒
*******************************************************************************/
#include <sys/select.h>
#include <unistd.h>
#include <iostream>
using namespace std;

int main(int argc, char **argv)
{
    struct timeval tv;

    while (1)
    {
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        select(0, NULL, NULL, NULL, &tv);
        cout << "Five second have passed." << endl;
    }

    return 0;
}

三、参考

http://blog.csdn.net/turkeyzhou/article/details/8609360

四、疑问

select是如何判断每个描述符可读、可写或者有异常的?

每个描述符没有设置为非阻塞的,那么如果select使用recv来尝试读,它是如何做到不阻塞的?或者说,select将这些描述符设置为非阻塞的了?还是它又另外的方法来测试可读?

select为什么有最大连接限制?不理解所谓的FD_SETSIZE。

posted @ 2015-07-16 10:47  冷冰若水  阅读(3336)  评论(0编辑  收藏  举报