Linux网络编程:select函数的用法和原理

news2024/11/18 14:25:19

Linux网络编程:select函数的用法和原理

Linux上的select函数

select函数用于检测一组socket中是否有事件就绪.这里的事件为以下三类:

  1. 读事件就绪
    • socket内核中,接收缓冲区中的字节数大于或者等于低水位标记SO_RCVLOWAT,此时调用recread函数可以无阻塞的读取该文件描述符,并且返回值大于零
    • TCP连接的对端关闭连接,此时本端调用rrecvread函数对socket进行读操作,recvread函数返回0
    • 在监听的socket上有新的连接请求
    • socket尚有未处理的错误
  2. 写事件就绪
    • socket内核中,发送缓冲区中的可用字节数大于等于低水位标记时,可以无阻塞的写,并且返回值大于0
    • socket的写操作被关闭时,对一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号
    • socket使用非阻塞connect连接成功或失败时
  3. 异常事件就绪

select()如下:

#include <sys/select.h>   
    int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

参数说明

nfds:Linux上的socket也叫作fd,将这个参数的值设置为所有需要使用select函数检测事件的fd中的最大值加1即 nfds=max(fd1,fd2,...,fdn)+1
readfds:需要监听可读事件的fd集合
writefds:需要监听可写事件fd的集合
exceptfds:需要监听异常事件的fd集合
timeout:超时时间,即在这个参数设定的时间内检测这些fd的事件,超过这个时间后,select函数立即返回,这是一个timeval结构体

其定义如下:

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

参数readfds,writefds,exceptfds的类型都是fd_set,这是一个结构体信息

定义如下

//#define __FD_SETSIZE		1024
#define __NFDBITS	(8 * (int) sizeof (__fd_mask))
#define	__FD_ELT(d)	((d) / __NFDBITS)
#define	__FD_MASK(d)	((__fd_mask) (1UL << ((d) % __NFDBITS)))

/* fd_set for select and pselect.  */
typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#ifdef __USE_XOPEN
    //typedef long int __fd_mask;
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#endif
  } fd_set;

/* 最大数量`fd_set'.  */
#define	FD_SETSIZE		__FD_SETSIZE

假设未定义__USE_XOPEN整理一年

typedef struct
  {
//typedef long int __fd_mask;
    long int fds_bits[__FD_SETSIZE / __NFDBITS];
  } fd_set;

将一个fd添加到fd_set这个集合中时需要使用FD_SET宏,其定义如下:

void FD_SET(fd, fdsetp)

实现如下:

#define	FD_SET(fd, fdsetp)	__FD_SET (fd, fdsetp)

__FD_SET (fd, fdsetp)实现如下:

/* We don't use `memset' because this would require a prototype and
   the array isn't too big.  */
# define __FD_ZERO(set)  \
  do {									      \
    unsigned int __i;							      \
    fd_set *__arr = (set);						      \
    for (__i = 0; __i < sizeof (fd_set) / sizeof (__fd_mask); ++__i)	      \
      __FDS_BITS (__arr)[__i] = 0;					      \
  } while (0)

#endif	/* GNU CC */

#define __FD_SET(d, set) \
  ((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))

举个例子,假设现在fd的值为43,那么在数组下表为0的元素中第43个bit被置为1


再Linux上,向fd_set集合中添加新的fd时,采用位图法确定位置;在windows中添加fd至fd_set的实现规则依次从数组第0个位置开始向后递增


也就是说,FD_SET宏本质上是在一个有1024个连续bit的数组的第fd位置置1.

同理,FD_CLR删除一个fd的原理,也就是将数组的第fd位置置为0

image-20210706144042275

实例;

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <cstring>
#include <sys/time.h>
#include <vector>
#include <cerrno>

//Customize the value representing invalid fd
#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
#define INVALID_FD -1
int main(int argc,char * argv[])
{
    //create a listen socket
    int listenfd = socket(AF_INET,SOCK_STREAM,0);
    if(listenfd == INVALID_FD)
    {
        printf("创建监听socket失败");
        return -1;
    }
    //init server addr
    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)
    {
        printf("绑定socket失败");
        close(listenfd);
        return -1;
    }
    //start listen
    if(listen(listenfd,SOMAXCONN) == -1)
    {
        printf("监听失败!");
        close(listenfd);
        return -1;
    }
    //Store the client's socket data
    std::vector<int> clientfds;
    int maxfd;
    while(true)
    {
        fd_set readset;
        FD_ZERO(&readset);
        FD_SET(listenfd,&readset);
        maxfd = listenfd;
        unsigned long 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, nullptr, nullptr,&tm);
        if(ret == -1)
        {
            if (errno != EINTR)
                break;
        }
        //time out
        else if (ret ==0 )
        {
            continue;
        }
        else
        {
            //event detected on a socket
            if (FD_ISSET(listenfd,&readset))
            {
                sockaddr_in clientaddr{};
                socklen_t  clientaddrlen = sizeof(clientaddr);
                //accept client connection
                int clientfd = accept(listenfd,(struct sockaddr *)&clientaddr,&clientaddrlen);
                if (clientfd == INVALID_FD)
                {
                    break;
                }
                std::cout<<"接受到客户端连接,fd:"<<clientfd<<std::endl;
                clientfds.push_back(clientfd);
            }
            else
            {
                //Assume that the data length sent by the client is not greater than 63
                char recvbuf[64];
                unsigned long 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));
                        //accept data
                        int length = recv(clientfds[i],recvbuf,64,0);
                        //recv的返回值等于0,表示客户端关闭了连接
                        if (length <=0 )
                        {
                            //error
                            std::cout<<"error"<<clientfds[i]<<std::endl;
                            close(clientfds[i]);
                            clientfds[i] == INVALID_FD;
                            continue;
                        }
                        std::cout<<"clientfd: "<<clientfds[i]<<", recv data:"<<recvbuf<<std::endl;
                    }
                }
            }
        }
    }
    //close all client socket
    int clientfdslength = clientfds.size();
    for (int i = 0; i < clientfdslength; ++i)
    {
        if(clientfds[i] != INVALID_FD)
        {
            close(clientfds[i]);
        }
    }
    //close socket
    close(listenfd);
    return 0;
}
#pragma clang diagnostic pop

使用nc -v 127.0.0.1 3000来模拟客户端,打开三个终端

image-20210706165026755

关于以上代码,需要注意以下几点:

  1. select函数在调用前后可能会修改readfds,writefds,exceptfds所以想在下次调用select函数时服用这些fd_set变量需要重新清零,添加内容

    for (int i = 0; i < clientfdslength; ++i)
            {
                if(clientfds[i] != INVALID_FD)
                {
                    FD_SET(clientfds[i],&readset);
                    if(maxfd<clientfds[i])
                        maxfd = clientfds[i];
                }
            }
    
  2. select函数也会修改timeval结构体的值,如果想复用这些变量,需要重新设置

    timeval tm{};
            tm.tv_sec = 1;
            tm.tv_usec =0;
    
  3. 如果将selecttimeval参数设置为NULL,则select函数会一直阻塞下去

windows上的socket函数

在windows上,select函数结束后,不会修改timeval函数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/956289.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Web UI自动化测试】Web UI自动化测试之框架篇(全网最全)

本文大纲截图&#xff1a; UnitTest框架&#xff1a; PyTest框架&#xff1a; 框架&#xff1a; 框架英文单词 framework&#xff0c;为解决一类事情的功能的集合。需要按照框架的规定&#xff08;套路&#xff09;去书写代码。 一、UnitTest框架介绍【文末分享自动化测试学…

报错:axios发送的所有请求都是404

axios发送的所有请求都是404 一、问题二、分析三、解决一、问题 对后台发送数据请求接口,在 Swagger 上是可以请求到的 但是通过 Ajax 发送请求就会报 404 Swagger 上调用如下 项目接口请求如下

深入MaxCompute -第十一弹 -QUALIFY

简介&#xff1a; MaxCompute支持QUALIFY语法过滤Window函数的结果&#xff0c;使得查询语句更简洁易理解。Window函数和QUALIFY语法之间的关系可以类比聚合函数GROUP BY语法和HAVING语法。 MaxCompute&#xff08;原ODPS&#xff09;是阿里云自主研发的具有业界领先水平的分…

【附安装包】3ds Max2023安装教程

软件下载 软件&#xff1a;3ds Max版本&#xff1a;2023语言&#xff1a;简体中文大小&#xff1a;6.85G安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU3GHz 内存16G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan.baidu.c…

【科普】干货!带你从0了解移动机器人(五) ( 如何选择控制器类型)

控制器是移动机器人&#xff08;AGV/AMR&#xff09;最主要的核心部件&#xff0c;是整个车体的“大脑”&#xff0c;关系到车体的可靠稳定、性能指标和安全性。它通常用于接收传感器模块采集的数据&#xff0c;并进行信息处理分析&#xff0c;从而下发各种运动指令的硬件。目前…

哪里可以找到优质的文章?

我认为中外科技内容过去主要是“信息差”&#xff0c;即人们可以直接从国外文章中摄取信息并直接实践&#xff0c;谁快谁赢。 而现在主要是“观点差”&#xff0c;国内科技相关的理论和评论文章的数量和质量都还比较弱。 所以&#xff0c;优质文章建议多找外文。 参考风险投资人…

django-项目

一、RESTful设计风格 基础概念 全称&#xff1a;Representational State Transfer 1.资源 网络上的一个实体&#xff0c;每个资源都有一个独一无二的URL与之对应&#xff1b;获取资源-直接访问URL即可 2.表现层 资源的表现形式 如HTML、xml、JPG、json等 3.状态转化 …

Kubernetes技术--部署实际的java项目部署

1.容器交付流程 (1).总体的流程如下所示: (2).k8s部署项目细节流程(详细过程) 2.java实际项目部署 (1).准备java项目,把项目打成jar包或者war包,这里需要依赖两个环境:jdk和maven环境。 使用mvn clean package进行打包,如下所示

2023年口腔医疗行业研究报告

第一章 行业概况 1.1 定义 口腔医疗行业是以口腔医疗服务消费为基础&#xff0c;包含医疗及消费双重属性&#xff0c;是 为满足口腔及颌面部疾病的预防和诊疗、口腔美容等需求提供相关医疗服务的行业。 该行业的主要参与者包括口腔保健专业人员&#xff08;如牙医、口腔外科…

【重要】这是我见过最好的Flash科普文了

一、Flash Memory简介 Flash Memory 是一种非易失性的存储器。在嵌入式系统中通常用于存放系统、应用和数据等。在 PC 系统中&#xff0c;则主要用在固态硬盘以及主板 BIOS 中。 另外&#xff0c;绝大部分的 U 盘、SDCard 等移动存储设备也都是使用 Flash Memory 作为存储介质…

day 2

多态&#xff0c;虚函数&#xff0c;纯虚函数 1.多态&#xff1a;父类的指针或者引用&#xff0c;指向或初始化子类的对象&#xff0c;调用子类对父类重写的函数&#xff0c;进而展开子类的功能。 函数重写 1> 必须有继承关系 2> 子类和父类有同名同类型的函数 3>…

【深度学习实验】NumPy的简单用法

目录 一、NumPy介绍 1. 官网 2. 官方教程 二、实验内容 1. 导入numpy库 2. 打印版本号 3. arange 函数 4. array函数 5. reshape函数 6. 矩阵点乘&#xff08;逐元素相乘&#xff09; 7. 矩阵乘法 一、NumPy介绍 NumPy是一个常用于科学计算的Python库&#xff0c;尤…

司徒理财:8.31黄金高空低多布局静等非农来袭

黄金行情走势分析&#xff1a;      黄金现在处于底部震荡走势&#xff0c;反弹已经接近尾声&#xff0c;周五公布大非农数据&#xff0c;消息不确定的情况下&#xff0c;黄金不会轻易突破日线压力&#xff0c;今日将依托1950的压力位置做空看跌&#xff0c;看波段回调&…

【Python小项目】Python的GUI库Tkinter实现随机点名工具或抽奖工具并封装成.exe可执行文件

一、项目背景 受朋友所托,帮他在公司年会活动上做一个点名抽奖的小工具。经过沟通后,他发给我一个人员名单表格,是xlsx格式的excel工作表,并大概设计了一下抽奖工具的界面以及相关要求。话不多说,马上开始项目流程。 二、需求分析 客户需求总结如下: UI界面设计如下:…

SpringBoot之@RefreshScope

注解RefreshScope时一个组合注解。 Target({ ElementType.TYPE, ElementType.METHOD }) Retention(RetentionPolicy.RUNTIME) Scope("refresh") Documented public interface RefreshScope {// Scope代理模式之ScopedProxyMode&#xff0c;包含TARGET_CLASS、INTERF…

技术领导力实战笔记:14

14&#xff5c;团队优化&#xff1a;如何妥善且优雅地做好解聘工作&#xff1f; 我们需要在思想上对这件事情有一个正确的认识&#xff0c;解聘对团队、个人和管理者三方都是有好处的。摆正自己的立场才能做出正确的决定。 1.公司发展太快&#xff0c;个人没有跟上脚步 2.个人…

Go在安装Gin时出现Failed to connect 报错问题的解决方案(已解决)

在命令行中输入&#xff1a;go get -u github.com/gin-gonic/gin指令安装Gin第三方包时出现连接错误与连接超时的情况如下&#xff1a; 在较新版本的Go中引入了全新的包管理机制&#xff0c;出现上述错误可能是包管理机制设置不恰当的问题&#xff0c;尝试在终端窗口输入如下…

Mysql数据库(1)—索引

索引是什么&#xff1f; 索引是帮助MySQL高效获取数据的排好序的数据结构。常见的索引数据结构包括&#xff1a; 二叉树红黑树Hash表B-Tree mysql索引分类 按逻辑结构分类&#xff1a;B tree索引、Hash索引、Full-text索引。按物理存储分类&#xff1a; &#xff08;1&…

更健康舒适更科技的照明体验!SUKER书客护眼台灯 L1上手体验

低价又好用的护眼台灯是多数人的需求&#xff0c;很多人只追求功能性护眼台灯&#xff0c;显色高、无频闪、无蓝光等基础需求。但是在较低价格中很难面面俱到&#xff0c;然而刚发布的SUKER书客L1护眼台灯却是一款不可多得的性价比护眼台灯&#xff0c;拥有高品质光源&#xff…