Linux网络编程系列之服务器编程——多路复用模型

Linux网络编程系列  (够吃,管饱)

        1、Linux网络编程系列之网络编程基础

        2、Linux网络编程系列之TCP协议编程

        3、Linux网络编程系列之UDP协议编程

        4、Linux网络编程系列之UDP广播

        5、Linux网络编程系列之UDP组播

        6、Linux网络编程系列之服务器编程——阻塞IO模型

        7、Linux网络编程系列之服务器编程——非阻塞IO模型

        8、Linux网络编程系列之服务器编程——多路复用模型

        9、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,防止端口误用// 定义客服端管理类
struct ClientManager
{int client[MAX_LISTEN];     // 存储客户端的套接字char ip[MAX_LISTEN][20];    // 客户端套接字IPuint16_t port[MAX_LISTEN];  // 客户端套接字端口号
};// 初始化客户端管理类
void client_manager_init(struct ClientManager *manager)
{for(int i = 0; i < MAX_LISTEN; i++){manager->client[i] = -1;manager->port[i] = 0;memset(manager->ip, 0, sizeof(manager->ip));}
}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)、初始化能接受的活跃客户端管理类struct ClientManager manager;client_manager_init(&manager);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(manager.client[i] == -1){manager.client[i] = new_client_fd;manager.port[i] = port;strcpy(manager.ip[i], ip);break;}}// 如果只有服务器的套接字发生变化,新的套接字没有发送数据// 那就继续监听,否则需要打印套接字的信息if(--num == 0){continue;}}}// 如果客服端发送数据过来for(int i = 0; i < MAX_LISTEN; i++){if(manager.client[i] == -1){continue;}// 如果活跃的客户端有发送数据,注意这里要采用client_set,而不是active_set,否则会读取不了数据if(FD_ISSET(manager.client[i], &client_set)){// 接收数据memset(recv_msg, 0, sizeof(recv_msg));ret = recv(manager.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", manager.ip[i], manager.port[i]);FD_CLR(manager.client[i], &active_set); // 清空对应活跃集合的套接字manager.client[i] = -1;manager.port[i] = 0;memset(manager.ip[i], 0, sizeof(ip));// 需要重新更新活跃集合中最大的文件描述符maxfd = sockfd;for(int j = 0; j < MAX_LISTEN; j++){if(manager.client[j] != -1 && maxfd < manager.client[j]){maxfd = manager.client[j];}}}else if(ret > 0){printf("[%s:%d] send data: %s\n", manager.ip[i], manager.port[i], 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协议使用,但是不代表不行,案例给出了演示。

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

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

相关文章

肿瘤科常用评估量表汇总,建议收藏!

根据肿瘤科医生的量表使用情况&#xff0c;笔者整理了10个肿瘤科常用量表&#xff0c;可在线评测直接出结果&#xff0c;可转发使用&#xff0c;可生成二维码使用&#xff0c;可创建项目进行数据管理&#xff0c;有需要的小伙伴赶紧收藏&#xff01; 肿瘤患者的ECOG评分标准 肿…

Spring中Setter注入详解

目录 一、setter注入是什么 二、setter注入详解 三、JDK内置类型注入方式 3.1 数组类型 3.2 set集合类型 3.3 list集合 3.4 map集合 3.5 properties类型 四、用户自定义类型 一、setter注入是什么 书接上回&#xff0c;我们发现在Spring配置文件中为类对象的属性赋值时&#x…

MATLAB——RBF、GRNN和PNN神经网络案例参考程序

欢迎关注“电击小子程高兴的MATLAB小屋” GRNN_PNN程序 %% I. 清空环境变量 clear all clc %% II. 训练集/测试集产生 %% % 1. 导入数据 load iris_data.mat %% % 2 随机产生训练集和测试集 P_train []; T_train []; P_test []; T_test []; for i 1:3 temp_input …

编辑器功能:用一个快捷键来【锁定】或【解开】Inspector面板

一、需求 我有一个脚本&#xff0c;上面暴露了许多参数&#xff0c;我要在场景中拖物体给它进行配置。 如果不锁定Inspector面板的话&#xff0c;每次点击物体后&#xff0c;Inspector的内容就是刚点击的物体的内容&#xff0c;而不是挂载脚本的参数面板。 二、 解决 &…

adb调试Linux嵌入式设备记录

1. ADB的全称为Android Debug Bridge&#xff0c;调试设备或调试开发的Android APP。 2.adb的windows下载安装路径&#xff1a;SDK 平台工具版本说明 | Android 开发者 | Android Developers 3.linux中安装adb,参考该链接&#xff1a; https://www.cnblogs.com/androidsu…

【OSPF Loading、FULL状态与display ospf peer brief命令、OSPF的数据库讲解】

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;喜欢编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️ 零基…

(Python)使用Matplotlib将x轴移动到绘图顶部

移动前&#xff1a; 我们有两种方法可以实现这个目标&#xff1a; import warnings warnings.filterwarnings(ignore)import numpy as np import matplotlib.pyplot as pltcolumn_labels list(ABCD) row_labels list(WXYZ)data np.random.rand(4, 4)fig, ax plt.subplots(…

32 数据分析(下)pandas介绍

文章目录 工具excelTableauPower Queryjupytermatplotlibnumpypandas数据类型Series基础的SeriesSeries的字典操作增加表的索引名字和表名字索引操作 DataFrameDataFrame 的基础使用DataFrame的列方法------理解DataFrame的行列方法------使用loc 与 iloc 对齐操作SeriesDataFr…

gitlab自编译 源码下载

网上都是怎么用 gitlab&#xff0c;但是实际开发中有需要针对 gitlab 进行二次编译自定义实现功能的想法。 搜索了网上的资料以及在官网的查找&#xff0c;查到了如下 gitlab 使用 ruby 开发。 gitlab 下载包 gitlab/gitlab-ce - Packages packages.gitlab.com gitlab/gitl…

Excel冻结窗格

1、冻结表格首行 点击菜单栏中的“视图”&#xff0c;选择“窗口”选项卡中的“冻结窗格”下的小三角&#xff0c;再选择“冻结首行”&#xff1b; 2.冻结表格首列 点击菜单栏中的“视图”&#xff0c;选择“窗口”选项卡中的“冻结窗格”下的小三角&#xff0c;再选择“冻结…

linux性能分析(二)如何从日志分析 PV、UV

一 如何从日志分析 PV、UV 本文是从业务侧来衡量整个应用系统的性能,区别与上篇的网络性能分析备注&#xff1a; 这里的日志不仅指的是业务类型日志,也包括系统日志等各种类型的日志关键&#xff1a; 掌握PV和UV的概念和度量方式 "以下是关于埋点的科普文章" 埋…

gcc编译器和gdb调试工具

gcc编译器 GCC&#xff08;GNU Compiler Collection&#xff09;是一套由GNU计划开发的自由软件编译器集合&#xff0c;它支持多种编程语言&#xff0c;包括C、C、Objective-C、Fortran、Ada和Go等。GCC 是一个功能强大、稳定可靠的编译器&#xff0c;被广泛应用于各种操作系统…

jmeter(三十三):阶梯线程组Stepping Thread Group,并发线程Concurrency Thread Group

Stepping Thread Group参数详解 this group will start:表示总共要启动的线程数;若设置为 100,表示总共会加载到 100 个线程first,wait for:从运行之后多长时间开始启动线程;若设置为 0 秒,表示运行之后立即启动线程then start:初次启动多少个线程;若设置为 0 个,表示…

DH48WK 温控器参数设置

北京东昊力伟科技有限责任公司 温控仪、温度控制器 产品特点&#xff1a; 可外接温度传感器Pt100、Cu50、K、E、J、N、T、R、S、B兼容输入&#xff1b;PID控制输出、位式控制输出、继电器报警输出&#xff1b;控温能满足设定温度值的0.2℃&#xff1b;既可用于加热控制、也可…

通讯协议学习之路:USART协议理论

通讯协议之路主要分为两部分&#xff0c;第一部分从理论上面讲解各类协议的通讯原理以及通讯格式&#xff0c;第二部分从具体运用上讲解各类通讯协议的具体应用方法。 后续文章会同时发表在个人博客(jason1016.club)、CSDN&#xff1b;视频会发布在bilibili(UID:399951374) 一、…

易点易动设备管理系统:提升生产企业设备保养效率的利器

在现代生产企业中&#xff0c;设备保养是确保生产线稳定运行和产品质量的关键环节。然而&#xff0c;传统的设备保养方式往往面临效率低下、数据不准确等问题&#xff0c;影响了生产效率和竞争力。随着科技的进步&#xff0c;易点易动设备管理系统应运而生&#xff0c;以其智能…

短视频矩阵系统/pc、小程序版独立原发源码开发搭建上线

短视频剪辑矩阵系统开发源码----源头搭建 矩阵系统源码主要有三种框架&#xff1a;Spring、Struts和Hibernate。Spring框架是一个全栈式的Java应用程序开发框架&#xff0c;提供了IOC容器、AOP、事务管理等功能。Struts框架是一个MVC架构的Web应用程序框架&#xff0c;用于将数…

【算法训练-排序算法 三】【排序应用】合并区间

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【合并区间】&#xff0c;使用【数组】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&…

05在IDEA中配置Maven的基本信息

配置Maven信息 配置Maven家目录 每次创建Project工程后都需要设置Maven家目录位置&#xff0c;否则IDEA将使用内置的Maven核心程序和使用默认的本地仓库位置 一般我们配置了Maven家目录后IDEA就会自动识别到conf/settings.xml配置文件和配置文件指定的本地仓库位置创建新的P…

6-8 舞伴问题 分数 15

void DancePartner(DataType dancer[], int num) {LinkQueue maleQueue SetNullQueue_Link();LinkQueue femaleQueue SetNullQueue_Link();// 将男士和女士的信息分别加入对应的队列for (int i 0; i < num; i) {if (dancer[i].sex M){EnQueue_link(maleQueue, dancer[i]…