Linux C : select简介和epoll 实现

news2025/1/17 17:57:57

目录

一、基础知识

二、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 event
	struct 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 occurt
	   int 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 1234
 
 
struct 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.coloradmin.cn/o/997904.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

flink的几种常见的执行模式

背景 在运行flink时&#xff0c;我们经常会有几种不同的执行模式&#xff0c;比如在IDE中启动时&#xff0c;通过提交到YARN上&#xff0c;还有通过Kebernates启动时&#xff0c;本文就来记录一下这几种模式 flink的几种执行模式 flink嵌入式模式&#xff1a; 这是一种我们在…

基于keras中Lenet对于mnist的处理

文章目录 MNIST导入必要的包加载数据可视化数据集查看数据集的分布开始训练画出loss图画出accuracy图 使用数据外的图来测试图片可视化转化灰度图的可视化可视化卷积层的特征图第一层卷积 conv1 和 pool1第二层卷积 conv2 和 pool2 MNIST MNIST&#xff08;Modified National …

从零开始的PICO教程(4)--- UI界面绘制与响应事件

从零开始的PICO教程&#xff08;4&#xff09;— UI界面绘制与响应事件 文章目录 从零开始的PICO教程&#xff08;4&#xff09;--- UI界面绘制与响应事件一、前言1、大纲2、教程示例 二、具体步骤1、PICO VR环境配置2、XR的UI Canvas画布创建与调整&#xff08;1&#xff09;C…

【雷达原理】雷达信号级建模与仿真

目录 前言一、LFMCW信号概述1.1 优点1.2 缺点 二、LFMCW信号模型2.1 发射信号模型2.2 接收信号模型2.3 信号混频 三、MATLAB仿真3.1 仿真结果3.2 代码 四、参考文献 前言 雷达信号形式多种多样&#xff0c;按照雷达的体制进行分类&#xff0c;有脉冲雷达和连续波雷达。脉冲雷达…

C#程序到底从哪里开始看,从Main函数开始,那么Main函数是什么?

视觉人机器视觉粉丝问我,拿到自己公司得架构,问我,C#程序到底从哪里看,从Main函数开始,那么Main函数是什么? Main()函数 Main()是C#应用程序的入口点,执行这个函数就是执行应用程序。也就是说,在执行过程开始时,会执行Main()函数,在Main()函数执行完毕时,执行过…

微信小程序上拉触底事件

一、什么是上拉触底事件 上拉触底是移动端的专有名词&#xff0c;通过手指在屏幕上的上拉滑动操作&#xff0c;从而加载更多数据的行为。 二、监听上拉触底事件 在页面的.js文件中&#xff0c;通过onReachBottom()函数即可监听当前页面的上拉触底事件。 三、配置上拉触底距…

vue组件库开发,webpack打包,发布npm

做一个像elment-ui一样的vue组件库 那多好啊&#xff01;这是我前几年就想做的 但webpack真的太难用&#xff0c;也许是我功力不够 今天看到一个视频&#xff0c;早上6-13点&#xff0c;终于实现了&#xff0c;呜呜 感谢视频的分享-来龙去脉-大家可以看这个视频&#xff1a;htt…

【List篇】ArrayList 的线程不安全介绍

ArrayList 为什么线程不安全&#xff1f; 主要原因是ArrayList是非同步的,没有同步机制,并且其底层实现是基于数组&#xff0c;而数组的长度是固定的。当对 ArrayList 进行增删操作时&#xff0c;需要改变数组的长度&#xff0c;这就会导致多个线程可能同时操作同一个数组&…

Unlikely argument type for equals(): int seems to be unrelated to String

前面字符串 后面数值 if (new Integer(2).equals(loginUser.getStatus())) 或者另外定义一个吧

JAVASE 窗口

本文目录 1、前言2、JFrame、JButton3、JLabl4、ImageIcon 1、前言 java提供了很多已经写好了的类供我们使用&#xff0c;而我们没必要去细腻研究它的构成原理&#xff0c;就好比我们让我们编程让机器人动起来&#xff0c;没必要细腻研究机器人每个器件是怎么做出来的一样&…

Qt Designer UI设计布局小结

目录 前言1 居中布局2 左右布局3 上下布局4 复杂页面布局总结 前言 本文总结了在开发Qt应用程序时使用 Designer 进行UI布局的一些心得体会。Qt Designer是Qt提供的一个可视化界面设计工具&#xff0c;旨在帮助开发人员快速创建和布局用户界面。它提供了丰富的布局管理器和控件…

系统架构设计专业技能 · 计算机组成与结构

现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the future of dream weaving wings, let the dream fly in reality. 点击进入系列文章目录 系统架构设计高级技能 计算机组成与结构 一、计算机结构1.1 CPU 组成1.2 冯诺依曼…

【数据结构-队列】阻塞队列

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

vue学习之 v-for key

v-for key Vue使用 v-for渲染的元素列表时&#xff0c;它默认使用“就地更新”的策略。如果数据项的顺序被改变&#xff0c;Vue 将不会移动 DOM元素来匹配数据项的顺序&#xff0c;而是就地更新每个元素。创建 demo9.html,内容如下 <!DOCTYPE html> <html lang"…

60、RESTful 的高级配置---HttpMessageConverter

★ HttpMessageConverter的作用 RequestBody修饰处理方法的参数&#xff0c;如获取json格式的数据&#xff0c;将json格式的数据转换成我们需要的java对象&#xff0c; ResponseBody 这些把对象转成json格式响应给前端&#xff0c; 底层都是由这个HttpMessageConverter类实现的…

【Redis专题】大厂生产级Redis高并发分布式锁实战

目录 前言课程内容一、一个案例引发的思考二、Redis分布式锁的演进2.1 单纯使用Redis的setnx实现分布式锁2.2 setnx 过期时间3.3 Redisson实现分布式锁&#xff1a;setnx 过期时间 锁续命 三、Redisson客户端实现的分布式锁及源码分析 学习总结 前言 Redis中间件&#xff0…

文件上传之图片码混淆绕过(upload的16,17关)

目录 1.upload16关 1.上传gif loadup17关&#xff08;文件内容检查&#xff0c;图片二次渲染&#xff09; 1.上传gif&#xff08;同上面步骤相同&#xff09; 2.条件竞争 1.upload16关 1.上传gif imagecreatefromxxxx函数把图片内容打散&#xff0c;&#xff0c;但是不会…

Selenium - Tracy 小笔记2

selenium本身是一个自动化测试工具。 它可以让python代码调用浏览器。并获取到浏览器中加们可以利用selenium提供的各项功能。帮助我们完成数据的抓取。它容易被网站识别到&#xff0c;所以有些网站爬不到。 它没有逻辑&#xff0c;只有相应的函数&#xff0c;直接搜索即可 …

dubbo 服务注册使用了内网IP,而服务调用需要使用公网IP进行调用

一、问题描述&#xff1a; 使用dubbo时&#xff0c;提供者注册时显示服务地址ip为[内网IP:20880]&#xff0c;导致其他消费者在外部连接的情况下时&#xff0c;调用dubbo服务失败 二、解决办法 方法一、修改hosts文件 &#xff08;1&#xff09;. 先查询一下服务器的hostna…

【动态规划刷题 13】最长递增子序列 摆动序列

300. 最长递增子序列 链接: 300. 最长递增子序列 1.状态表示* dp[i] 表⽰&#xff1a;以 i 位置元素为结尾的「所有⼦序列」中&#xff0c;最⻓递增⼦序列的⻓度。 2.状态转移方程 对于 dp[i] &#xff0c;我们可以根据「⼦序列的构成⽅式」&#xff0c;进⾏分类讨论&#…