基于TCP的多路复用

1. 知识点

目前支持I/O多路复用的系统调用有select,pselect,poll,epoll。与多进程和多线程技术相

比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进

程/线程,从而大大减小了系统的开销。

I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般

是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都

是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是

阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用

户空间

2. select 函数

0 表示标准输入   STDIN_FILENO

1 表示标准输出     STDOUT_FILENO

2 表示标准错误输出 STDERR_FILENO

2.1 select存在三个问题

[1] 每次调用select,都需要把被监控的fds集合从用户态空间拷贝到内核态空间,高并发场景

下这样的拷贝会使得消耗的资源是很大的。

[2] 能监听端口的数量有限,单个进程所能打开的最大连接数由FD_SETSIZE宏定义,监听上

限就等于fds_bits位数组中所有元素的二进制位总数,32位机默认1024个,64位默2048。

[3] 被监控的fds集合中,只要有一个有数据可读,整个socket集合就会被遍历一次,用户线程并不知道哪些 fds 收到数据只能挨个遍历每个socket来收集可读事件了。

2.2 函数接口

1)用户进程需要监控某些资源 fds,在调用 select 函数后会阻塞,操作系统会将用户线程加入这些资源的等待队列中。

2)直到有描述符就绪(有数据可读、可写或有 except)或超时(timeout 指定等待时间,如果立即返回设为 null 即可),函数返回。

3)select 函数返回后,中断程序唤起用户线程。用户可以遍历 fds,通过 FD_ISSET 判断具体哪个 fd 收到数据,并做出相应处理。

select 函数优点明显,实现起来简单有效,且几乎所有操作系统都有对应的实现。

2.2.1 接口

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

2.2.2 参数:

int nfds:管理的最⼤的⽂件描述符+1

fd_set *readfds:⽂件描述符表(集合),监视管理⽂件描述符的读操作是否就绪

fd_set *writefds:⽂件描述符表(集合),监视管理⽂件描述符的写操作是否就绪,没有就 写NULL

fd_set *exceptfds:⽂件描述符表(集合),监视管理⽂件描述符的异常

struct timeval *timeout:timeout:超时设置。

Null:一直阻塞直到有文件描述符就绪或出错

时间值为0:仅仅检测文件描述符集的状态,然后立即返回

时间值不为0:在指定时间内,如果没有事件发生,则超时返回。

超时设置过后,如果select超时了,那么返回值是0, 并且超时时间的结构体会变成 0s

2.2.3 返回值:

成功:返回监视到就绪的⽂件描述符的个数,会把监视的表修改为只剩下就绪的⽂件描述符

失败:返回-1

2.2.4 操作⽂件描述符表:

void FD_CLR(int fd, fd_set *set);//把⽂件描述符fd从set表删除

int   FD_ISSET(int fd, fd_set *set);//判断fd是否在set集合中,返回值描述符存在集合里返回真1,不存在返回假0

void FD_SET(int fd, fd_set *set);//把fd 加⼊到set表,将 fd_set 结构中对应的位设置为1,表示便通过 select 函数对文件描述符 fd进行监视。

void FD_ZERO(fd_set *set);//清空表,将其所有位都设置为0。

2.3 多路复用实现通信

服务端select_serve.c

#include <stdio.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/time.h>
//基于TCP 的IO多路复用通信
//服务端
int main()
{//创建套接字int sock_fd = socket(PF_INET,SOCK_STREAM,0);//初始化本机地址和端口struct sockaddr_in srvaddr;srvaddr.sin_family = PF_INET;srvaddr.sin_port = htons(10000);srvaddr.sin_addr.s_addr = inet_addr("192.168.124.151");socklen_t srvaddr_len = sizeof(srvaddr);//绑定套接字bind(sock_fd,(struct sockaddr *)&srvaddr,srvaddr_len);//监听listen(sock_fd,4);//等待连接printf("等待连接中......\n");int conn_fd = accept(sock_fd,NULL,NULL);printf("连接成功!!\n");//定义一个集合fd_set jihe;while(1){FD_ZERO(&jihe);//清空集合,将其所有位都设置为0。FD_SET(conn_fd,&jihe);//把conn_fd套接字(套接字说白了也是文件描述符)添加进集合//标准输入文件STDIN_FILENO-------0FD_SET(STDIN_FILENO,&jihe);//把标准输入添加进集合//多路复用的系统调用,这里监控读操作,//一旦有操作select就会被select监控到,接着配合后面的FD_ISSET()判断出具体时集合中的哪个文件描述符int ret = select(conn_fd+1,&jihe,NULL,NULL,NULL);if(-1 == ret)//监控失败{perror("select failed");continue;}if(0 == ret)//超时{continue;}//判断文件描述符是否在集合中 如果已连接套接字在集合,则进行读操作,读取客户端发来的信息if(FD_ISSET(conn_fd,&jihe) == 1){char rbuf[128]={0};read(conn_fd,rbuf,sizeof(rbuf));printf("from cli:%s\n",rbuf);//FD_CLR(conn_fd,&jihe);}//如果我们有标准输入在集合中,则写入,即通过已连接套接字发送给客户端if(FD_ISSET(STDIN_FILENO,&jihe) == 1)//判断出是STDIN_FILENO标准输入{char wbuf[128]={0};//定义缓冲区fgets(wbuf,sizeof(wbuf),stdin);//键盘输入write(conn_fd,wbuf,sizeof(wbuf));//FD_CLR(STDIN_FILENO,&jihe);}}}

客户端select_cilent.c

#include <stdio.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/time.h>
//基于TCP 的IO多路复用通信
//客户端
int main()
{//创建套接字int sock_fd = socket(PF_INET,SOCK_STREAM,0);//初始化服务端网络地址struct sockaddr_in srvaddr;srvaddr.sin_family = PF_INET;srvaddr.sin_port = htons(10000);srvaddr.sin_addr.s_addr = inet_addr("192.168.124.151");socklen_t srvaddr_len = sizeof(srvaddr);//这个绑定可有可无// bind(sock_fd,(struct sockaddr *)&srvaddr,srvaddr_len);//listen(sock_fd,4);//请求连接connect(sock_fd,(struct sockaddr *)&srvaddr,srvaddr_len);//定义一个集合fd_set jihe;while(1){FD_ZERO(&jihe);//清空集合FD_SET(sock_fd,&jihe);//把sock_fd套接字(套接字说白了也是文件描述符)添加进集合//标准输入文件STDIN_FILENO-------0FD_SET(STDIN_FILENO,&jihe);//把标准输入添加进集合//多路复用的系统调用,这里监控读操作int ret = select(sock_fd+1,&jihe,NULL,NULL,NULL);if(-1 == ret){perror("select failed");continue;}if(0 == ret){continue;}//判断文件描述符是否在集合中 如果已连接套接字在集合,则进行读操作,读取客户端发来的信息if(FD_ISSET(sock_fd,&jihe) == 1){char rbuf[128]={0};read(sock_fd,rbuf,sizeof(rbuf));printf("from srv:%s\n",rbuf);//FD_CLR(sock_fd,&jihe);}//如果我们有标准输入文件描述符在集合中,则写入,即通过已连接套接字发送给服务端if(FD_ISSET(STDIN_FILENO,&jihe) == 1){char wbuf[128]={0};fgets(wbuf,sizeof(wbuf),stdin);write(sock_fd,wbuf,sizeof(wbuf));//FD_CLR(STDIN_FILENO,&jihe);}}}

3. poll

3.1 优点

poll 函数与 select 原理相似,都需要来回拷贝全部监听的文件描述符,不同的是:

1)poll 函数采用链表的方式替代原来 select 中 fd_set 结构,因此可监听文件描述符数量不受限。

2)poll 函数返回后,可以通过 pollfd 结构中的内容进行处理就绪文件描述符,相比 select 效率要高。

3)新增水平触发:也就是通知程序 fd 就绪后,这次没有被处理,那么下次 poll 的时候会再次通知同个 fd 已经就绪。

3.2 缺点

和 select 函数一样,poll 返回后,需要轮询 pollfd 来获取就绪的描述符。poll和select同样

存在一个性能缺点就是包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,以及个别描述符就绪触发整体描述符集合的遍历的低效问题。而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

4. epoll

epoll 使用一个文件描述符管理多个描述符,将用户进程监控的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间只需拷贝一次。

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

4.1 优点

1)没有最大并发连接的限制,能打开的 FD 的上限远大于 1024。

2)效率提升,不是轮询的方式,不会随着 FD 数目的增加效率下降。

3)内存拷贝,利用 mmap() 文件映射内存加速与内核空间的消息传递,即 epoll 使用 mmap 减少复制开销。

4)新增 ET 模式。

5. 总结

5.1 select、poll、epoll区别

三种函数在的 Linux 内核里有都能够支持,其中 epoll 是 Linux 所特有,而 select 则应该是 POSIX 所规定,一般操作系统均有实现。

5.2 工作模式

1)LT模式

LT(level triggered)模式:也是默认模式,即当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件,并且下次调用 epoll_wait 时,会再次响应应用程序并通知此事件。

2)ET模式

ET(edge-triggered)模式:当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

ET 是一种高速工作方式,很大程度上减少了 epoll 事件被重复触发的次数。epoll 工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

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

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

相关文章

class073 背包dp-01背包、有依赖的背包【算法】

class073 背包dp-01背包、有依赖的背包【算法】 算法讲解073【必备】背包dp-01背包、有依赖的背包 code1 P1048 [NOIP2005 普及组] 采药 // 01背包(模版) // 给定一个正数t&#xff0c;表示背包的容量 // 有m个货物&#xff0c;每个货物可以选择一次 // 每个货物有自己的体积…

想进阶JAVA高级程序员吗?多线程必学

❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。2022年度博客之星评选TOP 10&#x1f3c6;&#xff0c;Java领域优质创作者&#x1f3c6;&#xff0c;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;掘金年度人气作…

[C++] STL_priority_queue(优先级队列) 的使用及底层的模拟实现,容器适配器,deque的原理介绍

文章目录 1、priority_queue1.1 priority_queue的介绍和使用1.2 priority_queue的使用模拟实现&#xff1a; 2、容器适配器2.1 什么是适配器2.2 STL标准库中stack和queue的底层结构 3、deque3.1 deque的原理介绍3.2 deque的缺陷 4、为什么选择deque作为stack和queue的底层默认容…

[陇剑杯 2021]日志分析

[陇剑杯 2021]日志分析 题目做法及思路解析&#xff08;个人分享&#xff09; 问一&#xff1a;单位某应用程序被攻击&#xff0c;请分析日志&#xff0c;进行作答&#xff1a; 网络存在源码泄漏&#xff0c;源码文件名是_____________。(请提交带有文件后缀的文件名&…

高级系统架构设计师之路

前言&#xff1a;系 统 架 构 设 计 师 (System Architecture Designer)是项目开发活动中的众多角色之 一 &#xff0c;它可 以是 一个人或 一个小组&#xff0c;也可以是一个团队。架构师 (Architect) 包含建筑师、设计师、创造 者、缔造者等含义&#xff0c;可以说&#xff0…

年入百万的知识付费网站如何搭建:如何以低成本实现高转化?

我有才知识付费平台 一、引言 随着知识经济的崛起&#xff0c;越来越多的知识提供者希望搭建自己的知识付费平台。然而&#xff0c;对于新手来说&#xff0c;如何以低成本、高效率地实现这一目标&#xff0c;同时满足自身需求并提高客户转化率&#xff0c;是一大挑战。本文将…

管理空闲存储空间

位示图是操作系统中一种管理空闲存储空间的方法。管理空闲除使用位示图法还可用&#xff1a;空闲区表法&#xff0c;空闲链表法&#xff0c;成组链接法 1.空闲区表法 空闲表法属于连续分配方法。它与内存管理中的动态分区分配方法雷同。 将外存空间上一个连续未分配区域称为“…

[MySQL] SQL优化之性能分析

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 一、索引优化 1、索引是什么&#xff1a; 2、索引的数据结构&#xff1a; 3、索引种类&#xff1a; 4、sql分析&#xff08;回表查询&#xff09; 二、定位慢查询语句 1、慢查询日志 2、profile详情 3、…

四十三、Redis基础

目录 一、认识NoSql 1、定义&#xff1a; 2、常见语法 3、与关系型数据库&#xff08;SQL&#xff09;的区别&#xff1a; 二、认识Redis 1、定义&#xff1a; 2、特征&#xff1a; 3、Key的结构&#xff1a; 三、安装Redis 四、Redis常见命令 1、数据结构介绍 2、…

Mysql dumpling 导入导出sql文件

一&#xff1a;导出命令 mysqldump -u root -p saishi > saishi.sql mysqldump -u root -p saishi > saishi.sql root是用户名 saishi是数据库名 saishi.sql导出文件名 二&#xff1a;选择导入的数据库 cd到安装mysql的文件下&#xff08;找不到可以用&#xff1a;wh…

IP地址SSL证书申请指南

1&#xff0c;选择 与基于域名的SSL证书类似&#xff0c;IP地址SSL证书也分为多种类型&#xff0c;包括DV和OV 等。DV证书只需验证域名的所有权&#xff0c;适合个人和小型企业&#xff1b;OV证书除了验证域名所有权外&#xff0c;还需要验证申请单位的真实身份信息&#xff0…

代码随想录算法训练营第48天| 198.打家劫舍 213.打家劫舍II 337.打家劫舍III

JAVA代码编写 198.打家劫舍 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统会自动报警。 给…