高并发服务器(二)-- epoll模型

目录

20.1 epoll模型简介

20.2 epoll API

20.3 epoll 服务器示例



20.1 epoll模型简介

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

怎么说呢?emmm
epoll模型,就像服务端是一个boss,它有一个秘书,而那些请求执行命令的描述符则为boss的手下。
用select时,就是轮询手下看看哪个有交工了,这样耗时耗力,而且手下还不能多,多了顾不过来了。
于是,我们使用epoll模型,哪个手下要交工,跟秘书说一声,秘书再定时汇报给boss,这样boss就不用一直去轮询了,省时省力还能管更多的手下。
这就是epoll相较于select的优势。

查看一个进程打开最大数目的socket描述符数量:

cat /proc/sys/fs/file-max

设置最大打开文件描述符限制

sudo vi /etc/security/limits.conf
//写入以下配置(soft 软限制,hard 硬限制)
*		soft	nofile		66666
*		hard	nofile		100000
20.2 epoll API

#include<sys/epoll.h>

(1)int epoll_create( int size);
创建一个epoll句柄,参数size用于告诉内核监听的文件描述符个数,跟内存大小有关
返回epoll 文件描述符

(2)int epoll_ctl( int epfd, int op, int fd, struct epoll_event *event ); // 成功,返0,失败返-1
控制某个epoll监控的文件描述符上的事件:注册,修改,删除

参数释义:
epfd:为epoll的句柄
op:表示动作,用3个宏来表示
··· EPOLL_CTL_ADD(注册新的 fd 到epfd)
··· EPOLL_CTL_DEL(从 epfd 中删除一个 fd)
··· EPOLL_CTL_MOD(修改已经注册的 fd 监听事件)

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 event */
    epoll_data_t data;  /* User data variable */
};
/* epoll_event.events:
  EPOLLIN  表示对应的文件描述符可以读
  EPOLLOUT 表示对应的文件描述符可以写
  EPOLLPRI 表示对应的文件描述符有紧急的数据可读
  EPOLLERR 表示对应的文件描述符发生错误
  EPOLLHUP 表示对应的文件描述符被挂断
  EPOLLET  表示对应的文件描述符有事件发生
*/

//具体咋用看后面代码示例

(3)int epoll_wait( int epfd, struct epoll_event *events, int maxevents, int timeout);
等待所监控文件描述符上有事件的产生

参数释义:
events:用来从雷和得到事件的集合
maxevent:用于告诉内核这个event有多大,这个maxevent不能大于创建句柄时的size
timeout:超时时间
··· -1:阻塞
··· 0:立即返回
···>0:指定微秒

成功返回有多少个文件描述符准备就绪,时间到返回0,出错返回-1.

20.3 epoll模型示例
/*server.c*/

#include<stdio.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<errno.h>

#define MAXLINE 80
#define SERV_PORT 8000
#include OPEN_MAX 1024

int main(void)
{
	struct sockaddr_in servaddr,cliaddr;
	socklen_t cliaddr_len;
	int i,j,maxi,listenfd,connfd,sockfd;
	int nready,efd,res,client[OPEN_MAX];
	ssize_t n;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	struct epoll_event tep,ep[OPEN_MAX];
	
	listenfd = socket(AF_INET,SOCK_STREAM,0);
	
	bzero(&servaddr,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htonl(SERV_PORT);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
	listen(listenfd,20);

	maxfd = listenfd;  //初始化
	maxi = -1; //client[]的下标
	for(i = 0 ; i < OPEN_MAX ; i++ )
		client[i] = -1; //用-1初始化client

//套路开始
	efd = epoll_create(OPEN_MAX);  //创建句柄
	if(efd == -1)
		perrno("epoll_create");
	
	tep.events = EPOLLIN;  //设置读事件
	tep.data.fd = listenfd;  //套接socket文件描述符
	res = epoll_ctl(efd,EPOLL_CTL_ADD,listened,&tep);  //将listenfd加入监听文件表,监听listenfd的读取内容
	if(res == -1)
		perrno("epoll_ctl");
	
	while(1)
	{
		nready = epoll_wait(efd,ep,OPEN_MAX,-1);  //阻塞监听
		if(nready == -1)
			perrno("epoll_wait error:");
		
		for(i == 0; i<nready; i++)
		{
			if(!ep[i].event & EPOLLIN)
				continue;
			if(ep[i].data.fd == listenfd)
			{
				//开始接收数据了
				printf("Accepting connections···  \n");  //写完一定要来检查一下这个换行,一不小心就忘记了
				cliaddr_len = sizeof(cliaddr);  //这得实时更新
				connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);  //接收连接

				printf("Read from %s at port %d \n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
				/*将客户端的地址读取到str里面然后打印*/  /*将端口号转换成整形数输出*/

				for(j = 0;j < OPEN_MAX; j++)
				{
					if(client[j] < 0)
						client[j] = connfd;  //保存accept返回的文件描述符到client【】里
						//break;
	
					if( j == OPEN_MAX )
					{
						printf("Too many clients \n",stderr);
						exit(-1);
					}
		
					if(j > maxi)
						maxi = j;
	
					tep.events = EPOLLIN;
					tep.data.fd = connfd;
					res = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&tep);  //将connfd加入监听文件表,监听connfd的读取内容
					if(res == -1)
						perrno("epoll_ctl");
					else
					{
						sockfd = ep[i].data.fd;
						n = read(sockfd,buf,MAXLINE);
						if(n == 0)
						{
							for(j = 0 ; j <= maxi ; j++)
							{
								if(client[i] == sockfd)
								{
									client[j] = -1;
									break;
								}
							}
							res = epoll_ctl(efd,EPOLL_CTL_ADD,sockfd,&tep);  //将sockfd加入监听文件表,监听sockfd的读取内容
							if(res == -1)
								perrno("epoll_ctl")
							close(sockfd);
							printf("Client[%d] closed connetion \n",j);
						}
						else
						{
							for(j = 0; j<n; j++)
								buf[j] = toupper(buf[j]);
							write(sockfd,buf,n);
						}
					}
			}	
	}
	close(listenfd);
	close(efd)
	return 0;;		
}





发布了61 篇原创文章 · 获赞 3 · 访问量 1621

猜你喜欢

转载自blog.csdn.net/qq_43762191/article/details/104005521