服务器搭建(TCP套接字)-select版(服务端)

一、select头文件

#include <sys/select.h>

二、select原型

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

    select() 是一个系统调用函数,用于在多个文件描述符上进行 I/O 多路复用。通过 select() 函数,可以监视多个文件描述符的状态,以确定是否有读写事件准备就绪

多路复用:

  • “多路”指的是多个来源或通路。它表示可以同时处理来自多个不同源的 I/O 事件
  • "复用"指的是复用一个线程或进程来同时处理多个 I/O 事件。它允许在单个线程或进程中监视和处理多个文件描述符的读写操作,而不需要为每个事件创建一个独立的线程或进程

入参:

  • nfds:要检查的最大文件描述符值加 1。它表示 select 函数需要扫描的文件描述符范围,即从 0 到 nfds-1 的文件描述符将被监视。
  • readfds:用于检查可读性的文件描述符集合。
  • writefds:用于检查可写性的文件描述符集合。
  • exceptfds:用于检查异常条件的文件描述符集合。
  • timeout:超时时间,指定 select() 调用的最长等待时间。

select() 函数会阻塞当前进程,直到满足以下条件之一:

  • 有一个或多个文件描述符准备好进行读操作。
  • 有一个或多个文件描述符准备好进行写操作。
  • 发生了异常情况,如带外数据到达。

返回值:

  • 如果返回值大于 0:表示有文件描述符就绪,且返回值是就绪文件描述符的总数。
  • 如果返回值等于 0:表示超时,即在指定的超时时间内没有文件描述符就绪。
  • 如果返回值等于 -1:表示出现错误,可以通过查看 errno 变量来获取具体的错误信息。

2.1、fd_set

    fd_set 是一个数据结构,用于表示文件描述符的集合。它是一个位图,每个文件描述符在 fd_set 中占据一个位,用于标识该文件描述符的状态。

typedef struct fd_set {unsigned int fd_count;      // 文件描述符的数量int fd_array[FD_SETSIZE];   // 文件描述符数组
} fd_set;

其中,fd_count 表示文件描述符的数量,fd_array 是一个数组,用于存储文件描述符的值。

fd_set 数据结构是一个固定大小的数组,其大小由宏 FD_SETSIZE 定义。在大多数系统中,FD_SETSIZE 的默认值是 1024,因此 fd_set 可以容纳的文件描述符数量通常是有限的。如果需要监听更多的文件描述符,可能需要对 FD_SETSIZE 进行修改或使用其他更高效的多路复用机制。

fd_set 提供了一些宏函数来操作文件描述符集合,常用的宏函数有:

  • FD_ZERO(fd_set *set):将文件描述符集合中的所有位清零

  • FD_SET(int fd, fd_set *set):将指定的文件描述符加入文件描述符集合。

  • FD_CLR(int fd, fd_set *set):从文件描述符集合中移除指定的文件描述符。

  • FD_ISSET(int fd, fd_set *set)检查指定的文件描述符是否在文件描述符集合中。

2.2、timeval

timeval 是一个结构体,用于表示时间值(time value)。

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

这个参数有以下三种可能:

  • 永远等待
    仅在一个描述符准备好I/O时才返回,为此,我们可以把该参数置为空指针
  • 等待一段固定时间
    在有一个描述符准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数
  • 根本不等待
    检查描述符后立即返回,这称为"轮询"。该参数必须指向一个timeval结构,并且其中的定时器值(秒数和微秒数)必须为0.

三、代码实现

服务端使用select函数的基本流程:

1、创建并初始化套接字:服务端需要创建一个监听套接字,用于接受客户端的连接请求。同时,需要将监听套接字添加到select监视的文件描述符集中。

2、设置文件描述符集:在使用select之前,需要将所有需要监视的文件描述符(包括监听套接字和已连接的客户端套接字)添加到文件描述符集中。可以使用FD_SET宏将套接字添加到集合中。

3、调用select函数:使用select函数开始监听文件描述符集中的事件。select函数会阻塞程序执行,直到集合中的文件描述符有可读、可写或异常事件发生。

4、处理就绪的文件描述符:当select函数返回后,需要遍历文件描述符集,确定哪些套接字上发生了事件。可以使用FD_ISSET宏来检查文件描述符是否准备就绪。

5、处理连接请求:如果监听套接字(通常是服务器套接字)准备就绪,表示有新的客户端连接请求。此时,可以调用accept函数接受客户端的连接,并将其加入到文件描述符集中进行监视。

6、处理客户端数据:如果已连接的客户端套接字准备就绪,表示有数据可读或可写。此时,可以使用recv函数读取客户端发送的数据,或使用send函数向客户端发送数据。

7、循环监听:在处理完所有就绪的文件描述符后,可以再次调用select函数,继续监听新的事件。可以使用循环来反复执行这个过程,以实现持续的事件驱动。

#include <iostream>
//socket
#include <sys/types.h>
#include <sys/socket.h>
//close
#include <unistd.h>
//exit
#include <stdlib.h>
//perror
#include <stdio.h>
//memset
#include <string.h>
//htons
#include <arpa/inet.h>
//select
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>#define PORT 8596
#define MESSAGE_SIZE 1024
#define FD_SIZE 1024int main(){int ret=-1;int socket_fd=-1;int accept_fd=-1;int accept_fds[FD_SIZE]={-1,};//可用fd索引int canUseFDIndex=-1;//最大的fd索引int maxFDIndex=0;int max_fd=-1;fd_set fd_sets;int ready=0;int backlog=10;int flags=1;struct sockaddr_in local_addr,remote_addr;//create socketsocket_fd=socket(AF_INET,SOCK_STREAM,0);if(socket_fd == -1){perror("create socket error");exit(1);}//set option of socketret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags));if ( ret == -1 ){perror("setsockopt error");}//set socket addresslocal_addr.sin_family=AF_INET;local_addr.sin_port=htons(PORT);local_addr.sin_addr.s_addr=INADDR_ANY;bzero(&(local_addr.sin_zero),8);//bind socketret=bind(socket_fd, (struct sockaddr *)&local_addr,sizeof(struct sockaddr_in));if(ret == -1){perror("bind socket error");exit(1);}ret=listen(socket_fd, backlog);if(ret ==-1){perror("listen error");exit(1);}//重置max_fd;max_fd=socket_fd;for(int i=0;i < FD_SIZE;i++){accept_fds[i]=-1;}//loop to accept clientfor(;;){//清空FD_ZERO(&fd_sets);//socket_fd加入集合FD_SET(socket_fd,&fd_sets);//同步集合中最大的文件描述符for(int j=0;j<maxFDIndex;j++){if(accept_fds[j] !=-1){if(accept_fds[j] > max_fd){max_fd=accept_fds[j];}//重新加入需要监听的文件描述符到集合里FD_SET(accept_fds[j],&fd_sets);}}struct timeval timeout;timeout.tv_sec = 5;  // 设置超时时间为 5 秒timeout.tv_usec = 0;ready=select(max_fd+1,&fd_sets,nullptr,nullptr,timeout);if(ready<0){perror("error in select");break;}else if(ready==0){perror("select time out!");continue;}else if(ready){printf("ready:%d\n",ready);//socket有新的连接请求if(FD_ISSET(socket_fd,&fd_sets)){//找到没有使用的位置int k=0;for(;k<FD_SIZE;k++){if(accept_fds[k] == -1){canUseFDIndex=k;break;}}if(k==FD_SIZE){perror("the connected is full!\n");continue;}socklen_t addrlen = sizeof(remote_addr);accept_fd=accept(socket_fd,( struct sockaddr *)&remote_addr, &addrlen);accept_fds[canUseFDIndex]=accept_fd;if(canUseFDIndex+1 >maxFDIndex){maxFDIndex=canUseFDIndex+1;}//同步最大文件描述符if(accept_fd > max_fd){max_fd=accept_fd;}}for(int p=0;p<maxFDIndex;p++){if(accept_fds[p] !=-1 && FD_ISSET(accept_fds[p],&fd_sets)){char in_buf[MESSAGE_SIZE]={0,};memset(in_buf,0,MESSAGE_SIZE);//read dataint ret =recv(accept_fds[p], (void*)in_buf, MESSAGE_SIZE, 0);if(ret ==0){close(accept_fds[p]);accept_fds[p]=-1;break;}printf("receive data:%s\n",in_buf);send(accept_fds[p], (void *)in_buf, MESSAGE_SIZE, 0);}}}}printf("quit server....");close(socket_fd);return 0;
}

四、总结

select是一种用于多路复用(multiplexing)的系统调用,常用于实现异步I/O操作。它在编程中具有一些优点和缺点:

优点:

  • 高效的事件驱动:select允许程序同时监视多个文件描述符(如套接字),并在其中任何一个文件描述符准备好进行I/O操作时通知程序。这种事件驱动的方式可以提高程序的效率,避免了不必要的忙等待。

  • 跨平台兼容性:select是标准的POSIX接口,因此在大多数主流操作系统上都有良好的支持。这使得可以使用相同的代码在不同的平台上进行开发,提高了可移植性。

  • 简单易用:select的接口相对简单,适用于处理少量的文件描述符。它使用简洁的参数和返回值,易于理解和使用。对于简单的I/O多路复用需求,select是一个较为直观的选择。

缺点:

  • 低效的扩展性:select的一个主要缺点是其在处理大量文件描述符时的低效性。它采用线性扫描的方式遍历所有待监视的文件描述符,当文件描述符数量较大时,性能会明显下降

  • 需要维护文件描述符集:使用select需要维护一个文件描述符集,包含所有要监视的文件描述符。这要求开发人员在程序中维护一个数据结构来管理这些文件描述符,增加了一定的复杂性。

  • 不支持高级特性:相比其他更高级的I/O多路复用机制(如epoll、kqueue等),select的功能相对有限。它不支持一些高级特性,如边缘触发(edge-triggered)模式和自动扩展等。

总体而言,select是一种简单易用且可移植的多路复用机制,适用于处理少量文件描述符的情况。然而,在高并发和大规模的I/O操作中,可能需要考虑其他更高效和功能更强大的替代方案。

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

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

相关文章

JavaWeb后端开发 JWT令牌解析 登录校验 通用模板/SpringBoot整合

目录 实现思路 会话跟踪的三个方案--引出Jwt令牌技术 1.访问cookie的值,在同一会话的不同请求之间共享数据 2.session 3.现代普遍采用的令牌技术--JWT令牌 JWT令牌技术 ​第一步--生成令牌 1.引入依赖 2.生成令牌 第二步--校验令牌 第三步--登录下发令牌 需要解决的…

黑马JVM总结(二十三)

&#xff08;1&#xff09;字节码指令-init 方法体内有一些字节&#xff0c;对应着将来要由java虚拟机执行方法内的代码&#xff0c;构造方法里5个字节代码&#xff0c;main方法里有9个字节的代码 java虚拟机呢内部有一个解释器&#xff0c;这个解释器呢可以识别平台无关的字…

四种自动化测试模型实例及优缺点

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; 一&#xff0c;线性测试 1.概念&#xff1a; 通过录制或编写对应应用程序的操作步骤产生的线性脚本。单…

分工是财富的秘密

友情提示&#xff1a;这是一篇干货&#xff0c;需要深度阅读 前几天&#xff0c;我看到一个做自媒体的大 V 说了这么一个观点&#xff1a;分工是财富的秘密。 然后&#xff0c;我根据这句话&#xff0c;自己做了点引申。 分工是财富的秘密。分工越细&#xff0c;赚钱机会越多&a…

OpenGL之坐标系统

将坐标变换为标准化设备坐标&#xff0c;接着再转化为屏幕坐标的过程通常是分步进行的&#xff0c;也就是类似于流水线那样子。在流水线中&#xff0c;物体的顶点在最终转化为屏幕坐标之前还会被变换到多个坐标系统(Coordinate System)。将物体的坐标变换到几个过渡坐标系(Inte…

从Python代码到诗

&#x1f433;序言 在Python社区&#xff0c;没有强制的编码标准&#xff0c;这虽然赋予了开发者更多的自由&#xff0c;但也导致代码风格不一致性。使得部分代码变得晦涩难懂&#xff0c;本文将探讨一系列的开发技巧和最佳实践&#xff0c;开发出优雅的Python脚本。 1、参数接…

ElasticSearch(三)

1.数据聚合 聚合&#xff08;aggregations&#xff09;可以让我们极其方便的实现对数据的统计、分析、运算。例如&#xff1a; 什么品牌的手机最受欢迎&#xff1f; 这些手机的平均价格、最高价格、最低价格&#xff1f; 这些手机每月的销售情况如何&#xff1f; 实现这些…

laravel框架 - 消息队列如何使用

业务场景&#xff1a;项目里边有很多视频资源需要上传到抖音资源库&#xff0c;通过队列一条一条上传。 参考实例&#xff1a;发送邮件&#xff0c;仅供参考 (1)创建任务【生成任务类】 在你的应用程序中&#xff0c;队列的任务类都默认放在 app/Jobs 目录下。如果这个目录不存…

解决ubuntu系统python2.7安装uwsgi报错

背景 因为项目老旧&#xff0c;仍需使用python2.7&#xff0c;仍需要使用pip2 安装依赖。在安装uwsgi的时候&#xff0c;报错。 错误一 Building wheel for uwsgi (setup.py) ... error ERROR: Command errored out with exit status 1: command: /usr/bin/python2 -u -…

vulhub打靶第三周

第三周 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/chronos-1,735/ 环境折磨导致做晚了&#xff0c;再加上期末的考试多耽搁下来了&#xff0c;然后就是辗转反侧打比赛&#xff0c;拖了这么久&#xff0c;时隔三个月重新开打 因为陆陆续续打了两次&#xff0c;所…

【Java】Java中对List进行排序

探讨几种Java对List进行排序的方法。 使用Collections.sort()方法 Java中的Collections.sort()方法是对List进行排序的最常用方法。它使用TimSort算法&#xff08;是一种稳定的&#xff0c;基于合并的排序算法&#xff0c;是插入排序和归并排序的混合体&#xff09;&#xff…

[winerror 5] 拒绝访问。: ‘..\\data‘解决方案

使用Jupyter Notebook学习深度学习时出现错误如下&#xff1a;[winerror 5] 拒绝访问。: ‘…\data’ 解决方法&#xff1a; 打开anaconda3找到对应环境的python.exe 点开属性&#xff0c;点安全&#xff0c;选择如下&#xff1a; 点编辑&#xff0c;选择User&#xff0c;勾…

9、DVWA——XSS(Stored)

文章目录 一、存储型XSS概述二、low2.1 源码分析2.2 通关分析 三、medium3.1 源码分析3.2 通关思路 四、high4.1 源码分析4.2 通关思路 一、存储型XSS概述 XSS&#xff0c;全称Cross Site Scripting&#xff0c;即跨站脚本攻击&#xff0c;某种意义上也是一种注入攻击&#xff…

Touch命令使用指南:创建、更新和修改文件时间戳

文章目录 教程&#xff1a;touch命令的使用指南一、介绍1.1 什么是touch命令&#xff1f;1.2 touch命令的作用1.3 touch命令的语法 二、基本用法2.1 创建新文件2.2 更新文件时间戳2.3 创建多个文件2.4 修改文件访问时间2.5 修改文件修改时间2.6 修改文件创建时间 三、高级用法3…

李航老师《统计学习方法》第1章阅读笔记

1.1 统计学习 统计学习的特点 统计学习&#xff1a;计算机基于数据构建概率统计模型并运用模型对数据进行预测与分析 现在人们提及机器学习时&#xff0c;往往指统计机器学习&#xff0c;所以可以认为本书介绍的是机器学习方法 统计学习的对象 统计学习研究的对象是数据(data)…

《Web安全基础》09. WAF 绕过

web 1&#xff1a;基本概念1.1&#xff1a;DoS & DDos1.2&#xff1a;CC 攻击1.3&#xff1a;扫描绕过方式 2&#xff1a;WAF 绕过2.1&#xff1a;信息收集阶段2.2&#xff1a;漏洞发现阶段2.3&#xff1a;权限控制阶段2.3.1&#xff1a;密码混淆2.3.2&#xff1a;变量覆盖…

Langchain-chatchat本地部署

Langchain-chatchat本地部署 参考官网 环境配置 conda安装 minicoda下载地址 安装时注意勾选上添加环境变量。安装完成之后使用conda --version测试一下版本。 环境创建 先配置一下conda的镜像地址&#xff08;使用阿里的靠谱一些&#xff09;&#xff0c;这里要修改一下…

解决方案:TSINGSEE青犀+智能分析网关助力智慧仓储智能化监管

为全面保障物流仓储的安全性与完整性&#xff0c;解决仓库管理难题&#xff0c;优化物流仓储方式&#xff0c;提升仓储效率&#xff0c;降低人工成本&#xff0c;旭帆科技推出智慧仓储AI视频智能分析方案&#xff0c;利用物联网、大数据、云计算等技术&#xff0c;对仓储管理进…

IDEA2023新UI回退老UI

idea2023年发布了新UI&#xff0c;如下所示 但是用起来真心不好用&#xff0c;各种位置也是错乱&#xff0c;用下面方法可以回退老UI

abc 321 c

#include <bits/stdc.h> using namespace std; using ll long long; using VI vector<int>; using PII pair<int , int>; int k; int main(){vector<ll>r;//数位枚举一下 0 到 9 哪些数字存在for(int i 1 ; i < (1 << 10) ; i){ll t 0;f…