• IO多路复用模型之epoll实现机制


    设想一下如下场景:有100万个客户端同时与一个服务器进程保持着TCP连接。而每一时刻,通常只有几百上千个TCP连接是活跃的(事实上大部分场景都是这种情况)。如何实现这样的高并发?

    在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,因此,select/poll一般只能处理几千的并发连接。

    epoll的设计和实现与select完全不同。epoll通过在Linux内核中申请一个简易的文件系统(文件系统一般用什么数据结构实现?B+树)。把原先的select/poll调用分成了3个部分:

    1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)

    2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字

    3)调用epoll_wait收集发生的事件的连接

    如此一来,要实现上面说是的场景,只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数据,内核也不需要去遍历全部的连接。

    下面来看看Linux内核具体的epoll机制实现思路。

    当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关。eventpoll结构体如下所示:

    struct eventpoll{
        ....
        /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
        struct rb_root  rbr;
        /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
        struct list_head rdlist;
        ....
    };
    

      

    每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。

    而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

    在epoll中,对于每一个事件,都会建立一个epitem结构体,如下所示:

    struct epitem{
        struct rb_node  rbn;//红黑树节点
        struct list_head    rdllink;//双向链表节点
        struct epoll_filefd  ffd;  //事件句柄信息
        struct eventpoll *ep;    //指向其所属的eventpoll对象
        struct epoll_event event; //期待发生的事件类型
    }
    

      当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。

     

    从上面的讲解可知:通过红黑树和双链表数据结构,并结合回调机制,造就了epoll的高效。

    OK,讲解完了Epoll的机理,我们便能很容易掌握epoll的用法了。一句话描述就是:三步曲。

    第一步:epoll_create()系统调用。此调用返回一个句柄,之后所有的使用都依靠这个句柄来标识。

    第二步:epoll_ctl()系统调用。通过此调用向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。

    第三部:epoll_wait()系统调用。通过此调用收集收集在epoll监控中已经发生的事件。

    最后,附上一个epoll编程实例。

    服务器端

    /*************************************************************************
      File Name: tcp_s.c
    Author: AlexCthon
    Mail: AlexCthon@163.com 
    Created Time: Tue 15 May 2018 10:25:10 PM CST
     ************************************************************************/
    
    //边沿触发
    #include"fun.h"
    void setnoblock(int fd)
    {
        int status;
        status=fcntl(fd,F_GETFL);
        status = status|O_NONBLOCK;
        fcntl(fd,F_SETFL,status);
    }
    int main(int argc,char**argv)
    {
        if(argc!=3)
        {
    	printf("./server IPPROT
    ");
    	return -1;
        }
    
        int sfd;
        sfd = socket(AF_INET,SOCK_STREAM,0);
        if(sfd==-1)
        {
    	perror("socket");
    
    	return -1;
        }
    
        struct sockaddr_in ser;
        bzero(&ser,sizeof(ser));
        ser.sin_family = AF_INET;
        ser.sin_port = htons(atoi(argv[2]));//将端口号转换为网络字节序
        ser.sin_addr.s_addr = inet_addr(argv[1]);//将点分十进制转换为网络字节序
    
        int ret;
        ret = bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
        if(-1==ret)
        {
    	perror("bind");
    	close(sfd);
    	return -1;
        }
    
        if(-1==listen(sfd,10))
        {
    	perror("listen");
    	close(sfd);
    	return -1;
        }
        struct sockaddr_in client;
        bzero(&client,sizeof(client));
    
        int newfd;
        int len; 
    
        char buf[10]={0};
        
        int epfd=epoll_create(1);
        struct epoll_event event,evs[3];
        event.events=EPOLLIN;
        event.data.fd=0;
        ret = epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event);
        if(-1==ret)
        {
    	perror("epoll_ctl");
    	return -1;
        }
        event.events=EPOLLIN;
        event.data.fd=sfd;
        epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);
        int ret1;
        int i=0;
        printf("before select
    ");
        while(1)
        {
    	ret1 = epoll_wait(epfd,evs,3,-1);
    
    	for(i=0;i<ret1;i++)
    	{
    	    if(evs[i].data.fd==sfd)//sfd可读,代表有客户端连接
    	    {
    		len = sizeof(struct sockaddr);
    		newfd = accept(sfd,(struct sockaddr*)&client , &len);
    		if(newfd==-1)
    		{
    		    perror("accept");
    		    return -1;
    		}
    		setnoblock(newfd);	
    		event.events = EPOLLIN;
    		event.data.fd = newfd;
    		epoll_ctl(epfd,EPOLL_CTL_ADD,newfd,&event);
    		//  printf("%d
    ",ret);
    		printf("client ip = %s,port = %d
    ",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
    	    }
    	    if(evs[i].data.fd==newfd)
    	    {
    		bzero(buf,sizeof(buf));
    		ret = recv(newfd,buf,sizeof(buf)-1,0);
    		if(ret==0)
    		{
    		    printf("bye!
    ");
    		    event.events = EPOLLIN;
    		    event.data.fd = newfd;
    		    epoll_ctl(epfd,EPOLL_CTL_DEL,newfd,&event);
    		    close(newfd);
    		}
    
    		printf("%s
    ",buf);
    		//  printf("%d
    ",ret);
    	    }
    
    	    if(evs[i].data.fd==0)
    	    {
    		bzero(buf,sizeof(buf));
    		ret=read(STDIN_FILENO,buf,sizeof(buf)-1);
    		if(ret==0)
    		{
    	 	    printf("bye!
    ");
    		    event.events = EPOLLIN|EPOLLET;
    		    event.data.fd = newfd;
    
    		    epoll_ctl(epfd,EPOLL_CTL_DEL,newfd,&event);
    		    close(newfd);
    		   
    		}
    		send(newfd,buf,strlen(buf)-1,0);//不把
    传过去
    	    }
    	}
        }
    
        close(sfd);
    
        printf("祝您生活愉快");
        return 0;
    }
    

      

  • 相关阅读:
    Volley学习(RequestQueue分析)
    Volley学习(Volly分析)
    AsyncTask学习
    如何排放表单标签介绍的位置?
    了解hr标签的各种浏览器兼容问题
    多屏移动端网站开发需要注意的十大技巧
    让吃货垂涎三尺的美食酷站!—— 每周酷站欣赏 #1
    12个响应式导航菜单jQuery插件,让您轻松应对响应式网站!
    网页设计师必看!27个响应式的简约风格网站
    网页设计师必看!12个漂亮颜色搭配的国外网站欣赏
  • 原文地址:https://www.cnblogs.com/cthon/p/9052175.html
Copyright © 2020-2023  润新知