• 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。

  • 相关阅读:
    perl自定义简易的面向对象的栈与队列类
    java idea实现.java文件编译成class并运行
    git代码管理及提交
    k8s和docker的区别
    pycharm查看代码结构的方法
    ssh 登录的方式
    excel分析数据绘制统计直方图
    linux普通用户使用yum安装nginx,并使用nginx
    iterm2 + virtualbox + centos环境搭建
    python源码安装
  • 原文地址:https://www.cnblogs.com/lit10050528/p/4650557.html
Copyright © 2020-2023  润新知