(4.5)监听端口实战、epoll介绍及原理详析

一:监听端口

(1.1)开启监听端口

  (4.5)监听端口实战、epoll介绍及原理详析_第1张图片

 

二:epoll技术简介 

//(2.1)epoll概述


    //(1)I/O多路复用epoll就是一种典型的I/O多路复用技术:epoll技术的最大特点是支持高并发;
     //传统多路复用技术select,poll,在并发量达到1000-2000,性能就会明显下降;
     //epoll,kquene(freebsd)
     //epoll,从linux内核2.6引入的,2.6之前是没有的;


    //(2)epoll和kquene技术类似:单独一台计算机支撑少则数万,多则数十上百万并发连接的核心技术;
    //epoll技术完全没有这种性能会随着并发量提高而出现明显下降的问题。但是并发没增加一个,必定要消耗一定的内存去保存这个连接相关的数据;
     //并发量总还是有限制的,不可能是无限的;


    //(3)10万个连接同一时刻,可能只有几十上百个客户端给你发送数据,epoll只处理这几十上百个客户端;


    //(4)很多服务器程序用多进程,每一个进程对应一个连接;也有用多线程做的,每一个线程对应 一个连接;
    //epoll事件驱动机制,在单独的进程或者单独的线程里运行,收集/处理事件;没有进程/线程之间切换的消耗,高效


    //(5)适合高并发,融合epoll技术到项目中,作为大家将来从事服务器开发工作的立身之本;
    //写小demo非常简单,难度只有1-10,但是要把epoll技术融合到商业的环境中,那么难度就会骤然增加10倍;

 

 

三:epoll原理与函数介绍:三个函数

//(3.1)课件介绍

    课件地址
    //a)c1000k_test这里,测试百万并发的一些测试程序;一般以main();
    //b)ntytcp:nty_epoll_inner.h,nty_epoll_rb.c
          //epoll_create();
          //epoll_ctl();
          //epoll_wait();
          //epoll_event_callback();  

 

//(3.2)epoll_create()函数


 //格式:int epoll_create(int size);
 //功能:创建一个epoll对象,返回该对象的描述符【文件描述符】,这个描述符就代表这个epoll对象,后续会用到;
 //这个epoll对象最终要用close(),因为文件描述符/句柄 总是关闭的;
 //size: >0;

 

(4.5)监听端口实战、epoll介绍及原理详析_第2张图片
    //原理:
    //a)struct eventpoll *ep = (struct eventpoll*)calloc(1, sizeof(struct eventpoll)); 
    //b)rbr结构成员:代表一颗红黑树的根节点[刚开始指向空],把rbr理解成红黑树的根节点的指针;
       //红黑树,用来保存  键【数字】/值【结构】,能够快速的通过你给key,把整个的键/值取出来;
    //c)rdlist结构成员:代表 一个双向链表的表头指针;
       //双向链表:从头访问/遍历每个元素特别快;next。
    //d)总结:创建了一个eventpoll结构对象,被系统保存起来;
       //rbr成员被初始化成指向一颗红黑树的根【有了一个红黑树】;
       //rdlist成员被初始化成指向一个双向链表的根【有了双向链表】;


//创建epoll对象,创建一颗空红黑树,一个空双向链表
int epoll_create(int size) {

	if (size <= 0) return -1;

	nty_tcp_manager *tcp = nty_get_tcp_manager();
	if (!tcp) return -1;
	
	struct _nty_socket *epsocket = nty_socket_allocate(NTY_TCP_SOCK_EPOLL);
	if (epsocket == NULL) {
		nty_trace_epoll("malloc failed\n");
		return -1;
	}

	//(1)相当于new了一个eventpoll对象【开辟了一块内存】
	struct eventpoll *ep = (struct eventpoll*)calloc(1, sizeof(struct eventpoll)); //参数1:元素数量 ,参数2:每个元素大小
	if (!ep) {
		nty_free_socket(epsocket->id, 0);
		return -1;
	}

	ep->rbcnt = 0;

	//(2)让红黑树根节点指向一个空
	RB_INIT(&ep->rbr);       //等价于ep->rbr.rbh_root = NULL;

	//(3)让双向链表的根节点指向一个空
	LIST_INIT(&ep->rdlist);  //等价于ep->rdlist.lh_first = NULL;

	if (pthread_mutex_init(&ep->mtx, NULL)) {
		free(ep);
		nty_free_socket(epsocket->id, 0);
		return -2;
	}

	if (pthread_mutex_init(&ep->cdmtx, NULL)) {
		pthread_mutex_destroy(&ep->mtx);
		free(ep);
		nty_free_socket(epsocket->id, 0);
		return -2;
	}

	if (pthread_cond_init(&ep->cond, NULL)) {
		pthread_mutex_destroy(&ep->cdmtx);
		pthread_mutex_destroy(&ep->mtx);
		free(ep);
		nty_free_socket(epsocket->id, 0);
		return -2;
	}

	if (pthread_spin_init(&ep->lock, PTHREAD_PROCESS_SHARED)) {
		pthread_cond_destroy(&ep->cond);
		pthread_mutex_destroy(&ep->cdmtx);
		pthread_mutex_destroy(&ep->mtx);
		free(ep);

		nty_free_socket(epsocket->id, 0);
		return -2;
	}

	tcp->ep = (void*)ep;
	epsocket->ep = (void*)ep;

	return epsocket->id;
}

 

 

//(3.3)epoll_ctl()函数


    //格式:int epoll_ctl(int efpd,int op,int sockid,struct epoll_event *event);


    //功能:把一个socket以及这个socket相关的事件添加到这个epoll对象描述符中去,目的就是通过这个epoll对象来监视                     这 个socket【客户端的TCP连接】上数据的来往情况;
                 //当有数据来往时,系统会通知我们;  
                 //我们把感兴趣的事件通过epoll_ctl()添加到系统,当这些事件来的时候,系统会通知我们


    //efpd:epoll_create()返回的epoll对象描述符;
    //op:   动作,添加/删除/修改 ,对应数字是1,2,3, EPOLL_CTL_ADD, EPOLL_CTL_DEL ,EPOLL_CTL_MOD
         //EPOLL_CTL_ADD添加事件:等于你往红黑树上添加一个节点,每个客户端连入服务器后,服务器都会产生 一个                                                               对应的socket,每个连接这个socket值都不重复
                                                          //所以,这个socket就是红黑树中的key,把这个节点添加到红黑树上去;
         //EPOLL_CTL_MOD:修改事件;你 用了EPOLL_CTL_ADD把节点添加到红黑树上之后,才存在修改;
         //EPOLL_CTL_DEL: 是从红黑树上把这个节点干掉;这会导致这个socket【这个tcp链接】上无法收到任何系统通                                                 知事件;
    //sockid:表示客户端连接,就是你从accept();这个是红黑树里边的key;
    //event: 事件信息,这里包括的是 一些事件信息;EPOLL_CTL_ADD和EPOLL_CTL_MOD都要用到这个event参数里                       边 的事件信息;

(4.5)监听端口实战、epoll介绍及原理详析_第3张图片
    //原理:
    //a)epi = (struct epitem*)calloc(1, sizeof(struct epitem));


    //b)epi = RB_INSERT(_epoll_rb_socket, &ep->rbr, epi);    【EPOLL_CTL_ADD】 增加节点到红黑树中
         //epitem.rbn ,代表三个指针,分别指向红黑树的左子树,右子树,父亲;
         //epi = RB_REMOVE(_epoll_rb_socket, &ep->rbr, epi);【EPOLL_CTL_DEL】,从红黑树中把节点干掉
                                                                                                【EPOLL_CTL_MOD】,找到红黑树节点,修改节点内容;

    //红黑树的节点是epoll_ctl[EPOLL_CTL_ADD]往里增加的节点;面试可能考
    //红黑树的节点是epoll_ctl[EPOLL_CTL_DEL]删除的;


    //总结:
    //EPOLL_CTL_ADD: 等价于往红黑树中增加节点
    //EPOLL_CTL_DEL: 等价于从红黑树中删除节点
    //EPOLL_CTL_MOD:等价于修改已有的红黑树的节点

   

//往红黑树中加每个tcp连接以及相关的事件
int epoll_ctl(int epid, int op, int sockid, struct epoll_event *event) {

	nty_tcp_manager *tcp = nty_get_tcp_manager();
	if (!tcp) return -1;

	nty_trace_epoll(" epoll_ctl --> 1111111:%d, sockid:%d\n", epid, sockid);
	struct _nty_socket *epsocket = tcp->fdtable->sockfds[epid];
	//struct _nty_socket *socket = tcp->fdtable->sockfds[sockid];

	//nty_trace_epoll(" epoll_ctl --> 1111111:%d, sockid:%d\n", epsocket->id, sockid);
	if (epsocket->socktype == NTY_TCP_SOCK_UNUSED) {
		errno = -EBADF;
		return -1;
	}

	if (epsocket->socktype != NTY_TCP_SOCK_EPOLL) {
		errno = -EINVAL;
		return -1;
	}

	nty_trace_epoll(" epoll_ctl --> eventpoll\n");

	struct eventpoll *ep = (struct eventpoll*)epsocket->ep;
	if (!ep || (!event && op != EPOLL_CTL_DEL)) {
		errno = -EINVAL;
		return -1;
	}

	if (op == EPOLL_CTL_ADD) {
		//添加sockfd上关联的事件
		pthread_mutex_lock(&ep->mtx);

		struct epitem tmp;
		tmp.sockfd = sockid;
		struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp); //先在红黑树上找,根据key来找,也就是这个sockid,找的速度会非常快
		if (epi) {
			//原来有这个节点,不能再次插入
			nty_trace_epoll("rbtree is exist\n");
			pthread_mutex_unlock(&ep->mtx);
			return -1;
		}

		//只有红黑树上没有该节点【没有用过EPOLL_CTL_ADD的tcp连接才能走到这里】;

		//(1)生成了一个epitem对象,大家注意这个结构epitem,这个结构对象,其实就是红黑的一个节点,也就是说,红黑树的每个节点都是 一个epitem对象;
		epi = (struct epitem*)calloc(1, sizeof(struct epitem));
		if (!epi) {
			pthread_mutex_unlock(&ep->mtx);
			errno = -ENOMEM;
			return -1;
		}
		
		//(2)把socket(TCP连接)保存到节点中;
		epi->sockfd = sockid;  //作为红黑树节点的key,保存在红黑树中

		//(3)我们要增加的事件也保存到节点中;
		memcpy(&epi->event, event, sizeof(struct epoll_event));

		//(4)把这个节点插入到红黑树中去
		epi = RB_INSERT(_epoll_rb_socket, &ep->rbr, epi); //实际上这个时候epi的rbn成员就会发挥作用,如果这个红黑树中有多个节点,那么RB_INSERT就会epi->rbi相应的值:可以参考图来理解
		assert(epi == NULL);
		ep->rbcnt ++;
		
		pthread_mutex_unlock(&ep->mtx);

	} else if (op == EPOLL_CTL_DEL) {
		//把红黑树节点从红黑树上删除
		pthread_mutex_lock(&ep->mtx);

		struct epitem tmp;
		tmp.sockfd = sockid;
		
		struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);//先在红黑树上找,根据key来找,也就是这个sockid,找的速度会非常快
		if (!epi) {
			nty_trace_epoll("rbtree no exist\n");
			pthread_mutex_unlock(&ep->mtx);
			return -1;
		}
		
		//只有在红黑树上找到该节点【用过EPOLL_CTL_ADD的tcp连接才能走到这里】;

		//(1)从红黑树上把这个节点干掉
		epi = RB_REMOVE(_epoll_rb_socket, &ep->rbr, epi);
		if (!epi) {
			nty_trace_epoll("rbtree is no exist\n");
			pthread_mutex_unlock(&ep->mtx);
			return -1;
		}

		ep->rbcnt --;
		free(epi);
		
		pthread_mutex_unlock(&ep->mtx);

	} else if (op == EPOLL_CTL_MOD) {
		//修改红黑树某个节点的内容
		struct epitem tmp;
		tmp.sockfd = sockid;
		struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp); //先在红黑树上找,根据key来找,也就是这个sockid,找的速度会非常快
		if (epi) {
			//(1)红黑树上有该节点,则修改对应的事件
			epi->event.events = event->events;
			epi->event.events |= EPOLLERR | EPOLLHUP;
		} else {
			errno = -ENOENT;
			return -1;
		}

	} else {
		nty_trace_epoll("op is no exist\n");
		assert(0);
	}

	return 0;
}

 

 //当事件发生,我们如何拿到操作系统的通知;

//(3.4)epoll_wait()函数


    //格式:int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);


    //功能:阻塞一小段时间并等待事件发生,返回事件集合,也就是获取内核的事件通知;
                  //说白了就是遍历这个双向链表,把这个双向链表里边的节点数据拷贝出去,拷贝完毕的就从双向链表里移除;
                  //因为双向链表里记录的是所有有数据/有事件的socket【TCP连接】;


    //参数epfd:是epoll_create()返回的epoll对象描述符;
    //参数events:是内存,也是数组,长度 是maxevents,表示此次epoll_wait调用可以收集到的maxevents个已经继续                                【已经准备好的】的读写事件;
                            //说白了,就是返回的是 实际 发生事件的tcp连接数目;
    //参数timeout:阻塞等待的时长;


 //epitem结构设计的高明之处:既能够作为红黑树中的节点,又能够作为双向链表中的节点;

(4.5)监听端口实战、epoll介绍及原理详析_第4张图片

(4.5)监听端口实战、epoll介绍及原理详析_第5张图片 

//到双向链表中去取相关的事件通知
int epoll_wait(int epid, struct epoll_event *events, int maxevents, int timeout) {

	nty_tcp_manager *tcp = nty_get_tcp_manager();
	if (!tcp) return -1;

	//nty_socket_map *epsocket = &tcp->smap[epid];
	struct _nty_socket *epsocket = tcp->fdtable->sockfds[epid];
	if (epsocket == NULL) return -1;

	if (epsocket->socktype == NTY_TCP_SOCK_UNUSED) {
		errno = -EBADF;
		return -1;
	}

	if (epsocket->socktype != NTY_TCP_SOCK_EPOLL) {
		errno = -EINVAL;
		return -1;
	}

	struct eventpoll *ep = (struct eventpoll*)epsocket->ep;
	if (!ep || !events || maxevents <= 0) {
		errno = -EINVAL;
		return -1;
	}

	if (pthread_mutex_lock(&ep->cdmtx)) {
		if (errno == EDEADLK) {
			nty_trace_epoll("epoll lock blocked\n");
		}
		assert(0);
	}

	//(1)这个while用来等待一定的时间【在这段时间内,发生事件的TCP连接,相关的节点,会被操作系统扔到双向链表去【当然这个节点同时也在红黑树中呢】】
	while (ep->rdnum == 0 && timeout != 0) {

		ep->waiting = 1;
		if (timeout > 0) {

			struct timespec deadline;

			clock_gettime(CLOCK_REALTIME, &deadline);
			if (timeout >= 1000) {
				int sec;
				sec = timeout / 1000;
				deadline.tv_sec += sec;
				timeout -= sec * 1000;
			}

			deadline.tv_nsec += timeout * 1000000;

			if (deadline.tv_nsec >= 1000000000) {
				deadline.tv_sec++;
				deadline.tv_nsec -= 1000000000;
			}

			int ret = pthread_cond_timedwait(&ep->cond, &ep->cdmtx, &deadline);
			if (ret && ret != ETIMEDOUT) {
				nty_trace_epoll("pthread_cond_timewait\n");
				
				pthread_mutex_unlock(&ep->cdmtx);
				
				return -1;
			}
			timeout = 0;
		} else if (timeout < 0) {

			int ret = pthread_cond_wait(&ep->cond, &ep->cdmtx);
			if (ret) {
				nty_trace_epoll("pthread_cond_wait\n");
				pthread_mutex_unlock(&ep->cdmtx);

				return -1;
			}
		}
		ep->waiting = 0; 

	}

	pthread_mutex_unlock(&ep->cdmtx);

	//等一小段时间,等时间到达后,流程来到这里。。。。。。。。。。。。。。

	pthread_spin_lock(&ep->lock);

	int cnt = 0;

	//(1)取得事件的数量
	//ep->rdnum:代表双向链表里边的节点数量(也就是有多少个TCP连接来事件了)
	//maxevents:此次调用最多可以收集到maxevents个已经就绪【已经准备好】的读写事件
	int num = (ep->rdnum > maxevents ? maxevents : ep->rdnum); //哪个数量少,就取得少的数字作为要取的事件数量
	int i = 0;
	
	while (num != 0 && !LIST_EMPTY(&ep->rdlist)) { //EPOLLET

		//(2)每次都从双向链表头取得 一个一个的节点
		struct epitem *epi = LIST_FIRST(&ep->rdlist);

		//(3)把这个节点从双向链表中删除【但这并不影响这个节点依旧在红黑树中】
		LIST_REMOVE(epi, rdlink); 

		//(4)这是个标记,标记这个节点【这个节点本身是已经在红黑树中】已经不在双向链表中;
		epi->rdy = 0;  //当这个节点被操作系统 加入到 双向链表中时,这个标记会设置为1。

		//(5)把事件标记信息拷贝出来;拷贝到提供的events参数中
		memcpy(&events[i++], &epi->event, sizeof(struct epoll_event));
		
		num --;
		cnt ++;       //拷贝 出来的 双向链表 中节点数目累加
		ep->rdnum --; //双向链表里边的节点数量减1
	}
	
	pthread_spin_unlock(&ep->lock);

	//(5)返回 实际 发生事件的 tcp连接的数目;
	return cnt; 
}

 

 

//(3.5)内核向双向链表增加节点


//一般有四种情况,会使操作系统把节点插入到双向链表中;
    //a)客户端完成三路握手;服务器要accept();
    //b)当客户端关闭连接,服务器也要调用close()关闭;
    //c)客户端发送数据来的;服务器要调用read(),recv()函数来收数据;
    //d)当可以发送数据时;服务武器可以调用send(),write();
    //e)其他情况;写实战代码再说;

//当发生客户端三路握手连入、可读、可写、客户端断开等情况时,操作系统会调用这个函数,用以往双向链表中增加一个节点【该节点同时 也在红黑树中】
int epoll_event_callback(struct eventpoll *ep, int sockid, uint32_t event) {

	struct epitem tmp;
	tmp.sockfd = sockid;

	//(1)根据给定的key【这个TCP连接的socket】从红黑树中找到这个节点
	struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);
	if (!epi) {
		nty_trace_epoll("rbtree not exist\n");
		assert(0);
	}

	//(2)从红黑树中找到这个节点后,判断这个节点是否已经被连入到双向链表里【判断的是rdy标志】
	if (epi->rdy) {
		//这个节点已经在双向链表里,那无非是把新发生的事件标志增加到现有的事件标志中
		epi->event.events |= event;
		return 1;
	} 

	//走到这里,表示 双向链表中并没有这个节点,那要做的就是把这个节点连入到双向链表中

	nty_trace_epoll("epoll_event_callback --> %d\n", epi->sockfd);
	
	pthread_spin_lock(&ep->lock);

	//(3)标记这个节点已经被放入双向链表中,我们刚才研究epoll_wait()的时候,从双向链表中把这个节点取走的时候,这个标志被设置回了0
	epi->rdy = 1;  

	//(4)把这个节点链入到双向链表的表头位置
	LIST_INSERT_HEAD(&ep->rdlist, epi, rdlink);

	//(5)双向链表中的节点数量加1,刚才研究epoll_wait()的时候,从双向链表中把这个节点取走的时候,这个数量减了1
	ep->rdnum ++;
	pthread_spin_unlock(&ep->lock);

	pthread_mutex_lock(&ep->cdmtx);

	pthread_cond_signal(&ep->cond);
	pthread_mutex_unlock(&ep->cdmtx);
	return 0;
}

 

 

你可能感兴趣的:(服务器框架设计学习)