epoll学习笔记

高级 IO

select

select是同步的,由进程自己操作
int select
(int nfds,fd_set * readfds,fd_set * writefds,fd_set* exceptfds,struct timeval * timeout );

返回值:
大于0:所有监听集合(3个)中,满足对应事件的总数       
0:没有满足的      
-1:error
参数:
nfds: 最大的描述符+1的值
readfds/writefds/exceptfds: 可读/可写/异常事件监控集合,关心则将描述符加入集合
timeout:timeval{tv_sec,tv_usec} select超时等待时间
select 默认timeout 为NULL,永久阻塞,直到,直到描述符就绪

1.用户自己定义相应事件(读,写,异常)描述符集合 fd_set;
用户关心 客户事件某一事件(如可读),则添加到集合(如 fd_set* readfds),关心所有的事件,则添加到所有集合

2.select将集合拷贝到内核遍历轮询,判断有没描述符就绪对应事件

3.若集合中有描述符就绪,先遍历完毕集合,然后把没有就绪的文件描述符从集合移除,这个时候返回调用,通知进程有多少描述符就绪

  • 这个时候集合保存的都是就绪的描述符

4.进程处理就绪的描述符

fd_set是一个位图,用于存储监控的描述符,需要监控则置1,大小取决于__FD_SETSIZE=1024宏

void FD_CLR(int fd, fd_set * set); //将一个文件描述符从集合中移除  
int FD_ISSET(int fd, fd_set * set);  //判断一个文件描述符是否在一个集合中,返回值:在 1,不在 0
void FD_SET(int fd, fd_set * set);  //将监听的文件描述符,添加到监听集合中
void FD_ZERO(fd_set * set);    //清空一个文件描述符集合

优缺点:
select 能够监控的描述符有上限,1024 __FD_SETSIZE

缺点

  1. 性能实现监控原理时轮询,性能随着描述符增多而下降
  2. select并不会告诉用户具体哪个描述符就绪,(只给就绪集合)因此需要用户自己通过判断哪个描述符在集合中,这个判断是一个遍历过程。
  3. select会修改描述符集合,每次监控需要用户重新向监控集合中重新添加描述符
  4. 每次都需要将监控集合重新将监控拷贝到内核进行监控
    优点
  5. posix标准,可以跨平台
  6. 监控超时等待可以精细到微秒

poll

linxu 下使用,
原理:
int poll( struct pollfd * fds,nfds_t nfds,int timeout)
struct pollfd{
int fd;
short events;//requested,描述符关心事件 POLLIN/POLLOUT
short revents;…returned 描述符实际就绪的事件
}

实现原理:
1 用户定义描述符事件数组,向数组中添加关心的描述符事件
2 将pollfd事件数组拷贝到内核轮询监控,判断是否就绪关心的事件events
3 把描述符实际就绪的事件信息,标记到revents
3 当poll 返回,用户遍历pollfd事件数组,判断每一个事件revents是否是关心的事件,

poll和select对比

poll 对比select 没有监控的文件描述符上限
采用事件结构信息进行描述符监控,简化了三种描述符的操作流程

但是poll依然需要将事件结构信息拷贝到内核进行监控
在内核中需要轮询遍历的方式进行描述符事件监控(随着描述符增多而性能降低)
poll也不会告诉用户具体哪个描述符就绪,需要遍历判断revents来决定描述符应该进行何种操作

epoll

int epoll_create(int size) //在内核中创建eventpoll结构体
						// size 决定epoll监控多少描述符
						//返回值:返回描述符,决定epoll的操作句柄
						//struct eventpoll {  红黑树,双向链表}

int epoll_ctl (int epfd,int op,int fd,struct epoll_event *  event)

对内核eventpoll结构体操作,采用时间结构方式对描述符进行事件监控 用户定义struct
epoll_event描述符事件结构信息,将事件信息可以拷贝到北河,添加到eventpoll的红黑树结点

epfd:epoll 操作句柄
op:对内核eventpoll进行的操作

EPOLL_CTL_ADD 向红黑树中添加描述符fd的监控事件信息event
EPOLL_CTL_DEL 从红黑树中移除fd的监控事件信息event
EPOLL_CTL_MOD 修改描述符fd在红黑树的监控事件信息event

fd: 用户需要监控的描述符
event: 描述符对应的事件结构信息

    struct epoll_event{
    	uint32_t events;//用户对描述符进行监控的事件(EPOLLIN/EPOLLOUT   读写)
    	union{
    			int fd;
    			void* ptr;
    	}data
    }

    int epoll_wait (int epfd,struct epoll_event* events,int maxevents, int timeout)

epfd:epoll 操作句柄
events :epoll _event事件结构信息数组
maxevents :epoll_events

epoll监控流程:
epoll 对描述符的事件监控是一个异步操作;epoll_wait 发起调用,让操作系统对描述符进行相应事件监控,操作系统对每个要监控的描述符都定义了就绪事件回调函数,当描述符相应事件就绪的时候,触发事件,调用回调函数(将描述符事件结构信息添加到eventpoll的双向链表中)

epoll_wait没有返回(异步阻塞操作),每隔一会查看eventpoll中双向链表是否为空(为空:没有描述符就绪,则等待一会,链表不为空表示有描述符就绪;将这个描述符对应事件结构信息,拷贝到epoll_wait传入的事件结构数组中后调用返回,事件回调的方式)

epoll_wait 将就绪的描述符对应事件结构信息拷贝到events结构数组;相当于直接告诉用户哪个描述符就绪;


//epoll大致逻辑代码
    bool Init()
    bool add(sock)
    bool del(sock)
    bool wait(list ,int timeout)
    
    class Epoll
    {
    private:
    			int _epfd;
    	public:
    			Init()
    			{
    				_epfd=epoll_create(1;
    			}
    			Add(sock)
    			//int epoll_ctl (int epfd,int op,int fd,struct epoll_event *  event)
    			{
    				fd=sock.getfd();
    				struct epoll_event ev;
    				ev.events=EPOLLIN;//读
    				//=EPOLLIN|EPOLLET   j就是边缘触发了
    				int ret=epoll_ctl(_epfd,EPOLL_CTL_ADD,fd,&ev);//ret<0==>return false
    			}
    			Del(sock)
    			{
    				//同add
    				//epoll_ctl(_epfd,EPOLL_CTL_DEL,fd,NULL)
    			}
    			Wait(vector<tcosocket> & list,int timeout_msec=3000)//3000毫秒,3秒
    			{
    			//  int epoll_wait (int epfd,struct epoll_event* events,int maxevents, int timeout)
    				struct epoll_event evs[MAX_EVENTS];
    				int nfds=epoll_wait(_epfd,evs,MAX_EVENTS,timeout_msec);//nfds<0,=0 出错和超时
    				for(0-nfds)
    				{
    					tcpsocket sock;
    					sock(evs[i].data.fd)
    					list.push_back(sock);//list里面的都是就绪的
    				}
    			}
    			
    }

边缘触发,水平触发

水平触发 默认
可读事件就绪:接受缓冲区中数据大小,大于低水位标记
可写事件就绪:发送缓冲区中空闲空间大小,大于低水位标记
只要接受/发送缓冲区数据大于低水位标记就会一直触发事件

边缘触发
可读事件就绪:接受缓冲区,只要新数据到来才触发
可写事件就绪 :发送缓冲区中,只有剩余空间大小从0变成>0时才会触发一次

边缘触发中,只有新数据到来的时候,可读事件才会被触发一次
需要用户在这一次事件中把缓冲区中数据全部读取完毕为止(循环读到不能读为止)
但是recv中缓冲区没有数据时,recv会阻塞,为了避免循环读取数据导致进程阻塞,把描述符设置为非阻塞
//fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK)

优缺点

事件结构方式对描述符进行监控,简化了select集合操作的流程
epoll描述符监控无上限
每个epoll 监控的描述符事件信息,只需要向内核拷贝一次
epoll_wait使用异步阻塞操作在内核完成事件监控

  • 事件监控是操作系统通过事件回调的方式将就绪描述符信息添加到就绪的事件信息
  • epoll_wait 只是间隔一段时间去查看双向链表是否为空判断描述符就绪,性能不随描述符增加而降低

epoll 直接通过epoll_wait 传入的事件结构数组 向用户返回就绪的事件信息
直接告诉用户哪些描述符就绪,不需要用户进行空遍历

场景 大量描述符监控,同一时间部分描述符活跃的场景 事件监控

你可能感兴趣的:(linux和操作系统,C/C++)