Sylar C++高性能服务器学习记录11 【IO调度模块-知识储备篇】

早在19年5月就在某站上看到sylar的视频了,一直认为这是一个非常不错的视频。
由于本人一直是自学编程,基础不扎实,也没有任何人的督促,没能坚持下去。
每每想起倍感惋惜,遂提笔再续前缘。

为了能更好的看懂sylar,本套笔记会分两步走,每个系统都会分为两篇博客。
分别是【知识储备篇】和【代码分析篇】
(ps:纯粹做笔记的形式给自己记录下,欢迎大家评论,不足之处请多多赐教)
QQ交流群:957100923
B站视频:https://b23.tv/YusP39I


IO调度模块-知识储备篇

一、select

sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof (addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd,(struct sockaddr*)&addr ,sizeof(addr));
listen (sockfd, 5); 

for (i=0;i<5;i++) {
	memset(&client, 0, sizeof (client));
	addrlen = sizeof(client);
	fds[i] = accept(sockfd,(struct sockaddr*)&client, &addrlen);
	if(fds[i] > max)
	max = fds[i];
}

//-------------------------------------------------------------
  
while(1){
	FD_ZERO(&rset);
	for (i = 0; i< 5; i++ ) {
		FD_SET(fds[i],&rset);
	}
	
 	puts("round again");
	select(max+1, &rset, NULL, NULL, NULL);
	
	for(i=0;i<5;i++) {
		if (FD_ISSET(fds[i], &rset)){
			memset(buffer,0,MAXBUF);
			read(fds[i], buffer, MAXBUF);
			puts(buffer);
		}
	}	
}

我们对代码进行分析

FD_ZERO(&rset);
for (i = 0; i< 5; i++ ) {
	FD_SET(fds[i],&rset);
}

可以看到每次循环都需要重置每一个fd的状态,这里不是很合理

//我们循环创建5个文件句柄,存放到 fds 数组中
for (i=0;i<5;i++) {
	memset(&client, 0, sizeof (client));
	addrlen = sizeof(client);
	//通过这行代码我们会得到一个随机的文件句柄,默认是1024以内的
	fds[i] = accept(sockfd,(struct sockaddr*)&client, &addrlen);
	//由于select是使用bitmap来标记对应fd是否有消息,所以我们需要知道fd的最大值
	if(fds[i] > max)
	max = fds[i];
}

如果我们得到的fds是[3,9,7,2]
那么我们就需要计算出用来标记的bitmap最大尺寸就是max = 9+1
也就是 0000000000 这样10个位置

select(max+1, &rset, NULL, NULL, NULL);

当我们调用select方法时,bitmap就会做以下的转换:
0000000000
0011000101
其中1的位置表示有fd的存在也就是位置被坐了,0表示没有fd也就是位置空着
其中的rset会从用户态拷贝一份到内核态执行,存在一定的开销

//当句柄发生变化是需要遍历全部的句柄查看是哪个fd有数据
for(i=0;i<5;i++) {
	if (FD_ISSET(fds[i], &rset)){
		memset(buffer,0,MAXBUF);
		read(fds[i], buffer, MAXBUF);
		puts(buffer);
	}
}	

所以我们可以得出select有以下几个缺点:

  1. 监听有上限,默认是 1024个
  2. FDset不可重用,需要每次都循环重置
  3. 判断某个fd是否有信息需要遍历全部的fd
  4. select的底层实现上需要将rset由用户态拷贝到内核态执行,所以有拷贝操作的开销

二、poll

for (i=0;i<5;i++) {
    memset(&client, 0, sizeof (client));
    addrlen = sizeof(client);
    pollfds[i].fd = accept(sockfd,(struct sockaddr*)&client, &addrlen);
    pollfds[i].events = POLLIN;
}
sleep(1);

//-------------------------------------------------------------------

while(1){
 	puts("round again");
	poll(pollfds, 5, 50000);

	for(i=0;i<5;i++) {
		if (pollfds[i].revents & POLLIN){
			pollfds[i].revents = 0;
			memset(buffer,0,MAXBUF);
			read(pollfds[i].fd, buffer, MAXBUF);
			puts(buffer);
		}
	}
}

以上代码我们可以看到,监听的fd没有长度限制
我们的fd被包装成了结构体,这样取消了rset用revents来代替,所以只需要重置revents就可以

struct pollfd {
	int fd;			//文件句柄
	short events;	//操作类型
	short revents;	//状态
};

所以虽然poll比起select优化了一些,但是还存在以下缺点:

  1. 判断某个fd是否有信息需要遍历全部的fd
  2. poll的底层实现上还是需要将信息从用户态拷贝到内核态,所以有一定的开销

三、epoll

struct epoll_event events[5];
 int epfd = epoll_create(10);
 ...
 ...
 for (i=0;i<5;i++) 
 {
   static struct epoll_event ev;
   memset(&client, 0, sizeof (client));
   addrlen = sizeof(client);
   ev.data.fd = accept(sockfd,(struct sockaddr*)&client, &addrlen);
   ev.events = EPOLLIN;
   epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev); 
 }

//--------------------------------------------------------------------  

while(1){
 	puts("round again");
 	nfds = epoll_wait(epfd, events, 5, 10000);
	for(i=0;i<nfds;i++) {
			memset(buffer,0,MAXBUF);
			read(events[i].data.fd, buffer, MAXBUF);
			puts(buffer);
	}
}

epoll取消了标记位,那么如何告知用户哪些fd有消息呢?

nfds = epoll_wait(epfd, events, 5, 10000);

这里epoll_wait有返回值,返回的值表示有信息的fd的个数。
难道通过个数就能知道哪些fd有信息了吗?显然不行。
但是epoll_wait会将events进行排序,将有消息的排到前面,
这样一来只要知道个数就能知道哪些fd有消息了。
假如有以下5个fd:
[fd1,fd2,fd3,fd4,fd5]

如果这时候 fd2和fd5有消息来了就会变成:
[fd2,fd5,fd1,fd3,fd4]
并且告诉你有2个消息

那么你就可以直接取出数组中的前两个就行了。

而且epoll的底层实现上共用了events,无需从用户态切换到内核态,较少了不必要的开销。


参考资料: 【IO多路复用】强烈建议花30分钟看一看。博客里描述很糙,这个视频看完一定会有帮助。


四、epoll的API介绍

1.方法列表
//头文件
#include <sys/epoll.h>  

//创建一个epoll实例 
int epoll_create(int size);   

//控制epoll上的事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
 
 //阻塞等待事件发生
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout); 
2.epoll_event
typedef union epoll_data {
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;
 
struct epoll_event {
  uint32_t events;	
  epoll_data_t data;
}

其中的events有以下几种:

EPOLLIN:可读事件,表示连接上有数据可读。
EPOLLOUT:可写事件,表示连接上可以写入数据。
EPOLLPRI:紧急事件,表示连接上有紧急数据可读。
EPOLLRDHUP:连接关闭事件,表示连接已关闭。
EPOLLERR:错误事件,表示连接上发生错误。
EPOLLHUP:挂起事件,表示连接被挂起。
3.epoll_create
//创建一个epoll fd,返回一个新的epoll文件描述符。
//参数size用于指定监听的文件描述符个数,但是在Linux 2.6.8之后的版本,该参数已经没有实际意义。传入一个大于0的值即可。
int epfd=epoll_create(1);
4.epoll_ctl
//用于向epoll实例中添加、修改或删除关注的文件描述符和对应事件
//epfd:epoll文件描述符,通过epoll_create函数创建获得。
//op:操作类型,可以是以下三种取值之一:
//EPOLL_CTL_ADD:将文件描述符添加到epoll实例中。
//EPOLL_CTL_MOD:修改已添加到epoll实例中的文件描述符的关注事件。
//EPOLL_CTL_DEL:从epoll实例中删除文件描述符。
//fd:要控制的文件描述符。
//event:指向epoll_event结构体的指针,用于指定要添加、修改或删除的事件。
//成功时返回0,表示操作成功。
//失败时返回-1,并设置errno错误码来指示具体错误原因。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
5.epoll_wait
//epfd:epoll文件描述符,通过epoll_create函数创建获得。
//events:用于接收事件的epoll_event结构体数组。
//maxevents:events数组的大小,表示最多可以接收多少个事件。
//timeout:超时时间,单位为毫秒,表示epoll_wait函数阻塞的最长时间。常用的取值有以下三种:
//-1:表示一直阻塞,直到有事件发生。
//0:表示立即返回,不管有没有事件发生。
//> 0:表示等待指定的时间(以毫秒为单位),如果在指定时间内没有事件发生,则返回。
//成功时返回接收到的事件的数量。如果超时时间为0并且没有事件发生,则返回0。
//失败时返回-1,并设置errno错误码来指示具体错误原因。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
6.工作模式

水平触发(Level Triggered, LT)模式(默认)
有事件就一直不断通知

  1. 当被监控的文件描述符上的状态发生变化时,epoll会不断通知应用程序,直到应用程序处理完事件并返回。
  2. 如果应用程序没有处理完事件,而文件描述符上的状态再次发生变化,epoll会再次通知应用程序。
  3. 应用程序可以使用阻塞或非阻塞I/O来处理事件。
  4. 水平触发模式适合处理低并发的I/O场景。
int epfd=epoll_create(1);
struct epoll_event even;
even.events=EPOLLIN;  //用水平触发模式来检测
even.data.fd=lfd;

边缘触发(Edge Triggered, ET)模式
有事件只通知一次,后续一次处理没解决完的内容需要程序员自己解决

  1. 仅当被监控的文件描述符上的状态发生变化时,epoll才会通知应用程序。
  2. 当文件描述符上有数据可读或可写时,epoll会立即通知应用程序,并且保证应用程序能够全部读取或写入数据,直到读写缓冲区为空。
  3. 应用程序需要使用非阻塞I/O来处理事件,以避免阻塞其他文件描述符的事件通知。
  4. 边缘触发模式适合处理高并发的网络通信场景。
int epfd = epoll_create(1);
struct epoll_event even;
even.events = EPOLLIN | EPOLLET; //使用边缘触发模式检测
even.data.fd = lfd;

五、总结

epoll用法很简单,了解了他的概念和使用方法就可以了,具体的内部实现我希望不要过分深入,毕竟我们要学的东西很多。

【最后求关注、点赞、转发】
QQ交流群:957100923
在这里插入图片描述

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值