Linux网络编程系列之服务器编程——多路复用模型
一、什么是多路复用模型
服务器的多路复用模型指的是利用操作系统提供的多路复用机制,同时处理多个客户端连接请求的能力。在服务器端,常见的多路复用技术包括select、poll和epoll等。这些技术允许服务器同时监听多个客户端连接请求,当有请求到达时,会通知服务器进行处理。通过使用多路复用技术,可以避免一个线程只处理一个客户端连接的情况,提高服务器的并发性能和响应速度。在实际应用中,多路复用技术被广泛地应用于Web服务器、游戏服务器、消息队列等领域。
注:下面案例演示采用select结合TCP协议,一般不结合UDP协议使用,案例也演示了select结合UDP协议。
二、特性
1、支持大量并发连接
多路复用技术可以同时监听多个客户端连接请求,避免了一个线程只处理一个客户端连接的情况,从而可以支持更多的并发连接。
2、减少系统开销
采用多路复用技术可以减少系统开销,因为不需要为每个连接开启一个线程或进程,避免了系统资源浪费。
3、提高响应速度
采用多路复用技术可以提高服务器的响应速度,因为多个连接可以同时处理,避免了连接排队的情况。
4、更好的可扩展性
多路复用技术可以更好的支持服务器的可扩展性,因为它可以动态地管理和调度连接,方便服务器的扩展和升级。
三、使用场景
1、高并发的Web服务器
对于高并发的Web服务器,采用多路复用技术可以同时监听多个客户端连接请求,避免了一个线程只处理一个客户端连接的情况,从而可以支持更多的并发连接。
2、实时通信服务器
对于实时通信服务器,采用多路复用技术可以同时监听多个客户端连接请求,可以处理多种类型的通信,包括即时通讯、实时游戏等。
3、TCP/IP服务器
对于TCP/IP服务器,采用多路复用技术可以提高服务器的性能和可靠性,因为多个连接可以同时处理,避免了连接排队的情况。
4、网络监控工具
对于网络监控工具,采用多路复用技术可以同时处理多个客户端的请求,并对网络数据进行监控和分析。
四、模型框架(通信流程)
1、建立套接字。使用socket()
2、设置端口复用。使用setsockopt()
3、绑定自己的IP地址和端口号。使用bind()
4、设置监听。使用listen()
5、多路复用准备工作。使用文件描述符集合操作
6、循环监听,开始多路复用。使用select()
7、处理客户端连接或者数据接收。使用accept()或者recv()
8、关闭套接字。使用close()
五、相关函数API接口
TCP通信流程常规的API那些在本系列的TCP协议里有大量展示,这里省略,详情可以点击本文开头的链接查看
1、多路复用select
// 多路复用select int select(int nfds, fd_set *readfds,fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);// 接口说明返回值:成功返回readfds,writefds,exceptfds中状态发生变化的文件描述符数量,失败返回-1参数nfds:通常填写三个集合中最大的文件描述符值+1,让内核检测多少个文件描述符的状态参数readfds:监控有读数据到达文件描述符集合参数writefds:监控有写数据到达文件描述符集合参数exceptfds:监控有异常发生到达文件描述符集合参数timeout:设置阻塞等待时间,三种情况(1)、设置为NULL,一直阻塞等待(2)、设置timevl,等待固定的时间(3)、设置timeval里时间为0,在检测完描述符后立即返回
2、集合操作
// 把文件描述符集合里fd清0 void FD_CLR(int fd, fd_set *set);// 把文件描述符集合里fd位置1 void FD_SET(int fd, fd_set *set);// 把文件描述符集合里所有位清0 void FD_ZERO(fd_set *set);// 测试文件描述符集合里fd是否置1 int FD_ISSET(int fd, fd_set *set);
六、案例
1、 采用select函数,完成多路复用TCP服务器的通信演示,用nc命令来模拟客户端
// 多路复用TCP服务器的案例#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h>#define MAX_LISTEN FD_SETSIZE // 最大能处理的连接数, 1024 #define SERVER_IP "192.168.64.128" // 记得改为自己IP #define SERVER_PORT 20000 // 不能超过65535,也不要低于1000,防止端口误用int main(int argc, char *argv[]) {// 1、建立套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd == -1){perror("socket fail");return -1;}// 2、设置端口复用(推荐)int optval = 1; // 这里设置为端口复用,所以随便写一个值int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));if(ret == -1){perror("setsockopt fail");close(sockfd);return -1;}// 3、绑定自己的IP地址和端口号struct sockaddr_in server_addr = {0};socklen_t addr_len = sizeof(struct sockaddr);server_addr.sin_family = AF_INET; // 指定协议为IPV4地址协议server_addr.sin_port = htons(SERVER_PORT); // 端口号// server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // IP地址ret = bind(sockfd, (struct sockaddr *)&server_addr, addr_len);if(ret == -1){perror("bind fail");close(sockfd);return -1;}// 4、设置监听ret = listen(sockfd, MAX_LISTEN);if(ret == -1){perror("listen fail");close(sockfd);return -1;}// 5、多路复用的准备工作fd_set client_set, active_set;// (1)、清空活跃的文件描述符集合FD_ZERO(&active_set);// (2)、把服务器的套接字文件描述符加入到活跃的文件描述符集合中FD_SET(sockfd, &active_set);// (3)、初始化活跃集合中最大的文件描述符int maxfd = sockfd;// (4)、初始化能接受的活跃客户端套接字数组int client[MAX_LISTEN];for(int i = 0; i < MAX_LISTEN; i++){client[i] = -1; // 空的置为-1,活跃的置为对应的文件描述符}uint16_t port = 0; // 新的客户端端口号char ip[20] = {0}; // 新的客户端IPstruct sockaddr_in client_addr; // 新的客户端地址char recv_msg[128] = {0}; // 用来接收客户端的数据printf("wait client...\n");while(1){client_set = active_set; // 先备份活跃的集合// 6、多路复用,同时监听多个文件描述符状态,阻塞等待int num = select(maxfd+1, &client_set, NULL, NULL, NULL);if(num == -1){perror("select fail");close(sockfd);return -1;}// 如果监听文件描述符发生变化,说明一定有新的客户端连接上来if(FD_ISSET(sockfd, &client_set)){int new_client_fd = accept(sockfd, (struct sockaddr*)&client_addr, &addr_len);if(new_client_fd == -1){perror("accept fail");continue;}else{// 打印连接的客服端IP和端口号memset(ip, 0, sizeof(ip));strcpy(ip, inet_ntoa(client_addr.sin_addr));port = ntohs(client_addr.sin_port);printf("[%s:%d] connect\n", ip, port);// 把新的客户端套接字加入到活跃的集合中FD_SET(new_client_fd, &active_set);// 更新最大活跃文件描述符if(maxfd < new_client_fd){maxfd = new_client_fd;}// 把新的套接字加入到空的活跃客户端套接字数组for(int i = 0; i < MAX_LISTEN; i++){if(client[i] == -1){client[i] = new_client_fd;break;}}// 如果只有服务器的套接字发生变化,新的套接字没有发送数据// 那就继续监听,否则需要打印套接字的信息if(--num == 0){continue;}}}// 如果客服端发送数据过来for(int i = 0; i < MAX_LISTEN; i++){if(client[i] == -1){continue;}// 如果活跃的客户端有发送数据,注意这里要采用client_set,而不是active_set,否则会读取不了数据if(FD_ISSET(client[i], &client_set)){// 接收数据memset(recv_msg, 0, sizeof(recv_msg));ret = recv(client[i], recv_msg, sizeof(recv_msg), 0);memset(ip, 0, sizeof(ip));strcpy(ip, inet_ntoa(client_addr.sin_addr));port = ntohs(client_addr.sin_port);if(ret == 0){printf("[%s:%d] disconnect\n", ip, port);FD_CLR(client[i], &active_set); // 清空对应活跃集合的套接字client[i] = -1; // 清空客户端套接字数组// 需要重新更新活跃集合中最大的文件描述符maxfd = sockfd;for(int i = 0; i < MAX_LISTEN; i++){if(client[i] != -1 && maxfd < client[i]){maxfd = client[i];}}}else{printf("[%s:%d] send data: %s\n", ip, port, recv_msg);}// 如果所有发生变化的套接字都已经处理完成if(--num == 0){break;}}}}close(sockfd);return 0; }
2、 采用select函数,完成多路复用UDP服务器的通信演示,用nc命令来模拟客户端
// 多路复用TCP服务器的案例#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <errno.h>#define MAX_LISTEN FD_SETSIZE // 最大能处理的连接数, 1024 #define SERVER_IP "192.168.64.128" // 记得改为自己IP #define SERVER_PORT 20000 // 不能超过65535,也不要低于1000,防止端口误用int main(int argc, char *argv[]) {// 1、建立套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd == -1){perror("socket fail");return -1;}// 2、设置端口复用(推荐)int optval = 1; // 这里设置为端口复用,所以随便写一个值int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));if(ret == -1){perror("setsockopt fail");close(sockfd);return -1;}// 3、绑定自己的IP地址和端口号struct sockaddr_in server_addr = {0};socklen_t addr_len = sizeof(struct sockaddr);server_addr.sin_family = AF_INET; // 指定协议为IPV4地址协议server_addr.sin_port = htons(SERVER_PORT); // 端口号// server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // IP地址ret = bind(sockfd, (struct sockaddr *)&server_addr, addr_len);if(ret == -1){perror("bind fail");close(sockfd);return -1;}// 4、多路复用的准备工作fd_set client_set, active_set;// (1)、清空活跃的文件描述符集合FD_ZERO(&active_set);// (2)、把服务器的套接字文件描述符加入到活跃的文件描述符集合中FD_SET(sockfd, &active_set);// (3)、初始化活跃集合中最大的文件描述符int maxfd = MAX_LISTEN;// (4)、初始化能接受的活跃客户端套接字数组int client[MAX_LISTEN];for(int i = 0; i < MAX_LISTEN; i++){client[i] = -1; // 空的置为-1,活跃的置为对应的文件描述符}uint16_t port = 0; // 新的客户端端口号char ip[20] = {0}; // 新的客户端IPstruct sockaddr_in client_addr; // 新的客户端地址char recv_msg[128] = {0}; // 用来接收客户端的数据printf("wait client...\n");while(1){client_set = active_set; // 先备份活跃的集合// 5、多路复用,同时监听多个文件描述符状态,阻塞等待int num = select(maxfd+1, &client_set, NULL, NULL, NULL);if(num == -1){perror("select fail");close(sockfd);return -1;}else{// 接收数据memset(recv_msg, 0, sizeof(recv_msg));ret = recvfrom(sockfd, recv_msg, sizeof(recv_msg), 0, (struct sockaddr*)&client_addr, &addr_len);memset(ip, 0, sizeof(ip));strcpy(ip, inet_ntoa(client_addr.sin_addr));port = ntohs(client_addr.sin_port);printf("[%s:%d] send data: %s\n", ip, port, recv_msg);}}close(sockfd);return 0; }
注:TCP和UDP的代码有所不同,多路复用监听方式有所不同。
七、总结
多路复用适用于处理连接的客户端的数量小于1024的场景,当然你可以改,让其超过1024限制,这里不做讨论。多路复用模型TCP服务器跟简单的TCP服务器通信流程很像,就是在接收客户端时要采用select要进行操作。一般情况下,不采用多路复用select结合UDP协议使用,但是不代表不行,案例给出了演示。
相关文章:
Linux网络编程系列之服务器编程——多路复用模型
一、什么是多路复用模型 服务器的多路复用模型指的是利用操作系统提供的多路复用机制,同时处理多个客户端连接请求的能力。在服务器端,常见的多路复用技术包括select、poll和epoll等。这些技术允许服务器同时监听多个客户端连接请求,当有请求…...
在SQL语句里使用正则表达式,因该怎么使用
在SQL中使用正则表达式通常需要使用特定的函数或运算符,具体的语法可能因不同的数据库系统而有所不同。以下是使用正则表达式的一般方法,但请注意,具体语法可能会因您使用的数据库而有所不同。 一般情况下,您可以使用以下方法在S…...
扫码登录-测试用例设计
扫码登录测试用例...
PyTorch CUDA GPU高占用测试
0x00 问题描述 安装完成PyTorch、CUDA后,验证PyTorch是否能够通过CUDA高占用GPU(占用>95%),特地使用以下代码测试。 0x01 代码设计 这个代码会持续执行神经网络的训练任务,每次循环都进行前向传播、反向传播和参数…...
Java|学习|abstract ,接口 Interface , Object
1.abstract 1.1 abstract abstract 是修饰符,表示抽象的,用来修饰抽象类和抽象方法。 abstract 修饰的类是抽象类,抽象类不能创建对象,主要用于被子类继承。 abstract 修饰的方法是抽象方法,该方法没有方法体&…...
安全的Sui Move是Web3大规模采用之路的基石
没有信任,就没有Web3的大规模采用。还有其他重要障碍阻碍了首个十亿用户的到来,包括令人困惑的用户体验、复杂的身份验证模式以及不确定的监管体系,但所有障碍中,要数大多数人对区块链技术持怀疑和不信任态度最严重。 对于许多人…...
Python中图像相似性度量方法汇总
1. 引言 在当前到处充满着图像的世界里,测量和量化图像之间的相似性已经成为一项关键的任务。无论是图像检索、内容推荐还是视觉搜索,图像相似性方法在现代计算机视觉的应用中都发挥着关键的作用。 幸运的是,Python提供了大量的工具和库&am…...
pycharm中快速对比两个.py文件
在学习一个算法的时候,就想着自己再敲一遍代码,结果最后出现了一个莫名其妙的错误,想跟源文件对比一下到底是在哪除了错,之前我都是大致定位一个一个对比,想起来matlab可以快速查找出两个脚本文件(.m文件)的区别&#…...
C++程序结束
在C程序任意位置结束程序需要return 0,如果只return的话会发生生成错误...
嵌入式学习-核心板、开发板和单片机
目录 核心板开发板单片机三者关系 核心板 核心板是一种电路板,它集成了微处理器、存储器和一些必要的接口电路。它通常用于嵌入式系统或物联网设备中,作为整个系统的核心组件。它的主要功能是将微处理器的指令和数据总线转换为各种外设的接口࿰…...
【pycharm】控制台报错:终端无法加载文件\venv\Scripts\activate.ps1
目录 一、在pycharm控制台输入 二、在windows的power shell (以管理员方式打开) 三、 在pycharm控制台输入 四、重新打开pycharm即可 前言:安装pycharm2022-03版本出现的终端打开报错 一、在pycharm控制台输入 get-executionpolicy …...
Python算术运算符:加减乘除 整除 取余 幂指数 小括号
运算案例 需求:用户手工输入梯形的上底、下底以及高,能直接通过Python打印出梯形的面积为多少。 做这个需求前,首先要知道Python的算数运算符有哪些。 2、算术运算符 所谓的算数运算符就是我们日常生活中的加减乘除等待。 运算符描述实例…...
访问者模式:对象结构的元素处理
欢迎来到设计模式系列的第十九篇文章,本篇将介绍访问者模式。访问者模式是一种行为型设计模式,它用于处理对象结构中不同类型的元素,而不需要修改这些元素的类。 什么是访问者模式? 访问者模式是一种将数据结构与数据操作分离的…...
ChatGPT快速入门
ChatGPT快速入门 一、什么是ChatGPT二、ChatGPT底层逻辑2.1 实现原理2.2 IO流程 三、ChatGPT应用场景3.1 知心好友3.2 文案助理3.3 创意助理3.4 角色扮演 一、什么是ChatGPT ChatGPT指的是基于GPT(Generative Pre-trained Transformer)模型的对话生成系…...
链表的实现(c语言)
链表分为单链表、双链表和循环链表,这些理论知识在笔记中自然写了,这里我只写出其中的实现: 单链表的实现 #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct Book {char bookname[20];char a…...
【Redis】渐进式遍历
scan命令渐进式遍历 Redis使⽤scan命令进⾏渐进式遍历键,进⽽解决直接使⽤keys获取键时可能出现的阻塞问题。每次scan命令的时间复杂度是O(1),但是要完整地完成所有键的遍历,需要执⾏多次scan。 SCAN 以渐进式的⽅式进⾏键的遍历。 SCAN…...
uni-app开发微信小程序的报错[渲染层错误]排查及解决
一、报错信息 [渲染层错误] Framework nner error (expect FLOW INITIALCREATION end but get FLOW CREATE-NODE) 二、原因分析及解决方案 第一种 原因:基础库版本的原因导致的。 解决: 1.修改调试基础库版本 2.详情—>本地设置—>调试基础库…...
三、C语言常用运算符
1、算术运算符 符号说明加号-减号*乘号/除号%取余符号,相除以后余数是几自增运算符,整数值增加 1--自减运算符,整数值减少1 2、关系运算符 符号说明检查两个操作数的值是否相等,如果相等则条件为真。!检查两个操作数的值是否相…...
ubuntu联网图标消失
sudo service NetworkManager stopsudo rm /var/lib/NetworkManager/NetworkManager.statesudo service NetworkManager start执行 sudo rm /var/lib/NetworkManager/NetworkManager.state 命令将删除位于 /var/lib/NetworkManager 目录下的 NetworkManager.state 文件。...
中华人民共和国网络安全法
中华人民共和国网络安全法 《中华人民共和国网络安全法》已由中华人民共和国第十二届全国人民代表大会常务委员会第二十四次会议于2016年11月7日通过,现予公布,自2017年6月1日起施行。2022年9月12日,国家互联网信息办公室发布关于公开征求《…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...
人工智能 - 在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型
在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型。这些平台各有侧重,适用场景差异显著。下面我将从核心功能定位、典型应用场景、真实体验痛点、选型决策关键点进行拆解,并提供具体场景下的推荐方案。 一、核心功能定位速览 平台核心定位技术栈亮…...

