多路I/O转接服务器——epoll

什么是epoll?

epoll接口是为解决Linux内核处理大量文件描述符而提出的方案。该接口属于Linux下多路I/O复用接口中select/poll的增强。其经常应用于Linux下高并发服务型程序,特别是在大量并发连接中只有少部分连接处于活跃下的情况 (通常是这种情况),在该情况下能显著的提高程序的CPU利用率。

目前epell是linux大规模并发网络程序中的热门首选模型。
epoll除了提供 select/ poll那种 IO事件的电平触发(Level Triggered)外,还提
供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存 IO状态,减少
epoll_wait/epoll_pwait的调用,提高应用程序效率。

EPOLL 事件有两种模型:
Edge Triggered (ET) 边缘触发 只有新数据到来,才触发,不管缓存区中是否还有数据。
Level Triggered (LT) 水平触发 只要有数据都会触发,不管数据是哪里的。

epoll API

头文件

#include<sys/epoll.h>

创建句柄

int epoll_create(int size)
size:告诉内核监听的数目

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

epoll控制函数

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event );
 // 成功,返0,失败返-1

epfd:为epoll_creat的句柄
op:表示动作,用3个宏来表示:
···EPOLL_CTL_ADD(注册新的fd到epfd),
···EPOLL_CTL_MOD(修改已经注册的fd的监听事件),
···EPOLL_CTL_DEL(从epfd删除一个fd);
event:告诉内核需要监听的事件

struct epoll_event
{
    
    
    __uint32_t events;  /* epoll event */
    epoll_data_t data;  /* User data variable */
};

typedef union epoll_data
{
    
    
    void* ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;  /* 保存触发事件的某个文件描述符相关的数据 */
 
/* epoll_event.events:
  EPOLLIN  表示对应的文件描述符可以读
  EPOLLOUT 表示对应的文件描述符可以写
  EPOLLPRI 表示对应的文件描述符有紧急的数据可读
  EPOLLERR 表示对应的文件描述符发生错误
  EPOLLHUP 表示对应的文件描述符被挂断
  EPOLLET  表示对应的文件描述符有事件发生
*/

epoll消息读取

//等待所监控文件描述符上有事件的产生,类似于select()调用。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

events:用来从内核得到事件的集合,
maxevents:告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
timeout:是超时时间
-1:阻塞
0:立即返回,非阻塞
>0:指定微秒
返回值:成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1

代码示例

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

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

int main()
{
    
    
	struct sockaddr_in servaddr,cliaddr;
    socklen_t cliaddr_len;
    int i,j,maxi,maxfd,listenfd,connfd,sockfd;
    int nready,efd,res,client[OPEN_MAX];
    ssize_t n;
    char buf[MAXLINE];
    char sendbuf[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 = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    listen(listenfd,20); //把先完成三次握手的客户端加入到队列之中

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

    efd = epoll_create(OPEN_MAX); //创建句柄

    tep.events = EPOLLIN;  //设置读事件
    tep.data.fd = listenfd; //套接socket文件描述符
    res = epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&tep);//将listenfd加入监听文件表,监听listenfd的读取内容
	if(res == -1)
		perror("epoll_ctl");

    while(1)
    {
    
    
        nready = epoll_wait(efd,ep,OPEN_MAX,-1);//阻塞监听
        if(nready == -1)
			perror("epoll_wait error:");

        for(i=0;i<nready; i++)
        {
    
    
            if(!ep[i].events & EPOLLIN) //判断是否EPOLLIN
                continue;
            if(ep[i].data.fd == listenfd)
            {
    
    
            //开始接收数据
                printf("Accepting connections num:%d \n",i+1); 
                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));
				//将客户端的地址、端口号转换成整形数输出
                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)
					perror("epoll_ctl");
			}
			else
			{
    
    
				sockfd = ep[i].data.fd;
				n = read(sockfd,buf,MAXLINE);
				printf("client data:\n");
				printf(" %s\n",buf);
				
				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)
						perror("epoll_ctl");
					close(sockfd);
					printf("Client[%d] closed connetion \n",j);
				}
				else
				{
    
    
					for(j = 0; j<n; j++)
						buf[j] = toupper(buf[j]);
					printf("server write data:\n");
					printf(" %s\n",buf);
					write(sockfd,buf,n);
					memset(buf,0,sizeof(buf));
			}
        }
    }
	close(listenfd);
	close(efd);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_46485161/article/details/115166579