首页 > 其他分享 >select函数的用法和原理

select函数的用法和原理

时间:2023-11-22 18:12:46浏览次数:27  
标签:socket int 用法 fd clientfds include select 函数

Linux 上的 select 函数

select 函数用于检测在一组 socket 中是否有事件就绪。事件分为以下三类:

  1. 读就绪事件
  • 在 socket 内核中,接收缓冲区中的字节数大于或等于低水位标记 SO_RCVLOWAT,此时调用 recv 或 read 函数可以无阻塞地读该文件描述符,并且返回值大于 0。
  • TCP 连接的对端关闭连接,此时本端调用 recv 和 read 函数对 socket 进行读操作,recv 或 read 函数会返回 0 值。
  • 在监听 socket 上有新的连接请求。
  • 在 socket 上有未处理的错误。
  1. 写就绪事件
  • 在 socket 内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小)大于或等于低水位标记 SO_SENDLOWAT 时,可以无阻塞的写,并且返回值大于 0。
  • socket 的写操作被关闭(即调用了 close 或 shutdown 函数)时,对一个写操作被关闭的 socket 进行写操作,会触发 SIGPIPE 信号。
  • socket 使用阻塞 connect 连接成功或失败时。
  1. 异常就绪事件

函数签名:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

//timeval类型:
struct timeval
{
  long tv_sec;  /* 秒 */
  long tv_usec; /* 微秒 */
}

//fd_set结构体类型,简写如下
typedef struct
{
  long int __fds_bits[16];  //可以看做 128 bit 的数组 
} fe_set;

__fds_bits 是 long int 类型的数组, long int 占 8 字节,每字节都有 8bit,每个 bit 都对应一个 fd 的事件状态,0 表示无事件,1 表示有事件,数组长度是 16。因此一共可以表示 8816=1024 个 fd 的状态,这是 select 函数支持的最大 fd 数量。(位图法)

具体示例:

点击查看代码
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <sys/time.h>
#include <vector>
#include <errno.h>

//自定义代表无效 fd 的值
#define INVALID_FD -1

int main(int argc, char* argv[])
{
    //创建一个监听 socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd == INVALID_FD){
        std::cout << "create listen socket error." << std::endl;
        return -1;
    }

    //初始化服务器地址
    struct sockaddr_in bindaddr;
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bindaddr.sin_port = htons(3000);
    if(bind(listenfd, (struct sockaddr*)&bindaddr, sizeof(bindaddr)) == -1){
        std::cout << "bind listen socket error." << std::endl;
        close(listenfd);
        return -1;
    }

    //启动监听
    if(listen(listenfd, SOMAXCONN) == -1){
        std::cout << "listen error." << std::endl;
        close(listenfd);
        return -1;
    }

    //存储客户端 socket 的数组
    std::vector<int> clientfds;
    int maxfd;

    while(true)
    {
        fd_set readset;
        FD_ZERO(&readset);

        //将监听的socket加入待检测的可读事件中
        FD_SET(listenfd, &readset);

        maxfd = listenfd;
        //将客户端fd加入待检测的可读事件中
        int clientfdslength = clientfds.size();
        for(int i = 0; i < clientfdslength; i++){
            if(clientfds[i] != INVALID_FD){
                FD_SET(clientfds[i], &readset);

                if(maxfd < clientfds[i]) maxfd = clientfds[i];
            }
        }

        timeval tm;
        tm.tv_sec = 1;
        tm.tv_usec = 0;
        //暂且只检测可读事件,不检测可写和异常事件
        int ret = select(maxfd+1, &readset, NULL,NULL,&tm);
        std::cout << "select success" << std::endl;
        if(ret == -1){
            //出错,退出程序
            if(errno == EINTR){
                break;
            }
        }else if(ret == 0){
            //select函数超时,下次继续
            continue;
        }else{
            //检测到某个socket有事件
            if(FD_ISSET(listenfd, &readset)){
                //监听socket的可读事件,表明有新的连接到来
                struct sockaddr_in clientaddr;
                socklen_t clientaddrlen = sizeof(clientaddr);
                //接受客户端连接
                int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientaddrlen);
                if(clientfd == INVALID_FD){
                    //接受连接出错,退出程序
                    break;
                }

                //只接受连接,不调用recv收取任何数据
                std::cout << "accept a client connection, fd: " << clientfd << std::endl;
                clientfds.push_back(clientfd);

            }
            else{
                //假设对端发来的数据长度不超过63个字符
                char recvbuf[64];
                int clientfdslength = clientfds.size();
                for(int i = 0; i < clientfdslength; i++){
                    if(clientfds[i] != INVALID_FD && FD_ISSET(clientfds[i], &readset)){
                        memset(recvbuf, 0, sizeof(recvbuf));
                        //非监听socket, 接收数据
                        int length = recv(clientfds[i], &recvbuf, 64, 0);
                        if(length <= 0){
                            //收取数据出错
                            std::cout << "recv data error, clientfd:" << clientfds[i] << std::endl;
                            
                            close(clientfds[i]);
                            //不直接删除该元素,将该位置的元素标记为 INVALID_FD
                            clientfds[i] = INVALID_FD;
                            continue;
                        }

                        std::cout << "clientfd:" << clientfds[i] << ", recv data:" << recvbuf << std::endl;

                    }
                    
                }
            }
        }

    }

    //关闭所有客户端 socket
    int clientfdslength = clientfds.size();
    for(int i = 0; i < clientfdslength; ++i){
        if(clientfds[i] != INVALID_FD)
        {
            close(clientfds[i]);
        }
    }

    //关闭监听socket
    close(listenfd);
    return 0;
}

标签:socket,int,用法,fd,clientfds,include,select,函数
From: https://www.cnblogs.com/ljx-0122/p/17849690.html

相关文章

  • python中常见函数
    filter,reduce,和map是Python中用于对集合数据进行处理和转换的内置函数。它们分别用于筛选、归约和映射集合中的元素。filter函数:filter(function,iterable)用于筛选集合中的元素。它接受一个函数function和一个可迭代的对象iterable,并返回一个包含iterable中满足......
  • mybatis plus order by 不支持convert函数
    最近业务上有个需求,要按照企业名称中文进行排序显示。项目使用的是mybatisplus+mysql从网上看到的排序解决方法是使用mysql的convert函数:select*from客户表where***orderbyconvert(`企业名称`usingGBK);为什么要使用convert函数那?因为一般使用的数据编码是utf-8,m......
  • 并发请求函数的实现
    实施步骤:初始化结果和执行数组:创建数组来存储每个请求的结果并跟踪当前正在执行的请求。为单个请求编写异步函数:该函数发送请求,存储结果,并从执行数组中删除已完成的请求。对并发请求使用循环和Promises:利用循环初始化一定数量的并发请求并将它们添加到执行数组中。递归调用函......
  • ###%%%%%%%%%%脚本的用法:
     ##准备测试文件:[root@zabbix-agentopt]#whilereadline;doecho${line%%*};done<stu.txt01oldboy1802lidao1703baojewery6604happyday905oldcao99##获取文件中的序号::::%从右边向左边删除:[root@zabbix-agentopt]#whilereadline;doecho${line%%*};done......
  • systemverilog中fork..join, join_any, join_none的用法和解析
    对fork..join,join_any以及join_none的用法进行总结,以及整理下可能遇到的坑。 简单的说就是:fork..join:必须等到ment1,ment2,ment3全部执行完之后,ment4才可以执行。fork..join_any:等到ment1,ment2,ment3之中任何一个执行完毕之后,ment4才可以执行fork..join_none:ment4的......
  • 常用度量函数/距离
    常用的距离变换闵可夫斯基距离/明氏距离(MinkowskiDistance)\[\left(\sum_{i=1}^n|x_i-y_i|^p\right)^{1/p}\]曼哈顿距离/城市街区距离(ManhattanDistance)\[d(x,y)=\sum_{i=1}^n|x_i-y_i|\]欧式距离(EuclideanDistance)\[d(x,y)=\sqrt{\sum_n^{i=1}(x_i-y_j)^2}\]切比......
  • 触发器dff与锁存器latch的用法和区别
    dff与latch的用法和区别废话少说,dff是边沿敏感,latch是电平敏感。用法上图:功能仿真: 以下部分是摘抄别人的技术心得:latch(锁存器)与DFF(D触发器)的区别1、latch由电平触发,非同步控制。在使能信号有效时latch相当于通路,在使能信号无效时latch保持输出状态。DFF由时钟沿触发,同......
  • Maybatis-Plus 数据库查询 lambdaQuery和mapper中EQ、NE、GT、LT、GE、LE、select、li
    Maybatis-PluslambdaQuery和mapper中EQ、NE、GT、LT、GE、LE的用法及详解实体当前实体如下,后续代码示例都用该实体;@Data@TableName("user_info")@ApiModel(value="UserInfo对象",description="")publicclassUserInfoimplementsSerializable{privatesta......
  • MySql 中 DATEDIFF() 用法
    DATEDIFF函数用于计算两个日期之间的差值,并以天数返回结果。它的语法如下:DATEDIFF(end_date,start_date)其中,end_date和start_date是要计算差值的结束日期和起始日期。以下是一个示例,计算两个日期之间的天数差值:SELECTDATEDIFF('2023-11-21','2023-11-01')ASdays_di......
  • 报错:Invalid bound statement (not found): com.ljxx.pts.dao.SitePriceMapper.select
    如果你是Mybatis的话请注意yml或者properties文件里面的组件扫描#指定mapper.xml的位置mybatis.mapperLocations=classpath*:mapper/**/*Mapper.xmlmybatis.configuration.map-underscore-to-camel-case=true注意:由于上面指定的是Mapper.xml,故xml文件不要携程Dao.xml......