Linux C : select简介和epoll 实现

news/2024/5/20 11:31:41/文章来源:https://blog.csdn.net/superSmart_Dong/article/details/132769271

目录

一、基础知识

二、select 模型服务流程

二、select 模式的缺点。

三、poll 概要

四、epoll 服务端实现流程

1.epoll_create:

2.epoll_ctl

3.epoll_wait

五、epoll示例代码实现

1.epoll实现服务端

2.客户端采用tcp进行访问


一、基础知识

      首先要知道,服务器与客户端的网络通信。服务器首先要创建一个socket用于指定需要监听的ip地址和端口。其中主机ip对应了一台主机,而ip+端口对应了一台主机上的某一个应用程序。当服务器将需要监听socket的文件描述符与监听的ip+端口绑定起来时,服务器就可以开启监听。此时客户端便可以连接上服务器这个端口。服务器利用accept查看监听端口中是否有输入事件,如果有就说明有新的客户端连接请求,并会开启一个新的文件描述符用于服务器和客户端的读写操作了。

        selelct 模型,poll模型,epoll模型 为网络通信中的IO多路复用方法。是为了提高网络通信并发量和通信效率而生的。除了IO多路复用方法,还可以利用多线程,多进程等方式,但是效果都不如IO多路复用。

        select模型,poll模型,epoll模型 首先也要经历创建监听socket,监听socket绑定主机和端口,开启监听,accept客户端连接这些阶段。它们和一般网络通信区别在于如何管理这些多个网络通信连接,使通信过程高效。

        在主机上的ip和端口用的是主机字节序,而网络上的通信用的是网络字节序。网络字节序采用大端的方式,即高位字节排放在内存的低地址端(即该值的起始地址),低位字节排放在内存的高地址端。而主机字节序根据CPU种类可能有小端模式或者大端模式。

二、select 模型服务流程

       1.select模型在accpet一堆客户端连接后,便把这些与客户端通信的文件描述符放进fd_set进行管理。fd_set是一个bitmap,哪些文件描述符被加入到fd_set中,该文件描述符在fd_set对应的bit位就会置为1.

       2.select 函数首先会把fd_set从用户态拷贝进内核态。由内核负责遍历哪一个文件描述符中有数据到达。没有数据就进行阻塞。其中第一个参数 maxfd,用于限定遍历范围,指定从0开始到maxfd之前的文件描述符进行遍历。

       3.如果没数据,进程就会进入阻塞状态,并将改进程放入socket的等待队列中。等待客户端把数据发送过来。

        4.网卡接受到了一个或多个客户端发来的数据。此时会产生一个中断,并且调用DMA拷贝,将网卡收到的数据,拷贝到内核环形缓冲区,而后再根据文件描述符信息放入对应的数据接收队列中。并唤醒进程。

        5.如果遍历中发现了多个客户端通信的文件描述符有接收到数据。那么select 函数就会修改fd_set,将有数据的文件描述符对应的bit位置为1,其他bit位置0. 。而后将修改后的fd_set拷贝会用户态,并return有数据的文件描述符的个数。

        6.应用程序可以遍历修改后的fd_set中的bit位,或者遍历文件描述符,判断哪些描述符中有对应的事件发生。而后可以开始与对应的客户端读写数据。

二、select 模式的缺点。

1.由于fd_set是bitmap,通常的最大容量为1024个bit位,意味着最多遍历1024个文件描述符。有最大连接限制。

2.由于fd_set传进select函数中为引用传递,每次循环都要重新赋值fd_set,无法重用。

3。频繁进行用户态和内核态的,当连接多时开销大。

4.  应用程序的轮询就绪的文件描述符的时间复杂度为O(n).

三、poll 概要

        poll流程和select模型流程大多都一样。都要建立监听,accept客户端连接,把客户端连接放在一个集合中,再将集合放到模型中管理,最后遍历有事件的文件描述符,进行通信。

        poll流程和select模型不一样的地方。也就是将集合fd_set 替换成pollfd。也就是poll模型不用bitmap了,而是用一个pollfd的结构体进行操作。再pollfd中设置需要注册的事件events,而,poll函数中不修改events属性,而是修改revents属性来表示发生的事件。 在客户端响应后只需要重置revents就可以达到重用效果。

1.由于pollfd不再是bitmap,而是一个结构体,再poll模型中,内核态采用链表的形式对accept进来的socket进行管理,意味着再内存允许的范围内不再限制最大连接数。

2.pollfd传进poll函数中为引用传递,但是poll不会修改注册的监听事件属性,可以达到重用的效果。

3。依旧频繁进行用户态和内核态的,当连接多时开销大。

4.  时间复杂度依旧为O(n).

四、epoll 服务端实现流程

        参考 深入了解epoll模型(特别详细) - 知乎

        和正常网络服务端程序一样,服务端首先要创建一个socket用于监听,其中socket地址指定需要监听的主机和程序对应的端口。并与socket对应的文件描述符绑定起来。而后利用epoll函数对网络通信的文件描述符管理起来。

        首先要了解epoll的数据结构,对epoll数据结构的管理和具体操作都是在内核态进行,操作系统对epoll管理都已经封装好了,程序员只需要负责调用就行。epoll的数据结构主要包括:

1.存放已就绪事件的就绪队列,它是双向链表构成的。

2.用于管理socket的红黑树。

3.以及双向链表表示的阻塞进程的队列。

        程序员可对epoll的操作主要是3种:

1.epoll_create:

在epoll文件系统建立了个file节点(B+树),并开辟epoll自己的内核高速cache区,建立红黑树,建立双向链表用于存储准备就绪的事件。其中

首先创建一个struct eventpoll对象;然后分配一个未使用的文件描述符;然后创建一个struct file对象,将file中的struct file_operations *f_op设置为全局变量eventpoll_fops,将void *private指向刚创建的eventpoll对象ep;然后设置eventpoll中的file指针;最后将文件描述符添加到当前进程的文件描述符表中,并返回给用户。因为内核初始化时(操作系统启动)注册了一个新的文件系统,叫"eventpollfs"专门由于服务epoll。返回的fd就是该文件系统新建的文件描述符

2.epoll_ctl

        epoll_ctl()首先判断op是不是删除操作,如果不是则将event参数从用户空间拷贝到内核中;获得struct eventpoll对象,接下来会从epoll实例的红黑树里寻找和被监控文件对应的epollitem对象,如果不存在,也就是之前没有添加过该文件;如果是添加操作,那么就会修改已有的或者创建新的epitem加入到红黑树中。

        这个加入过程将epollitem对象添加到被监视文件的等待队列上去。等待队列实际上就是一个回调函数链表,定义在/include/linux/wait.h文件中。通过struct file的poll操作,以回调的方式返回对象的等待队列,这里设置的回调函数是ep_ptable_queue_proc。

        在回调函数ep_ptable_queue_proc中,内核会创建一个struct eppoll_entry对象,然后将等待队列中的回调函数设置为ep_poll_callback()。也就是说,当被监控文件有事件到来时,比如socket收到数据时,ep_poll_callback()会被回调.ep_poll_callback函数主要的功能是将被监视文件的等待事件就绪时,将文件对应的epitem实例添加到就绪队列中

3.epoll_wait

        观察就绪列表里面有没有数据,并进行提取和清空就绪列表.没数据就sleep直到超时。传入epoll_events,用于获取已就绪的事件。而epoll_wait返回已就绪的个数。换而言之epoll_events避免了像select模型中通过遍历所有监听的socket获取已就绪文件的问题。epoll获取已就绪的算法复杂度达到了O(1).

五、epoll示例代码实现

1.epoll实现服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include<unistd.h>
#define MAX 1024
#define SERVER_IP "127.0.0.1"
#define SERVER_HOST "localhost"
// #define SERVER_PORT 1234
int server_init(int port){printf("=============server init========\n");printf("1. create a TCP socket\n");//sock的文件描述符	int mysock = socket (AF_INET,SOCK_STREAM,0);if(mysock < 0){printf("socket call failed\n"); return -1;}printf("2. fill server_addr with host_ip and port number \n");int opt =1; unsigned int len = sizeof(opt);setsockopt(mysock,SOL_SOCKET,SO_REUSEADDR,&opt,len);setsockopt(mysock,SOL_SOCKET,SO_KEEPALIVE,&opt,len);struct sockaddr_in server_addr ;server_addr .sin_family = AF_INET;server_addr .sin_port   =  htons(port);server_addr .sin_addr.s_addr = htonl(INADDR_ANY);printf("3.  bind socket to server address \n");int r =bind (mysock ,(struct sockaddr *) & server_addr  ,sizeof (server_addr ));if( r<0){printf("bind call failed\n"); exit(3);}printf("	hostname =%s, port = %d \n", SERVER_HOST ,port);listen(mysock , 5 );printf("============init done ===========\n");return mysock;
}int main (int argc, char *argv[]){if(argc !=2){printf("usage:./tcpepoll port\n");return 1;	}//char line [MAX];int listensocket=server_init(atoi(argv[1]));printf("listensocket=%d\n",listensocket);if(listensocket < 0){printf("listensocket failed \n");}char buffer[1024];memset(buffer,0,sizeof(buffer));
//create a fd 	int epollfd=epoll_create(1); 
//add a listen eventstruct epoll_event ev;ev.data.fd=listensocket;ev.events=EPOLLIN;epoll_ctl(epollfd,EPOLL_CTL_ADD,listensocket,&ev);while (1){struct epoll_event events[MAX];// waiting event occurtint infds=epoll_wait(epollfd,events,MAX,-1);if(infds <0 ){printf("epoll wait failed \n");break;}if(infds ==0){printf("epoll wait timeout \n");continue;	}for(int ii=0; ii<infds;ii++){if( (events[ii].data.fd == listensocket) &&(events[ii].events & EPOLLIN)){//如果监听的socket有事件,那么说明有客户端程序连接上来struct sockaddr_in client;socklen_t len = sizeof(client);printf("accepting \n");int clientsock = accept(listensocket,(struct  sockaddr*)&client,&len);if(clientsock < 0){printf("accept() fail\n"); continue;}//把监听到的socket加入到epoll中去管理memset(&ev,0,sizeof(struct epoll_event));ev.data.fd=clientsock;ev.events=EPOLLIN;epoll_ctl(epollfd,EPOLL_CTL_ADD,clientsock,&ev);printf("clientsock=%d success \n",clientsock);continue;}else if(events[ii].events & EPOLLIN){//如果是客户端socket有事件char buffer[1024];memset(buffer,0,sizeof(buffer));//读取客户端数据ssize_t  isize = read(events[ii].data.fd,buffer,sizeof(buffer));if(isize <= 0){//已断开的连接从epoll中删除memset(&ev,0,sizeof(struct epoll_event));ev.events = EPOLLIN;ev.data.fd = events[ii].data.fd;epoll_ctl(epollfd,EPOLL_CTL_DEL,events[ii].data.fd,&ev);close(events[ii].data.fd);continue;}printf("recv(eventfd=%d,size=%ld): %s \n",events[ii].data.fd,isize,buffer);//把收到的保温发回客户端write(events[ii].data.fd,buffer,strlen(buffer));}}}close(epollfd);return 0 ;
}

2.客户端采用tcp进行访问

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<unistd.h>#define MAX 256
#define SERVER_HOST "localhost"
#define SERVER_PORT 1234struct sockaddr_in server_addr ;
int sock,r;   int client_init(){printf("=============client init========\n");printf("1. create a TCP socket\n");sock = socket (AF_INET,SOCK_STREAM,0);if(sock < 0){printf("socket call failed\n"); exit(1);}printf("2. fill server_addr with host_ip and port number \n");server_addr .sin_family = AF_INET;server_addr .sin_port   =  htons(SERVER_PORT);server_addr .sin_addr.s_addr = htonl(INADDR_ANY);printf("3.  connecting to server \n");r = connect (sock ,(struct sockaddr *) & server_addr  ,sizeof (server_addr ));	if( r<0){printf("bind call failed\n"); exit(3);}printf("	hostname =%s, port = %d \n", SERVER_HOST ,SERVER_PORT);printf("============client init done ===========\n");return 0;
}int main (){char line [MAX] , ans[MAX];int n; client_init();printf(" ********processing loop *******************");while (1){printf("put a line... \n");bzero(line ,MAX);fgets(line,MAX,stdin);line[strlen(line) - 1] = 0;if(line[0] ==0) exit(0);n=write(sock,line,MAX);printf("client : wrote n =%d bytes ; line %s :\n", n , line);n =read(sock ,ans,MAX);	printf("client : read n =%d bytes ; line %s :\n", n , line);}}

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

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

相关文章

wpf C# 用USB虚拟串口最高速下载大文件 每包400万字节 平均0.7s/M,支持批量多设备同时下载。自动识别串口。源码示例可自由定制。

C# 用USB虚拟串口下载大文件 每包400万字节 平均0.7s/M。支持批量多设备同时下载。自动识别串口。可自由定制。 int 32位有符号整数 -2147483648~2147483647 但500万字节时 write时报端口IO异常。可能是驱动限制的。 之前用这个助手发文件&#xff0c;连续发送&#xff0…

微信小程序AI类目-深度合成-AI问答/AI绘画 互联网信息服务算法备案审核通过教程

近期小程序审核规则变化后&#xff0c;很多使用人类小徐提供的chatGPT系统的会员上传小程序无法通过审核&#xff0c;一直提示需要增加深度合成-AI问答、深度合成-AI绘画类目&#xff0c;该类目需要提供互联网信息服务算法备案并上传资质&#xff0c;一般对企业来说这种务很难实…

kafka学习-消费者

目录 1、消费者、消费组 2、心跳机制 3、消费者常见参数配置 4、订阅 5、反序列化 基本概念 自定义反序列化器 6、位移提交 6.1、自动提交 6.2、手动提交 同步提交 异步提交 7、再均衡 7.1、定义与基本概念 7.2、缺陷 7.3、如何避免再均衡 7.4、如何进行组内分…

Leangoo领歌 -敏捷任务管理软件,任务管理更轻松更透明

​任务管理&#xff0c;简单易懂&#xff0c;就是对任务进行管理。那怎么可以更好进行任务管理呢&#xff1f;怎么样样可以让任务进度可视化&#xff0c;一目了然呢&#xff1f;有效的管理可以让我们事半功倍。 接下来我们看一下如何借助任务管理软件高效的做任务管理。 首先…

国际版腾讯云/阿里云:云解析DNS是什么

云解析DNS是什么 ​ 问答 云解析DNS是一种安全、快速、安稳、牢靠的威望DNS解析处理服务。 云解析DNS为企业和开发者将易于处理辨认的域名转换为计算机用于互连通讯的数字IP地址&#xff0c;然后将用户的拜访路由到相应的网站或应用服务器。 云解析 DNS&#xff08;Domain Nam…

vue学习之事件绑定

事件绑定 创建 demo5.html,内容如下 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</…

DQN模型

1. DQN模型 References [1] 强化学习第五节&#xff08;DQN&#xff09;【个人知识分享】_哔哩哔哩_bilibili

LeetCode算法心得——判断能否在给定时间到达单元格(动态模拟)

大家好&#xff0c;我是晴天学长&#xff0c;这是一个动态模拟题&#xff0c;跟大佬相比&#xff0c;我的有点繁琐了&#xff0c;但是也算是锻炼到自己的一些细节问题&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。 1) .判断能否在给定时间到达单元…

Paper: 利用RNN来提取恶意软件家族的API调用模式

论文 摘要 恶意软件家族分类是预测恶意软件特征的好方法&#xff0c;因为属于同一家族的恶意软件往往有相似的行为特征恶意软件检测或分类方法分静态分析和动态分析两种&#xff1a; 静态分析基于恶意软件中包含的特定签名进行分析&#xff0c;优点是分析的范围覆盖了整个代码…

【C++】list的模拟实现【完整理解版】

目录 一、list的概念引入 1、vector与list的对比 2、关于struct和class的使用 3、list的迭代器失效问题 二、list的模拟实现 1、list三个基本函数类 2、list的结点类的实现 3、list的迭代器类的实现 3.1 基本框架 3.2构造函数 3.3 operator* 3.4 operator-> 3…

vue学习之条件渲染

条件渲染 用于控制组件显示创建 demo6.html,内容如下 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title&…

【数据结构】——排序的相关习题

目录 一、选择填空判断题题型一&#xff08;插入排序——直接插入排序&#xff09;题型二&#xff08;插入排序——折半插入排序&#xff09;题型三&#xff08;插入排序——希尔排序&#xff09;题型四&#xff08;交换排序——冒泡排序&#xff09;题型五&#xff08;交换排序…

Linux内存管理--smaps内存

一、内存的两个概念 了解smaps内存之前要先搞清楚Linux内存管理中的虚拟内存&#xff08;Virtual Memory&#xff09;和驻留内存&#xff08;Resident Memory&#xff09;两个概念。 1、虚拟内存 首先需要强调的是虚拟内存不同于物理内存&#xff0c;虽然两者都包含内存字眼…

[EROOR] SpringMVC之500 回调函数报错

首先&#xff0c;检查一下idea里面的报错的原因&#xff0c;我的是jdk的版本的问题。所以更换一下就可以了。

SpringMVC常用注解、参数传递及页面跳转

一.SpringMVC常用注解 1.1.RequestMapping RequestMapping注解是一个用来处理请求地址映射的注解&#xff0c;可用于映射一个请求或一个方法&#xff0c;可以用在类或方法上。 标注在方法上运行代码 用于方法上&#xff0c;表示在类的父路径下追加方法上注解中的地址将会访…

基于SpringBoot的在线教育平台系统

基于SpringBootVue的线教育平台系统&#xff0c;前后端分离 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 【主要功能】 角色&#xff1a;管理员、学生、老师 …

基于SSM的宿舍管理系统【附源码文档】

基于SSM的宿舍管理系统【附源码文档】 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 【主要功能】 角色&#xff1a;管理员、宿舍管理员、学生 管理员&#xff1a;院系信息、班级信…

阿里云oss上传视频测试,出现了413错误

阿里云oss上传视频测试&#xff0c;出现了413错误 &#xff08;1&#xff09;nginx抛出问题&#xff0c;请求体过大 &#xff08;2&#xff09;修改nginx配置&#xff0c;重新加载生效 client_max_body_size 1024m;在cmd下运行命令&#xff1a;nginx.exe -s reload

408-2011

一、选择题&#xff08;2分/题&#xff09; 1.设 n 是描述问题规模的非负整数&#xff0c;下列程序片段的时间复杂度是______。 x2; while(x<n/2){x2*x; } A.O() B.O(n) C.O() D.O(n^2) 解答&#xff1a;A 假设执行 y次&#xff0c;则 (2^y)*xn/2,y&a…

【项目 计网11】4.29 epoll API介绍 4.30 epoll 代码编写 4.31 epoll的两种工作模式

4.29 epoll API介绍 epoll_create实例在内核区&#xff0c;创建了一个eventpoll结构体。这个函数的返回值是一个文件描述符&#xff0c;通过这个fd去操纵eventpoll #include <sys/epoll.h> //创建一个新的epoll实例。在内核中创建了一个数据&#xff0c;这个数据中有两个…