select

2.1 简介

​ select函数可以用于实现高效的多路复用 I/O,同时处理多个文件描述符的事件,包括监听可读、可写和异常条件,具有阻塞和非阻塞模式,并可以设置超时时间。这使得程序能够高效地处理并发任务,提高性能和响应性。

2.2 select函数

头文件:#include <sys/select>

函数原型:int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);

函数参数:

nfds:监视文件描述符的最大值加一(文件描述符集合中所有的文件描述符的最大值+1)

readfds:指向一个文件描述符集合,用于监视是否有数据可读。

writefds:指向一个文件描述符集合,用于监视是否可以写入数据。

exceptfds:指向一个文件描述符集合,用于监视是否有异常情况。

timeout:指向一个struct timeval结构体,表示超时时间,即select函数最多等待的时间。

结构体struct timeval 的成员:

​ time_t tv_sec:表示秒数。

​ suseconds_t tv_usec:表示微妙数。

函数返回值

​ 成功返回准备就绪的文件描述符总数。

​ 监听超时则返回0。

​ 发生错误返回-1。

2.3 select的几个宏函数

2.3.1将指定的一个文件描述符从集合中清除

​ 函数原型:FD_CLR(int fd, fd_set *set);

​ 函数参数:fd为指定的文件描述符,set为指定的文件描述符集合

2.3.2 将指定的文件描述符集合清空,所有位置都置为0

​ 函数原型:FD_ZERO(fd_set *set);

​ 函数参数:set为指定的文件描述符集合

2.3.3 将指定的文件描述符加入到指定的集合中

​ 函数原型:FD_SET(int fd, fd_set *set);

​ 函数参数:fd为指定的文件描述符,set为指定的文件描述符集合

2.3.4 判断指定的文件描述符是否在某个文件描述符集合中

​ 函数原型:FD_ISSET(int fd, fd_set *set);

​ 函数参数:fd为指定的文件描述符,set为指定的文件描述符集合

2.4 使用select创建一个多路io复用的服务端:

​ 第一步:创建套接字。

​ 第二步:准备一些变量和常量,用于记录最大文件描述符,客户端发来的数据,循环次数,条件判断等待。

​ 第三步:初始化本地(服务器)地址结构体sockaddr_in。

​ 第四步:将套接字与本地地址绑定,并且建议判断一下是否绑定成功。

​ 第五步:切换为监听状态。

​ 第六步:进入一个无限循环然后持续监听。

​ 马上进入循环时初始化文件描述符集合,进入后将刚才监听的文件描述符添加到里面。

​ 在所有已连接的客户端文件描述符集合中寻找最大的文件描述符,然后记录下来。

​ 使用select函数对整个文件描述符集合进行监听,如果监听失败则退出程序,如果有文件描述符可操作, 则进行具体操作。

​ 当select监听有变化则检测监听的套接字是否有新的连接请求,如果有则使用accept接收连接,并且将accept返回的已经和客户端建立连接的描述符加入到已连接的客户端数组中。

​ 处理客户端的请求。遍历已连接的客户端数组,逐个判断每个客户端文件描述符数组中的元素是否中文件描述符集合中,如果在则代表有数据可读。

​ 从客户端接收数据,并且进行处理。如果无法接收数据则关闭客户端文件描述符然后将其从描述符集合中移除。否则就对客户端发来的数据进行相关操作。

​ 第七步:当循环结束后,记得关闭所有已连接的客户端文件描述符和服务器文件描述符。

2.5 使用select创建的多路io的服务端代码示例

点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h> #define MAX_CLIENTS 100 //最大客户端描述符数
#define BUF_SIZE 128 //字符串数组的最大长度 int main() { int client_fds[MAX_CLIENTS]; //用于存储客户端文件描述符,用来辅助文件描述符集使用 int server_fd; //服务端文件描述符(用于监听) fd_set readfds; //可读文件描述符集 char data[BUF_SIZE];//用于存储接收来自客户端的信息 int maxfd; //最大文件描述符 int i;//下面可能会用到循环,它用来循环 int retval; //用于记录select的值 //创建套接字,ipv4,tcp server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == -1) { ​ printf("创建套接字失败!\n"); ​ exit(-1); } //分别用于存储服务端和客户端的地址信息 struct sockaddr_in server_addr, client_addr; //初始化本地地址信息 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(10066); server_sddr.sin_addr.s_addr = htonl(INADDR_ANY); //将套接字与本地服务器地址绑定 int ret;//用于检测绑定或者切换至监听是否成功 ret = bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (ret == -1) { ​ printf("本地地址与套接字失败!\n"); ​ exit(-1); } //切换监听 ret = listen(server_fd, MAX_CLIENTS); if (ret == -1) { ​ printf("切换监听态失败!\n"); ​ exit(-1); } printf("服务器开始监听,监听端口:10066 监听地址:任意\n"); //因为接下来的select需要接收一个集合的最大描述符,先用一个已有的文件描述符给他赋值 maxfd = server_fd; //初始化客户端文件描述符集合,-1代表可用 for (i = 0; i < MAX_CLIENTS; i++) { ​ client_fds[i] = -1; } FD_ZERO(&readfds); //初始化可读文件描述符集 while (1) { ​ FD_SET(server_fd, &readfds); //将当前监听的描述符加入到里面 ​ //将已连接的文件描述符加入到集合中 ​ for (i = 0; i < MAX_CLIENTS; i++) { ​ int client_fd = client_fds[i]; ​ if (client_fd != -1) { ​ //不等于-1则代表在被使用中代表已连接 ​ FD_SET(client_fd, &readfds); ​ if (client_fd > maxfd) { ​ //如果当前已连接的文件描述符比之前的最大描述符要大,记得交换 ​ //因为select函数接收的是集合中值最大的 ​ maxfd = client_fd; ​ } ​ } ​ } ​ //使用select函数进程监听整个集合 ​ retval = select(maxfd + 1, &readfds, NULL, NULL, NULL); ​ if (retval == -1) { ​ //等于-1代表监听失败 ​ printf("监听失败!!!\n"); ​ exit(-1); ​ } ​ else if (retval > 0) { ​ //大于0代表描述符集readfds有变化 ​ //检测socket_fd是否有新的链接 ​ if (FD_ISSET(server_fd, &readfds)) { ​ socklen_t client_len = sizeof(client_addr); ​ int client_fd = accept(server_fd,(struct sockaddr *)&client_addr,&client_len); ​ if (client_fd == -1) { ​ printf("接收链接失败!!!\n"); ​ } ​ else { ​ //将刚才接收到的新的来自客户端的socket添加到结合中 ​ for (i = 0; i < MAX_CLIENTS; i++) { ​ if (client_fds[i] == -1) { ​ //-1则代表当前数组位置可用 ​ client_fds[i] = client_fd; ​ break; ​ } ​ } ​ } ​ } ​ //处理来自客户端的请求 ​ for (i = 0; i < MAX_CLIENTS; i++) { ​ int client_fd = client_fds[i]; ​ if (client_fd != -1 && FD_ISSET(client_fd, &readfds)) { ​ //首先数组元素是被使用的状态,并且要判断数组元素i对应的描述符是否中集合中 ​ memset(data, 0, BUF_SIZE); ​ //接收信息 ​ ssize_t n = recv(client_fd, data, sizeof(data),0); ​ if (n <= 0) { ​ printf("未能接收客户端数据!\n"); ​ close(client_fd);//关闭文件描述符 ​ client_fds[i] = -1;//记得将这个坑置为-1,因为咱们操作完了 ​ } ​ else { ​ //回应客户端 ​ printf("服务端已经接收到客户端数据:\n%s\n",data); ​ if (send(client_fd, data, sizeof(data), 0) == -1) { ​ printf("回送服务端数据失败!\n"); ​ } ​ } ​ } ​ } ​ } } //用完了记得循环关闭链接 for (i = 0; i < MAX_CLIENTS; i++) { ​ int client_fd = client_fds[i]; ​ if (client_fd != 1) { ​ close(client_fd); ​ } } close(server_fd); return 0; }

2.6 select的优缺点

select优点:

​ 1 一个进程可以支持多个客户端

​ 2 select支持跨平台

select缺点:

​ 1 代码编写困难

​ 2 会涉及到用户区到内核区的来回拷贝,销毁资源大。

​ 3 当客户端多个连接, 但少数活跃的情况, select效率较低

​ 例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低 下

4 最大支持1024个客户端连接

​ select最大支持1024个客户端连接不是有文件描述符表最多可以支持1024个文件描述符限制的, 而是由 FD_SETSIZE=1024限制的.

​ FD_SETSIZE=1024 fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核, 一般不建议这么做.

多路io复用Select [补档-2023-07-16]的更多相关文章

  1. Redis03——Redis之单线程+多路IO复用技术

    Redis 是单线程+多路IO复用技术 多路复用:使用一个线程来检查多个文件描述符的就绪状态 如果有一个文件描述符就绪,则返回 否则阻塞直到超时 得到就绪状态后进行真正的操作可以在同一个线程里执行,也 ...

  2. 多路IO复用模型--select, poll, epoll

    select 1.select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数 2.解决1024以下客户端时使用se ...

  3. Linux C++ 网络编程学习系列(2)——多路IO之select实现

    select实现多路IO 源码地址:https://github.com/whuwzp/linuxc/tree/master/select 源码说明: server.cpp: 监听127.1:6666 ...

  4. 基于select类型多路IO复用,实现简单socket并发

    还有很多缺限,如客户断开无限重复 以下转至老师博客: server: #!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = " ...

  5. Linux企业级项目实践之网络爬虫(27)——多路IO复用

    与多线程和多进程相比,I/O多路复用的最大优势是系统开销小,系统不需要建立新的进程或者线程,也不必维护这些线程和进程. 主要应用: (1)客户程序需要同时处理交互式的输入和服务器之间的网络连接 (2) ...

  6. io复用select方法编写的服务器

    摘要:io多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般都是读就绪或者写就绪),就能通知应用程序进行相应的读写操作.select函数作为io多路复用的机制,第一个参数nfds是f ...

  7. 协程与多路io复用epool关系

    linux上其实底层都基于libevent.so模块实现的,所以本质一样 gevent更关注于io和其它 epool只是遇到io就切换,而gevent其它等待也切换

  8. IO复用——select系统调用

    1.select函数 此函数用于在一段时间内,监听用户感兴趣的文件描述符上的可读.可写和异常等事件. #include<sys/select.h> int select(int nfds, ...

  9. 1高并发server:多路IO之select

     1 select A:select能监听的文件描写叙述符个数受限于FD_SETSIZE,一般为1024.单纯改变进程打开 的文件描写叙述符个数并不能改变select监听文件个数 B:解决1024 ...

  10. python3.x 多路IO复用补充asyncio

    asyncio模块是python之父写的模块,按说应该是靠谱的,python3.6版本定义为稳定版本. 说明书:https://docs.python.org/3/library/asyncio.ht ...

随机推荐

  1. 深挖 Rundll32.exe 的多种“滥用方式”以及其“独特”之处

    恶意软件作者通常会编写恶意软件模仿合法的Windows进程.因此,我们可能会看到恶意软件伪装成svchost.exe.rundll32.exe或lsass.exe进程,攻击者利用的就是大多数Windo ...

  2. mysql--read only

    问题背景: 1.在进行数据迁移和从库只读状态设置时,都会涉及到只读状态和Master-Slave主从关系设置 2.数据库参数文件默认是只读,重启数据库服务时 解决方法: 1.在my.cnf配置文件中添 ...

  3. CO40/CO41转生产订单下达时不能创建采购申请

    一.配置 CO01创建生产订单,创建时生成采购申请,改为下达时创建采购申请.通过配置,将预留/采购申请 更改为2即可. 但是CO41和CO40通过配置,并不能达到更改预留/采购申请 为2. 二.调试源 ...

  4. 【LibCurl】C++使用libcurl实现HTTP POST和GET、PUT

    libcurl简介 libcurl是一个跨平台的网络协议库,支持http, https, ftp, gopher, telnet, dict, file, 和ldap 协议.libcurl同样支持HT ...

  5. 一、linux单机版mongo安装(带密码验证)

    系列导航 一.linux单机版mongo安装(带密码验证) 二.mongo集群搭建 三.java连接mongo数据库 四.java对mongo数据库增删改查操作 五.mongo备份篇 mongoexp ...

  6. 面试重点:webpack

    webpack 熟练掌握Webpack的常用配置,能够自己构建前端环境,并进行项目优化; 001.谈谈你对webpack的看法: webpack是一个模块打包工具,可以使用它管理项目中的模块依赖,并编 ...

  7. python之HtmlTestRunner(三)中文字体乱码的情况

    使用HtmlTestRunner测试报告时,遇到中文字体无法识别的情况: 解决方案修改  \Lib\site-packages\HtmlTestRunner\result.py:def generat ...

  8. 你不知道的JavaScript APIs

    前言 在本文中,将介绍一些鲜为人知但却非常有用的API,如: Page Visibility API Web Share API Broadcast Channel API International ...

  9. UPF - Power Intent Basic

    Mainstream Low Power techniques Low Vth - 阈值电压比较低,翻转时间小,漏电流比较大,功耗大,速度快 High Vth - 阈值电压比较高,翻转时间长,漏电流比 ...

  10. 【Git】用法小记

    解决windows环境下的CRLF与unix环境下的LF问题,windows提交时CRLF=>LF,签出时LF=>CRLF,unix环境保留 git config --global cor ...