【高性能服务器】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多轮使用会出现大量重复的拷贝开销和挂载监听开销

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Cgi上传文件 注意事项

//核心代码 ofstream outfile("/opt/software/" file.getFilename(), ios::out | ios::binary); outfile << file.getData(); //错误方式&#xff1a;outfile << file.getData() <<endl; outfile.close(); 例如&#xff1a;上传tar.gz格式的压缩…

一站式天气预报解决方案,API接口轻松接入

天气对我们的日常生活有着重要的影响&#xff0c;无论是出门旅行还是安排工作&#xff0c;都需要提前了解天气情况。WAPI平台提供了一站式天气预报解决方案&#xff0c;通过简单的API接口&#xff0c;轻松获取各类天气预报数据。 这个API接口提供了丰富的天气预报信息&#xf…

海睿思问数(TableGPT):开创企业新一代指标应用模式

1 指标建设对企业经营管理数字化的价值分析 指标是将海量数据中关键信息提炼和挖掘出来&#xff0c;以数据为载体展示企业经营管理和分析中的统计量。它通过分析数据&#xff0c;形成一个具有度量值的汇总结果&#xff0c;使得业务状态可以被描述、量化和分解。指标通常由度量…

秋招突击——设计模式补充——简单工厂模式和策略模式

文章目录 引言正文简单工厂模式策略模式策略模式和工厂模式的结合策略模式解析 总结 引言 一个一个来吧&#xff0c;面试腾讯的时候&#xff0c;问了我单例模式相关的东西&#xff0c;自己这方面的东西&#xff0c;还没有看过。这里需要需要补充一下。但是设计模式有很多&…

棱镜七彩上榜数说安全《2024年中国网络安全市场全景图》

2024年7月4日&#xff0c;数说安全正式发布《2024年中国网络安全市场全景图》&#xff08;以下简称全景图&#xff09;&#xff0c;棱镜七彩凭借专业的技术优势和产品创新实力再次上榜开发安全-软件成分分析&#xff08;SCA&#xff09;领域。 据悉&#xff0c;本次全景图在各市…

如何通过KB知识库系统实现内部知识的管理

“Baklib 通过构建KB知识库系统实现内部知识的管理&#xff0c;构建 CMS 系统实现网站内容管理&#xff0c;构建 DAM 实现对原子化数字内容的管理。” Baklib 从多个维度和深度实现对数字内容的管理。 CMS 系统 CMS 系统(Content Management System 内容管理系统)是一种帮助用…

ESP32CAM物联网教学09

ESP32CAM物联网教学09 摄像头配上显示屏 小智给摄像头配上了一块液晶显示屏,ESP32Cam变得更加酷炫了,应用也更加广泛了。 TFT彩色显示屏从第一课的CameraWebServer开始,我们一直都是利用浏览器来查看显示摄像头的视频流,都需要借助这个网页提供的服务。 可以让ESP32Cam开…

Python爬虫康复训练——笔趣阁《神魂至尊》

还是话不多说&#xff0c;很久没写爬虫了&#xff0c;来个bs4康复训练爬虫&#xff0c;正好我最近在看《神魂至尊》&#xff0c;爬个txt文件下来看看 直接上代码 """ 神魂至尊网址-https://www.bqgui.cc/book/1519/ """ import requests from b…

文件操作及部分文件函数的介绍学习(上)

目录 前言 1.为什么要要使用文件&#xff1f; 2.什么是文件&#xff1f; 2.1程序文件 2.2数据文件 2.3文件名 4.文件的打开和关闭 4.1 流和标准流 4.1.1流 4.1.2标准流 4.2文件指针 4.3文件的打开和关闭 结语 前言 Hello&#xff0c;亲爱的小伙伴们&#xff0c;作…

【数智化人物展】数势科技创始人兼CEO黎科峰:数智化时代To B软件行业面临颠覆与重塑...

黎科峰 本文由数势科技创始人兼CEO黎科峰投递并参与由数据猿联合上海大数据联盟共同推出的《2024中国数智化转型升级先锋人物》榜单/奖项评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 2020年&#xff0c;对我而言&#xff0c;是职业生涯中的一个重大转折点。在全球新…

速度提升100倍!CVPR2024揭示迄今最快的3DGS视频重建方法

论文标题&#xff1a; 3DGStream: On-the-Fly Training of 3D Gaussians for Efficient Streaming of Photo-Realistic Free-Viewpoint Videos 论文作者&#xff1a; Jiakai Sun, Han Jiao, Guangyuan Li, Zhanjie Zhang, Lei Zhao, Wei Xing 导读&#xff1a; 渲染动态场景…

3个让你爽到爆炸的学习工具

We OCR WeOCR 是一个基于浏览器的文字识别工具&#xff0c;用户可以通过上传图片来识别其中的文本信息。它是一个渐进式网络应用程序&#xff08;PWA&#xff09;&#xff0c;可以在浏览器中离线使用。WeOCR 是开源的&#xff0c;并且基于 Tesseract OCR 引擎开发。用户无需在本…

JavaScript主要用途和方向

JavaScript是一种广泛使用的编程语言&#xff0c;可以用于开发各种类型的应用程序&#xff0c;包括Web应用程序、桌面应用程序、移动应用程序和游戏等。以下是博主整理的JavaScript可以做的一些事情&#xff1a; 1. Web开发&#xff1a; JavaScript是Web开发的核心语言之一&…

字节也没余粮了?天底下没有永远免费的GPT-4;AI产品用订阅制就不合理!让用户掏钱的N种定价技巧嘿嘿 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;ShowMeAI官网 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; 1. 当 Coze 也开始收费&#xff1a;天底下没有「永远」免费的 GPT-4 注&#xff1a;这里 Coze 指海外版。国内版 扣子 还是免费。 Coze (海外版) 官网链接 → htt…

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

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

Prometheus + Grafana 监控系统搭建使用指南-mysqld_exporter 安装与配置

使用mysqld_exporter 实现Prometheus 监控Mysql 系列文章目录 Prometheus 的安装部署Grafana的安装部署Linux服务器接入Prometheus监控-Node Exporter 安装指南Prometheus 接入SpringBoot微服务监控Mysql 接入 Prometheus RocketMQ 接入Prometheus 监控ElasticSearch 接入 Pr…

品牌推广的深层逻辑:自我提升与市场认同的和谐共生

品牌推广的深层逻辑&#xff1a;自我提升与市场认同的和谐共生 著名飞行员查尔斯林德伯格(Charles Lindbergh) 曾写道:“改善生活方式比传播生活方式更重要。如果我们自己的生活方式使别人感到满意&#xff0c;那么它将自动蔓延。如果不是这样&#xff0c;那么任何武力都不可能…

SpringBoot实现图片添加水印(完整)

提示&#xff1a;昨天不是写了一个类似与图片添加水印的版本吗,今天来写一个带数据库&#xff0c;并且可以完整访问的版本 文章目录 目录 文章目录 引入库 配置文件 数据库配置 字段配置 索引配置 数据库表语句 启动文件 前端代码 整体代码目录 配置类AppConfig Contro…

第十四届蓝桥杯省赛C++B组F题【岛屿个数】题解(AC)

题目大意 给定一个 01 地图&#xff0c;分别表示陆地和海&#xff0c;问地图中一共有多少块岛屿&#xff1f;另外&#xff0c;若一个岛屿在另一个岛屿的内部&#xff0c;则不统计。如下图中的大岛屿包含着内部的小岛屿&#xff0c;故内部小岛屿不计算&#xff0c;最终输出 1。…

20W+喜爱的Pathview网页版 | 整合表达谱数据KEGG通路可视化

Pathview网站简介 网址&#xff1a;https://pathview.uncc.edu/ 前段时间介绍了一个R包 — Pathview。它可以整合表达谱数据并可视化KEGG通路&#xff0c;操作是先自动下载KEGG官网上的通路图&#xff0c;然后整合输入数据对通路图进行再次渲染。从而对KEGG通路图进行一定程度…