【高性能服务器】select模型

  🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于知识分享,与更多的人进行学习交流

IO多路复用就是复用一个线程,从原先一个客户端需要一个线程去调用recv询问内核数据是否已经就绪,那么多个客户端就需要多个线程,转变成现在多个客户端都用一个线程使用select/poll去统一管理,主动通知用户哪些数据已经就绪(read,write,accept等事件),所以复用了这个线程,减少了系统开销。

在客户端增加时,线程不会呈O(n)增加

关于recv和accept工作流程

accpet通过服务端文件描述符监听socket事件,当监听到READ_EVENT事件时,说明有其他网络端向此socket发送数据,触发socket读事件(三次握手中客户端会发送数据),建立TCP连接。

recv通过客户端文件描述符监听socket事件,当监听到READ_EVENT事件,处理事件,将数据读取到用户缓冲区buffer

通过IO复用,实现监听到socket事件就绪后,直接调用accpet或recv即可,直接完成TCP连接或者数据读取,两个函数不会阻塞。

可以实现单进程一对多效果,但是没有使用并发技术

处理的业务复杂度不能过高,要在极短的时间内处理若干任务,投入二次监听

IO多路复用第一版select


实现原理

select 实现多路复用的方式是,将已连接的 Socket 都放到一个监听集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,就是通过遍历监听集合的方式进行检查。

当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

所以,对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。

select 使用固定长度的 BitsMap,表示文监听集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,最多只能监听1021个用户socket,因为0、1、2是标准文件描述符。

监听集合中对应的socket位码是1,表示监听次socket,为0表示不监听

在select这种I/O多路复用机制下,我们需要把想监控的文件描述集合通过函数参数的形式告诉select,然后select会将这些文件描述符集合拷贝到内核中,我们知道数据拷贝是有性能损耗的,因此为了减少这种数据拷贝带来的性能损耗,Linux内核对集合的大小做了限制,并规定用户监控的文件描述集合不能超过1024个,同时当select返回后我们仅仅能知道有些文件描述符可以读写了,但是我们不知道是哪一个,因此必须再遍历一边找到具体是哪个文件描述符可以读写了。 


实现流程

核心接口

void FD_ZERO(fd_set *fdset) 初始化监听集合为0

void FD_SET(int fd,fd_set *fdset) 对set集合中fd对应位码设置为1

void FD_CLR(int fd,fd_set *fdset) 对set集合中fd对应位码设置为0

int bitcode=void FD_ISSET(int fd,fd_set *fdset) 查看fd在监听集合中是1还是0,并直接返回


int ready=select(int nfds, fd_set* readset, fd_set* writeset, fd_set* exeptset,struct timeval* timeout);

nfds表示被select管理的描述符个数。值为最大描述符+1.不是描述符最大值

readsetwritesetexeptset可读事件集合、可写事件集合、异常事件集合。这三者都可以填null

timeout超时时间有三种含义: 阻塞(null)、正常超时、非阻塞(0)


使用服务器测试业务:

客户端向标准输入发送小写字符串,服务端响应回复对应大写字符,"abcAS"->"ABCAS"

客户端向服务端发送关键字localtime,服务端响应回复系统时间、

代码实现

MySock.h

#ifndef _MYSOCK_H_
#define _MYSOCK_H_#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>int SOCKET(int domain, int type, int protocol);
int BIND(int sockfd, struct sockaddr* addr, socklen_t addrlen);
ssize_t RECV(int sockfd, void* buf, size_t len, int flags);
ssize_t SEND(int sockfd, void* buf, size_t len, int flags);
int CONNECT(int sockfd, struct sockaddr* addr, socklen_t addrlen);
int ACCEPT(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
int LISTEN(int sockfd, int backlog);
char* FGETS(char* s, int size, FILE* stream);
int SELECT(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,struct timeval* timeout);
int socket_init();
int return_response(int clientfd, const char* clientip);
//void strDeal(int *client_fd);// 全局变量声明
char recv_buf[1024];
char time_buf[64];
int serverFd, clientFd;
struct sockaddr_in clientAddr;
fd_set set, oset;
int client_array[1020];
int maxfd, ready;
socklen_t addrlen;
char clientip[16];
time_t tp;
ssize_t recvlen;
int toupper_flag;
#define SHUTDOWN 1
#endif

MySock.c

#include "MySock.h"int SOCKET(int domain, int type, int protocol) {int reval = socket(domain, type, protocol);if (reval == -1) {perror("socket call failed");exit(0);}return reval;
}int BIND(int sockfd, struct sockaddr* addr, socklen_t addrlen) {int reval = bind(sockfd, addr, addrlen);if (reval == -1) {perror("bind call failed");exit(0);}return reval;
}ssize_t RECV(int sockfd, void* buf, size_t len, int flags) {ssize_t reval;reval = recv(sockfd, buf, len, flags);return reval;
}ssize_t SEND(int sockfd, void* buf, size_t len, int flags) {ssize_t reval;reval = send(sockfd, buf, len, flags);if (reval == -1)perror("send call failed");return reval;
}int CONNECT(int sockfd, struct sockaddr* addr, socklen_t addrlen) {int reval = connect(sockfd, addr, addrlen);if (reval == -1) {perror("connect call failed");exit(0);}return reval;
}int ACCEPT(int sockfd, struct sockaddr* addr, socklen_t* addrlen) {int reval = accept(sockfd, addr, addrlen);if (reval == -1) {perror("accept call failed");exit(0);}return reval;
}int LISTEN(int sockfd, int backlog) {int reval = listen(sockfd, backlog);if (reval == -1) {perror("listen call failed");exit(0);}return reval;
}char* FGETS(char* s, int size, FILE* stream) {char* str;if ((str = fgets(s, size, stream)) != NULL) {return str;} else {perror("fgets call failed");exit(0);}
}int SELECT(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,struct timeval* timeout) {int reval = select(nfds, readfds, writefds, exceptfds, timeout);if (reval == -1) {perror("select call failed");exit(0);}return reval;
}int socket_init() {struct sockaddr_in sockAddr;sockAddr.sin_family = AF_INET;sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);sockAddr.sin_port = htons(8080);int sock_fd = SOCKET(AF_INET, SOCK_STREAM, 0);BIND(sock_fd, (struct sockaddr*)&sockAddr, sizeof(sockAddr));LISTEN(sock_fd, 5);return sock_fd;
}int return_response(int clientfd, const char* clientip) {char response[1024];bzero(response, sizeof(response));sprintf(response, "Hi [%s],This is TCP Server Working...\n", clientip);SEND(clientfd, response, sizeof(response), 0);
}

SelectServer.c

#include "MySock.h"int main() {bzero(recv_buf, sizeof(recv_buf));bzero(time_buf, sizeof(time_buf));bzero(clientip,sizeof(clientip));serverFd = socket_init();FD_SET(serverFd, &set); // 设置监听int i;for (i = 0; i < 1020; ++i) {client_array[i] = -1;}maxfd = serverFd;printf("Test Select Server is Running...\n");while (SHUTDOWN) {oset = set;ready = SELECT(maxfd+1, &oset, NULL, NULL, NULL);while (ready) { // 辨别就绪if (FD_ISSET(serverFd, &oset)) {addrlen = sizeof(clientAddr);clientFd =ACCEPT(serverFd, (struct sockaddr*)&clientAddr, &addrlen);inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, clientip, 16);printf("Listen Server Socket Successfully Call Accept, Client IP [%s], PORT[%d]\n",clientip, ntohs(clientAddr.sin_port));return_response(clientFd, clientip);if (maxfd < clientFd)maxfd = clientFd;for (i = 0; i < 1020; ++i)if (client_array[i] == -1) {client_array[i] = clientFd;break;}FD_SET(clientFd, &set);//新socket设置监听FD_CLR(serverFd,&oset);//处理完毕,清理就绪集合} else {// 仅处理一次客户端请求,单进程不允许客户端持续占用for (i = 0; i < 1020; ++i){if (client_array[i] != -1)if (FD_ISSET(client_array[i], &oset)){if ((recvlen = RECV(client_array[i], recv_buf, sizeof(recv_buf), 0)) >0) { // 处理客户端业务printf("Client Say:%s\n", recv_buf);if (strcmp(recv_buf, "localtime") == 0) {tp = time(NULL); // 获取时间种子ctime_r(&tp, time_buf);time_buf[strcspn(time_buf, "\n")] = '\0';printf("[%s]Response SysTime Successfully!\n", clientip);SEND(client_array[i], time_buf, strlen(time_buf) + 1, 0);bzero(time_buf, sizeof(time_buf));} else {toupper_flag = 0;while (recvlen > toupper_flag) {recv_buf[toupper_flag] = toupper(recv_buf[toupper_flag]);++toupper_flag;}printf("[%s]Response Toupper Successfully!\n", clientip);SEND(client_array[i], recv_buf, recvlen, 0);bzero(recv_buf, sizeof(recv_buf));}} else if (recvlen == 0) {FD_CLR(client_array[i], &set); // 删除监听close(client_array[i]);client_array[i] = -1;printf("Client is Exiting, Delete Listen Item.\n");}FD_CLR(client_array[i],&oset);//处理完毕,清理就绪集合break;}}}ready--;}}printf("Server is Over\n");close(serverFd);return 0;
}

Client.c

#include "MySock.c"//客户端源码编写,连接服务器成功,服务器反馈信息#define _IP "xxx.xxx.xxx.xxx"
#define _PORT 8080
int main()
{struct sockaddr_in ServerAddr;bzero(&ServerAddr,sizeof(ServerAddr));ServerAddr.sin_family=AF_INET;ServerAddr.sin_port=htons(_PORT);inet_pton(AF_INET,_IP,&ServerAddr.sin_addr.s_addr);int Myfd=SOCKET(AF_INET,SOCK_STREAM,0);//看需求决定是否要绑定char Response[1024];//存放服务端反馈信息ssize_t recvlen;bzero(Response,sizeof(Response));char sendbuf[1024];if((CONNECT(Myfd,(struct sockaddr *)&ServerAddr,sizeof(ServerAddr)))==0){while(1){    if((recvlen=RECV(Myfd,Response,sizeof(Response),0))>0){printf("%s\n",Response);}printf("Please Type Some text:");//读取标准输入发送给服务端FGETS(sendbuf,sizeof(sendbuf),stdin);    sendbuf[strcspn(sendbuf,"\n")]='\0';SEND(Myfd,sendbuf,sizeof(sendbuf),0);}}close(Myfd);printf("Client is Over\n");return 0;
}
运行结果

IO多路复用实现使用一个进程实现多个客户端的统一管理

select模式优缺点

优点

1.可以通过简单的代码实现一对多效果, 比较轻量

2.select模型拥有较强的兼容性,各个平台和语言都有实现

3.支持微秒级别的定时阻塞监听,如果对时间精度有需求,select可以满足

4.较为适合监听数量较小(局域网)等场景

缺点:

1.监听数量较小,最大只能监听1024,无法满足 高并发需求

2.轮询问题, 随着轮询数量的增长,IO处理性能呈线性下降

3.用户需要对传出传出监听集合进行分离设置

4.select只返回就绪的数量,没有反馈就绪的socket,需要用户自行遍历查找,开销较大

5.select可以监听的事件数量较少,select设置监听是批处理以集合为单位的无法对不同的socket 设置不同的事件监听

6.select多轮使用会出现大量重复的拷贝开销和挂载监听开销

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

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

相关文章

Debug-017-elementUI-el-cascader组件首次选择选项不触发表单的自定义校验

前情提要&#xff1a; 今天维护一个表单校验的时候发现一件事情&#xff0c;就是在表单中使用了 el-cascader组件&#xff0c;希望根据接口返回数据去动态校验一下这里面的选项&#xff0c;符合逻辑就通过自定义的表单校验&#xff0c;不符合就在这一项的下面标红提示。做的时候…

Java后端每日面试题(day1)

目录 JavaWeb三大组件依赖注入的方式Autowire和Resurce有什么区别&#xff1f;Spring Boot的优点Spring IoC是什么&#xff1f;说说Spring Aop的优点Component和Bean的区别自定义注解时使用的RetentionPolicy枚举类有哪些值&#xff1f;如何理解Spring的SPI机制&#xff1f;Spr…

初阶数据结构二叉树练习系列(1)

这个系列的文章将带大家一起刷题&#xff0c;并且总结思路 温馨提示&#xff1a;本篇文章里的练习题仅适合刚学完二叉树的小白使用 相同的树 思路 情况分析&#xff1a;第一种情况&#xff1a;两棵树都为空 → 返回true 第二种情况&am…

七、MyBatis-Plus高级用法:最优化持久层开发-个人版

七、MyBatis-Plus高级用法&#xff1a;最优化持久层开发 目录 文章目录 七、MyBatis-Plus高级用法&#xff1a;最优化持久层开发目录 一、MyBatis-Plus快速入门1.1 简介1.2 快速入门回顾复习 二、MyBatis-Plus核心功能2.1 基于Mapper接口CRUDInsert方法Delete方法Update方法Se…

Elasticsearch中的post_filter后置过滤器技术

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

数据结构笔记第3篇:双向链表

1、双向链表的结构 注意&#xff1a;这里的 "带头" 跟前面我们说的 "头结点" 是两个概念&#xff0c;实际前面的在单链表阶段称呼不严谨&#xff0c;但是为了同学们更好的理解就直接称为单链表的头结点。 带头链表里的头结点&#xff0c;实际为 "哨兵…

数字IC设计-VCS和Verdi的使用

#学习记录# 前言&#xff1a;本文以一个简单的计数器来说明vcs和verdi的使用 1 代码文件 1.1 计数器代码 //Engineer&#xff1a;Mr-pn-junction module counter(input clk,input rst,output reg [5:0] count); always(posedge clk or negedge rst)beginif(!rst)coun…

自研直播系统-直播系统实战

文章目录 1 流媒体基础本文教程下载地址1.1 流媒体1.2 流式传输方式1.2.1 顺序流式传输1.2.2 实时流式传输 1.3 流媒体传输协议1.3.1 rtmp协议1.3.2 HLS协议1.3.3 RTSP协议1.3.4 视频流的对比 1.4 视频编码(codec)1.5 分辨率的规范分辨率簡介&#xff1a;1.5.2 分辨率單位 1.6 …

递归(三)—— 初识暴力递归之“字符串的全部子序列”

题目1 &#xff1a; 打印一个字符串的全部子序列 题目分析&#xff1a; 解法1&#xff1a;非递归方法 我们通过一个实例来理解题意&#xff0c;假设字符串str “abc”&#xff0c;那么它的子序列都有那些呢&#xff1f;" ", “a”&#xff0c; “b”&#xff0c;…

零基础STM32单片机编程入门(七)定时器PWM波输出实战含源码视频

文章目录 一.概要二.PWM产生框架图三.CubeMX配置一个TIME输出1KHZ&#xff0c;占空比50%PWM波例程1.硬件准备2.创建工程3.测量波形结果 四.CubeMX工程源代码下载五.讲解视频链接地址六.小结 一.概要 脉冲宽度调制(PWM)&#xff0c;是英文“Pulse Width Modulation”的缩写&…

「ETL趋势」FDL定时任务区分开发/生产模式、API输入输出支持自定义响应解析

FineDataLink作为一款市场上的顶尖ETL工具&#xff0c;集实时数据同步、ELT/ETL数据处理、数据服务和系统管理于一体的数据集成工具&#xff0c;进行了新的维护迭代。本文把FDL4.1.7最新功能作了介绍&#xff0c;方便大家对比&#xff1a;&#xff08;产品更新详情&#xff1a;…

leetcode--二叉树中的最长交错路径

leetcode地址&#xff1a;二叉树中的最长交错路径 给你一棵以 root 为根的二叉树&#xff0c;二叉树中的交错路径定义如下&#xff1a; 选择二叉树中 任意 节点和一个方向&#xff08;左或者右&#xff09;。 如果前进方向为右&#xff0c;那么移动到当前节点的的右子节点&…

Redis 中的通用命令(命令的返回值、复杂度、注意事项及操作演示)

Redis 中的通用命令(高频率操作) 文章目录 Redis 中的通用命令(高频率操作)Redis 的数据类型redis-cli 命令Keys 命令Exists 命令Expire 命令Ttl 命令Type命令 Redis 的数据类型 Redis 支持多种数据类型&#xff0c;整体来说&#xff0c;Redis 是一个键值对结构的&#xff0c;…

智能光伏开发都能用到什么软件和工具?

随着全球对可再生能源的日益重视和光伏技术的快速发展&#xff0c;智能光伏开发已成为推动能源转型的重要力量。在光伏项目的全生命周期中&#xff0c;从设计、建设到运营管理&#xff0c;各种软件和工具的应用发挥着至关重要的作用。 一、光伏系统设计软件 1、PVsyst PVsyst…

【ECCV 2024】首个跨模态步态识别框架:Camera-LiDAR Cross-modality Gait Recognition

【ECCV 2024】首个跨模态步态识别框架&#xff1a;Camera-LiDAR Cross-modality Gait Recognition 简介&#xff1a;主要方法&#xff1a;实验结果&#xff1a; 论文&#xff1a;https://arxiv.org/abs/2407.02038 简介&#xff1a; 步态识别是一种重要的生物特征识别技术。基…

16_更快的速度与精度:Faster R-CNN

回顾R-CNN:链接 回顾Fast R-CNN:链接 1.1 简介 Faster R-CNN是作者Ross Girshick继Fast R-CNN后的又一力作。同样使用VGG16作推理速度在GPU上达到5fps(包括候选区域的生成)&#xff0c;准确率为网络的backbone&#xff0c;也有进一步的提升。在2015年的ILSVRC以及COCO竞赛中…

狂赚三个亿,百亿医用耗材上市公司重金押注老人轮椅

布局海外市场&#xff0c;轮椅销量翻两番 作者 | 艾米莉 排版 | 张思琪 抛砖引玉 1.年销售60万台轮椅&#xff0c;英科医疗如何做到&#xff1f; 2.老年人轮椅是出海&#xff0c;还是深耕国内市场&#xff1f; 3.2022年全球轮椅市场规模为48亿美元&#xff0c;谁在喝汤&…

一文讲解Docker入门到精通

一、引入 1、什么是虚拟化 在计算机中&#xff0c;虚拟化&#xff08;英语&#xff1a;Virtualization&#xff09;是一种资源管理技术&#xff0c;它允许在一台物理机上创建多个独立的虚拟环境&#xff0c;这些环境被称为虚拟机&#xff08;VM&#xff09;。每个虚拟机都可以…

Node.js的下载、安装和配置

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

vue 组件el-tree添加结构指示线条

效果展示: 注意&#xff1a;组件中需要添加:indent"0" 进行子级缩进处理&#xff0c;否则会出现子级缩进逐级递增 :expand-on-click-node"false" 设置点击箭头图标才会展开或者收起 代码&#xff1a; <el-tree class"tree filter-tree" :da…