select

news2024/11/24 13:54:26

1. select模型

在这里插入图片描述

2. select()函数

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

fd_set类型

readfds和writefds, exceptfds的类型都是fd_set,那么fd_set类型是什么呢?

  • fd_set类型本质是一个位图,位图的位置 表示
    相对应的文件描述符,内容表示该文件描述符是否有效,1代表该位置的文件描述符有效,0则表示该位置的文件描述符无效
  • 如果将文件描述符2,3设置位图当中,则位图表示的是为1100
  • fd_set的上限是1024个文件描述符

readfds

  • readfds是 等待读事件的文件描述符集合,.如果不关心读事件(缓冲区有数据),则可以传NULL值
  • 应用进程和内核都可以设置readfds,应用进程设置readfds是为了通知内核去等待readfds中的文件描述符的读事件.而
    内核设置readfds是为了告诉应用进程哪些读事件生效

在这里插入图片描述

writefds

与readfds类似,writefds是等待写事件(缓冲区中是否有空间)的集合,如果不关心写事件,则可以传值NULL

exceptfds

如果内核等待相应的文件描述符发生异常,则将失败的文件描述符设置进exceptfds中,如果不关心错误事件,可以传值NULL

timeout

设置select在内核中阻塞的时间,如果想要设置为非阻塞,则设置为NULL。如果想让select阻塞5秒,则将创建一个struct timeval time={5,0};

其中struct timeval的结构体类型是:

           struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };

返回值

  • 如果没有文件描述符就绪就返回0;
  • 如果调用失败返回-1;
  • 如果在timeout时间内,readfds中有事件发生,则返回timeout剩下的时间

3. select的工作流程

应用进程和内核都需要从readfdswritefds获取信息,其中,内核需要从readfds和writefds知道哪些文件描述符需要等待,应用进程需要从readfdswritefds中知道哪些文件描述符的事件就绪
在这里插入图片描述

select采用水平触发的方式,如果报告了fd后事件没有被处理或数据没有被全部读取,那么下次select时会再次报告该fd。

select 模型是一种多路复用 I/O 模型,它可以同时监视多个文件描述符,等待其中任意一个文件描述符上有事件发生,从而实现 I/O 多路复用。select 模型的原理如下:

调用 select 函数,将要监视的文件描述符集合传递给它。

select 函数将进程挂起,等待文件描述符集合中的任意一个文件描述符上有事件发生。

当文件描述符集合中的任意一个文件描述符上有事件发生时,select 函数返回,并将有事件发生的文件描述符集合返回给进程。

进程遍历有事件发生的文件描述符集合,对每个文件描述符进行相应的操作,如读、写等

4. 缺陷

  • 由于fd_set的上限是1024,所以select能等待的读事件的文件描述符和写事件的文件描述是有上限的,如果作为一个大型服务器,能够同时链接的客户端是远远不够的
  • 内核每一次等待文件描述符 都会重新扫描所有readfds或者writefds中的所有文件描述符,如果有较多的文件描述符,则会影响效率

5. 源码

client.cpp

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
 
int main(int argc,char *argv[])
{
  if (argc!=3)
  {
    printf("Using:./client ip port\nExample:./client 127.0.0.1 5005\n\n"); return -1;
  }

  // 第1步:创建客户端的socket。
  int sockfd;
  if ( (sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); return -1; }
 
  // 第2步:向服务器发起连接请求。
  struct hostent* h;
  if ( (h = gethostbyname(argv[1])) == 0 )   // 指定服务端的ip地址
  { printf("gethostbyname failed.\n"); close(sockfd); return -1; }
  struct sockaddr_in servaddr;
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(atoi(argv[2])); // 指定服务端的通信端口。
  memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
  if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0)  // 向服务端发起连接清求。
  { perror("connect"); close(sockfd); return -1; }

  char buffer[1024];
 
  // 第3步:与服务端通信,发送一个报文后等待回复,然后再发下一个报文。
  for (int ii=0; ii<5; ii++)
  {
    int iret;
    memset(buffer, 0, sizeof(buffer));
    printf("Please input:");
    scanf("%s", buffer);
    //sprintf(buffer, "这是第%d个消息, 编号%03d。", ii+1, ii+1);
    if ( (iret=send(sockfd, buffer, strlen(buffer), 0))<=0) // 向服务端发送请求报文。
    { perror("send"); break; }
    printf("发送: %s\n",buffer);

    memset(buffer, 0, sizeof(buffer));
    if ( (iret=recv(sockfd, buffer, sizeof(buffer),0)) <= 0) // 接收服务端的回应报文。
    {
       printf("iret=%d\n", iret); break;
    }
    printf("接收: %s\n", buffer);
    sleep(1);
  }
 
  // 第4步:关闭socket,释放资源。
  close(sockfd);
}


tcpselect.cpp

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/fcntl.h>

// 初始化服务端的监听端口。
int initserver(int port);

int main(int argc,char *argv[])
{
    if (argc != 2)
    {
      printf("usage: ./tcpselect port\n"); return -1;
    }

    // 初始化服务端用于监听的socket。
    int listensock = initserver(atoi(argv[1]));
    printf("listensock=%d\n",listensock);

    if (listensock < 0)
    {
      printf("initserver() failed.\n"); return -1;
    }

    fd_set readfdset;  // 读事件的集合,包括监听socket和客户端连接上来的socket。
    int maxfd;  // readfdset中socket的最大值。 maxfd是集合中socket中最大的值

    // 初始化结构体,把listensock添加到集合中。
    FD_ZERO(&readfdset);
    FD_SET(listensock, &readfdset);
    maxfd = listensock;

    while (1)
    {
        // 调用select函数时,会改变socket集合的内容,所以要把socket集合保存下来,传一个临时的给select。
        fd_set tmpfdset = readfdset;
        struct timeval time = {5,0};
        int infds = select(maxfd+1, &tmpfdset, NULL, NULL, &time); //select会改变tmpfdset集合
        //printf("select infds=%d\n",infds);//select会阻塞在这里,

        // 返回失败。
        if (infds < 0)
        {
            printf("select() failed.\n"); 
            perror("select()"); 
            break;
        }

        // 超时,在本程序中,select函数最后一个参数为空,不存在超时的情况,但以下代码还是留着。
        if (infds == 0)
        {
            printf("select() timeout.\n"); continue;
        }

        // 检查有事情发生的socket,包括监听和客户端连接的socket。
        // 这里是客户端的socket事件,每次都要遍历整个集合,因为可能有多个socket有事件。
        for (int eventfd=0; eventfd <= maxfd; eventfd++)
        {
            if (FD_ISSET(eventfd, &tmpfdset) <= 0) continue;

            if (eventfd == listensock)
            { 
                // 如果发生事件的是listensock,表示有新的客户端连上来
                struct sockaddr_in client;
                socklen_t len = sizeof(client);
                int clientsock = accept(listensock, (struct sockaddr*)&client, &len);
                if (clientsock < 0)
                {
                  printf("accept() failed.\n"); continue;
                }

                printf ("client(socket=%d) connected ok.\n", clientsock);

                // 把新的客户端socket加入集合
                FD_SET(clientsock, &readfdset);

                if (maxfd < clientsock) maxfd = clientsock;//更新maxfd

                continue;
            }
            else
            {
                // 客户端有数据过来或客户端的socket连接被断开
                char buffer[1024];
                memset(buffer, 0, sizeof(buffer));

                // 读取客户端的数据。  通过 iSzie 的值判断客户端是否断开连接 iSize <= 0 
                ssize_t isize=read(eventfd, buffer, sizeof(buffer));

                // 发生了错误或socket被对方关闭
                if (isize <= 0)
                {
                    printf("client(eventfd=%d) disconnected.\n",eventfd);

                    close(eventfd);  // 关闭客户端的socket。

                    FD_CLR(eventfd, &readfdset);  // 从集合中移去客户端的socket。

                    // 重新计算maxfd的值,注意,只有当eventfd==maxfd时才需要计算。
                    if (eventfd == maxfd)
                    {
                        for (int ii=maxfd; ii>0; ii--)
                        {
                            if (FD_ISSET(ii, &readfdset))
                            {
                                maxfd = ii; break;
                            }
                        }

                        printf("maxfd=%d\n", maxfd);
                  }

                  continue;
                }

                printf("recv(eventfd = %d, size=%d):%s\n",eventfd, isize, buffer);

                // 把收到的报文发回给客户端。
                write(eventfd, buffer, strlen(buffer));
            }
        }
    }

  return 0;
}

// 初始化服务端的监听端口。
int initserver(int port)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        printf("socket() failed.\n"); return -1;
    }

    // Linux如下  解决重复开启程序时,报端口占用问题
    int opt = 1; unsigned int len = sizeof(opt);
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, len);
    setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &opt, len);

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);

    if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0 )
    {
        printf("bind() failed.\n"); close(sock); return -1;
    }

    if (listen(sock,5) != 0 )
    {
        printf("listen() failed.\n"); close(sock); return -1;
    }

    return sock;
}

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

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

相关文章

Word怎么生成目录?4个方法快速生成目录!

案例&#xff1a;Word怎么生成目录 【想问下大家在使用Word时是怎么生成目录的呀&#xff1f;正在写毕业论文的我真的很急&#xff01;感谢&#xff01;】 Word作为我们常用的办公软件&#xff0c;为我们的提供了很多便利。生成目录是在Word文档中创建一个方便导航的索引。 …

Go语言基于Viper封装实现

基于Viper的封装 Viper是什么 Viper是一个用于Go语言应用程序的配置管理库。它提供了一种简单而灵活的方式来处理应用程序的配置&#xff0c;支持多种格式的配置文件&#xff0c;并提供了一组API来读取和使用这些配置。 Viper支持多种配置文件格式&#xff0c;包括JSON、TOML、…

C learning_14 指针篇之破解版

目录 1. 指针是什么 指针变量 2. 指针和指针类型 那指针类型的意义是什么&#xff1f; 3. 野指针 规避野指针 4. 指针运算 指针-整数 指针-指针 指针的关系运算(比较指针的大小) 5. 指针和数组 6. 二级指针 7. 指针数组 1. 指针是什么 指针是C语言中一种非常重要的…

2023年美国大学生数学建模竞赛E题光污染解题全过程文档及程序

2023年美国大学生数学建模竞赛 E题 光污染 原题再现&#xff1a; 背景   光污染被用来描述任何过度或不良地使用人造光。我们所说的一些光污染现象包括光侵入、过度照明和光杂波。在大城市&#xff0c;这些现象最容易被观测到的是太阳落山后天空中的一道辉光; 然而&#xf…

网课录屏怎么录?分享3个录制网课方法!

案例&#xff1a;怎样才能录制清晰流畅的网课视频&#xff1f; 【我最近在网上购买了一个配音的课程&#xff0c;每天晚上8点老师会进行讲课&#xff0c;但是课程没有回放。我想通过录屏的方式将课程内容录制下来&#xff0c;那怎样才能录制清晰流畅的网课视频呢&#xff1f;】…

初识Mybatis -- Mybatis入门保姆级教程(一)

文章目录 前言一、认识mybatis1.mybatis是什么2.jdbc缺点3.mybatis优点4.MyBatis框架解决JDBC劣势 二、mybatis入门案例1.需求分析与操作步骤2.创建表tb_user、实体类和Mapper接口3.创建maven模块re_mb_demon4.在pom.xml配置文件中导入相关依赖5. 配置相关文件6.创建并编写模拟…

百汇BCR:十个外汇交易常见问题解析

外汇交易是一种受到市场欢迎的投资方式&#xff0c;参与交易很容易&#xff0c;但想要盈利却很难。特别是一些细节问题容易被忽略&#xff0c;百汇BCR小编整理了十个外汇交易常见问题供有需要的投资者借鉴学习。 问题一、外汇开户需要哪些资料&#xff1f; 个人相关信息资料、…

重新理解RocketMQ Commit Log存储协议

最近突然感觉&#xff1a;很多软件、硬件在设计上是有root reason的&#xff0c;不是by desgin如此&#xff0c;而是解决了那时、那个场景的那个需求。一旦了解后&#xff0c;就会感觉在和设计者对话&#xff0c;了解他们的思路&#xff0c;学习他们的方法&#xff0c;思维同屏…

JSON格式化工具

格式化JSON有多种方式&#xff0c;选两种。 1.在线JSON格式化 我喜欢用这个网站&#xff1a;在线JSON工具 2.Notepad插件 需要插件 - JSON Viewer。 安装方式&#xff1a; 2.1 点击工具栏“插件”-选择“插件管理…”,弹出插件管理窗口&#xff0c;在“可用”tab页&#xf…

一般人不会告诉你的FP独立站低成本运营内幕

不少朋友听说FP独立站盈利比较高之后就兴致勃勃地跟着别人建站、装修、选品……但在此之前有没有仔细想过&#xff0c;一个成功的F牌独立站到底要怎么运营&#xff1f;而且是&#xff0c;怎么低成本运营&#xff1f;现在我就跟大家分享一下&#xff0c;希望有想法入局F牌独立站…

CorelDRAW2023最新v24.4.0.623中文稳定版

图形设计软件CorelDRAW2023最新版下载矢量图形制作工具,CorelDRAW&#xff08;简称CDR&#xff09;是一款专业的图形设计软件。该软件是Corel公司开发的一款功能强大的专业平面设计软件、矢量设计软件、矢量绘图软件。这款矢量图形制作工具软件广泛应用于商标设计、标志制作、封…

史上最全类和对象 ,只要你认真看完C++类和对象,分分钟钟都吊打面试官【 C++】

文章目录 基础篇面向过程和面向对象类的引入类的定义类的访问限定符类的封装类的作用域类的实例化类对象模型如何计算类对象的大小类对象的存储方式猜测对象中包含类的各个成员代码只保存一份&#xff0c;在对象中保存存放代码的地址只保存成员变量&#xff0c;成员函数存放在公…

10.计算机基础-操作系统面试题—计算机系统概述、进程管理

本文目录如下&#xff1a; 计算机基础-操作系统 面试题一、计算机系统概述CPU 内核态 和 用户态 的区别&#xff1f;从 用户态 切换到 内核态 的常见方法&#xff1a; 二、进程管理线程 和 进程 的区别&#xff1f;协程 (goroutine) 和 线程的区别?进程有哪些状态&#xff1f;…

如果一定要在C++和JAVA中选择,是C++还是java?

前言 C和Java都是广泛应用于软件开发领域的高级编程语言。它们都有着各自的优势和适用场景&#xff0c;因此在进行选择时需要考虑到具体的需求和使用情况。 首先&#xff0c;C是一种面向对象的编程语言&#xff0c;它允许程序员直接控制计算机硬件&#xff0c;而且拥有较低的…

【分享】免费并集多个人工智能于一体的在线使用网站

哈喽&#xff0c;大家好&#xff0c;我是木易巷~ 今天来给大家分享一个集ChatGPT、GPT4、Claude等人工智能于一体的在线使用网站——Poe。 地址&#xff1a;https://poe.com 以下是一个汇集了目前热门人工智能工具的网站&#xff0c;只需使用一个神奇的工具&#xff0c;通过邮…

Todoist 的 10 种最佳替代品(功能、优点、缺点、定价)

寻找最好的 Todoist 替代品&#xff1f; Todoist对项目管理者来说并不陌生&#xff0c;它自 2007 年以来就一直很出名&#xff01; ……但是作为老牌的组织类应用程序之一&#xff0c;现在再用它是否会觉得有点不顺手&#xff1f; 虽然它为了更好适应当今的工作节奏&#xf…

java 餐饮培训平台系统Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

一、源码特点 java 餐饮培训平台系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&…

Redis学习---05

一、Redis集群搭建&#xff0c;Redis主从复制&#xff0c;读写分离 默认情况下每台redis服务器都是主节点。 (1) 主从复制&#xff1a;是指将一台redis服务器的数据&#xff0c;复制道其他redis服务。前者成为主节点&#xff0c;后者成为从节点。默认情况下每一台redis服务器…

matlab编程基础

1数据结构 1.1常量与变量 变量命名第一个字符必须为字母&#xff0c;变量可以由数字、字母、下划线组成&#xff0c;区分大小写。 常量&#xff1a; 1.2 数值型数据 双精度&#xff1a;64位 智能存储15位左右十进制数 double() 单精度 &#xff1a;32位&#xff0c;single…

C++ 类和对象下 [补充]

文章目录 友元内部类内部类是外部类的天生友元 匿名对象匿名对象的特性 友元 友元函数 重载operator<< 输出自定义类型 比如日期类的这个重载&#xff0c;就是解决类外访问私有变量的例子&#xff0c;设计成了友元函数。 说明&#xff1a; 友元函数可访问类的私有和保护成…