现在的位置: 首页 > 综合 > 正文

socket编程epoll函数–epoll实现过程分析

2018年04月25日 ⁄ 综合 ⁄ 共 4175字 ⁄ 字号 评论关闭

epoll对应的三个主要函数

1、int epoll_create(int size)与int epoll_create1(int flags)

epoll_create1 产生一个epoll 实例,返回的是实例的句柄epollfd。flag 可以设置为0 或者EPOLL_CLOEXEC,为0时函数表现与epoll_create一致,EPOLL_CLOEXEC标志与open 时的O_CLOEXEC 标志类似,即进程被替换时会关闭文件描述符。

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

(1)epfd:epoll 实例句柄;
(2)op:对文件描述符fd 的操作,主要有EPOLL_CTL_ADD、 EPOLL_CTL_DEL等;
(3)fd:需要操作的目标文件描述符;
(4)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 events */
        epoll_data_t  data;        /* User data variable */
 };

events 参数主要有EPOLLIN、EPOLLOUT、EPOLLET、EPOLLLT等;一般data 共同体我们设置其成员fd即可,也就是epoll_ctl 函数的第三个参数。

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

(1)epfd:epoll 实例句柄;
(2)events:结构体指针
(3)maxevents:事件的最大个数
(4)timeout:超时时间,设为-1表示永不超时

以上内容可参考:http://www.cppfans.org/1418.html

          每个epollfd在内核中有一个对应的eventpoll结构对象。其中关键的成员是一个readylist(eventpoll:rdllist)和一棵红黑树(eventpoll:rbr)。一个fd被添加到epoll中之后(EPOLL_ADD),内核会为它生成一个对应的epitem结构对象。epitem被添加到eventpoll的红黑树中.红黑树的作用是使用者调用EPOLL_MOD的时候可以快速找到fd对应的epitem。调用epoll_wait的时候,将readylist中的epitem出列,将触发的事件拷贝到用户空间。之后判断epitem是否需要重新添加回readylist。epitem重新添加到readylist必须满足下列条件:

        1) epitem上有用户关注的事件触发;

        2) epitem被设置为水平触发模式(如果一个epitem被设置为边界触发则这个epitem不会被重新添加到readylist中)。

更为具体内容请参考http://www.th7.cn/system/lin/201403/51432.shtml

回射服务器程序(附解释)

#include "unp.h"
#include <limits.h>
#include <vector>
#include <sys/epoll.h>
#include <iostream>
using namespace std;

typedef std::vector<struct epoll_event> EventList;

int main(int argc, char **argv){
	int i,maxConn,listenfd, connfd, sockfd;
	int epfd;
	socklen_t cliLen;
	char buffer[MAXLINE];
	
	struct sockaddr_in seraddr,cliaddr;
	listenfd = Socket(AF_INET, SOCK_STREAM, 0);
	
	bzero(&seraddr, 0);
	seraddr.sin_family = AF_INET;
	seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	seraddr.sin_port = htons(SERV_PORT);
	
	Bind(listenfd, (SA *)&seraddr, sizeof(seraddr));
	
	Listen(listenfd, LISTENQ);
	
	struct epoll_event event;
	event.data.fd = listenfd;
	event.events = EPOLLIN | EPOLLET;
	
	if((epfd = epoll_create1(EPOLL_CLOEXEC))<0)
		err_quit("epoll_create1 error");
	if(epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event)<0)
		err_quit("epoll_ctl error");
	
	EventList events(20);
	std::vector<int> clients;
	size_t eventSize = 0 ;
	int readsize = 0, nready;
	
	cliLen = sizeof(cliaddr);
	for(;;){
		eventSize = events.size() ;
		nready = epoll_wait(epfd, &*events.begin(), eventSize, -1);
		
		if(nready == -1){
			if(errno==EINTR)
				continue;
			err_quit("epoll_wait error");
		}
		
		if(nready == 0)
			continue;
		
		if(nready == eventSize)
		{
			events.resize(2*eventSize);
		}
		
		for(int i = 0; i < nready; i++ ){
			if(events[i].data.fd == listenfd){
				connfd = Accept(listenfd, (SA *)&cliaddr, &cliLen);
				
				clients.push_back(connfd);
				
				event.data.fd = connfd;
				event.events = EPOLLIN | EPOLLET;
				epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &event);
			}
			else if(events[i].events & EPOLLIN){	
				sockfd = events[i].data.fd;
				if((readsize = read(sockfd, buffer, MAXLINE))<0){
					if(errno==ECONNRESET)
						close(sockfd);
					else
						err_sys("read error");
				}else if(readsize==0){
					close(sockfd);
					
					event = events[i];
					epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, &event);
					clients.erase(std::remove(clients.begin(), clients.end(), conn), clients.end());
				}else{
					write(sockfd, buffer, readsize);
				}
			}
		}
	}	
	
	exit(0);
}

代码分析(结合epoll流程):

1~31行  与普通的套接字编程步骤相同。unp.h是《Unix网络编程中随书代码》,unp.h中包含了一些变量的定义和函数的声明在这里不必太在意

32行    epoll_create1()返回一个epollfd(在程序中取名为epfd),此时epollfd在内核中有一个对应的eventpoll结构对象。eventpoll中关键的成员是一个readylist
(eventpoll:rdllist)
和一棵红黑树(eventpoll:rbr)。

34行   epoll_ctl()添加listenfd到epoll中。listen有两个队列:一个是已完成连接(三路握手)队列,另一个是未完成连接队列。listenfd被添加epoll之后(EPOLL_CTL_ADD),内核会为它生成一个对应的epitem结构对象。epitem被添加到eventpoll的红黑树中。

45行   第一次进入for循环执行45行的epoll_wait()函数时,由于是第一次进入循环epollfd(在程序中取名为epfd)所对应的eventpoll的红黑树中只有一个与listenfd对应的epitem结构的数据。也就是说此时,epoll_wait()函数是在等待监听套接字listenfd的队列中存在三路握手连接完成。因为epoll_wait()函数最有一个参数设置为-1,所以不存在epoll_wait()函数等待超时返回的情况。一旦listenfd的已连接不为空,相当于有I/O准备好,epoll_wait()函数一旦监测到便会立即返回。以上谈论的是第一次进入for循环的情况。第二次、第三次......以后epoll_wait()函数不仅要监测listenfd是否有新的三路握手完成,还要监测connfd已连接的客户端是否有I/O准备好。

61行
  从61行开始以后的for循环用来处理已准备好的I/O。至于for循环中的if语句,如果某个epoll事件的fd是listenfd的话表明有新的客户连接,需要使用accept函数完成服务器与客户端的连接,并将connfd添加epollfd中,此时内核为connfd为之生成一个对应的
epitem结构对象,epitem被添加到eventpoll的红黑树中;如果某个epoll事件中的events标志EPOLLIN,表明关联的fd可以进行读操作了,这时候服务器从套接字中读取数据并回射给客户端(这段代码是回射服务器程序)。

参考:http://www.th7.cn/system/lin/201403/51432.shtml

         

http://blog.csdn.net/vividonly/article/details/7539342

             http://www.cppfans.org/1418.html

抱歉!评论已关闭.