zoukankan      html  css  js  c++  java
  • epoll的LT和ET模式

    原理參考该博客

    从man手冊中,得到ET和LT的详细描写叙述例如以下


    EPOLL事件有两种模型:
    Edge Triggered (ET)
    Level Triggered (LT)

    假如有这样一个样例:
    1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)加入到epoll描写叙述符
    2. 这个时候从管道的还有一端被写入了2KB的数据
    3. 调用epoll_wait(2),而且它会返回RFD,说明它已经准备好读取操作
    4. 然后我们读取了1KB的数据
    5. 调用epoll_wait(2)......

    Edge Triggered 工作模式:
    假设我们在第1步将RFD加入到epoll描写叙述符的时候使用了EPOLLET标志。那么在第5步调用epoll_wait(2)之后将有可能会挂起,由于剩余的数据还存在于文件的输入缓冲区内,并且数据发出端还在等待一个针对已经发出数据的反馈信息。

    仅仅有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候。调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的样例中。会有一个事件产生在RFD句柄上,由于在第2步运行了一个写操作,然后,事件将会在第3步被销毁。由于第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait(2)完毕后,是否挂起是不确定的。

    epoll工作在ET模式的时候。必须使用非堵塞套接口,以避免因为一个文件句柄的堵塞读/堵塞写操作把处理多个文件描写叙述符的任务饿死。

    最好以以下的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。
       i    基于非堵塞文件句柄
       ii   仅仅有当read(2)或者write(2)返回EAGAIN时才须要挂起,等待。但这并非说每次read()时都须要循环读,直到读到产生一个EAGAIN才觉得此次事件处理完毕,当read()返回的读到的数据长度小于请求的数据长度时,就能够确定此时缓冲中已没有数据了。也就能够觉得此事读事件已处理完毕。

    Level Triggered 工作模式
    相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比較快的poll(2)。而且不管后面的数据是否被使用。因此他们具有相同的职能。由于即使使用ET模式的epoll。在收到多个chunk的数据的时候仍然会产生多个事件。调用者能够设定EPOLLONESHOT标志。在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描写叙述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。


    然后详解ET, LT:

    LT(level triggered)是缺省的工作方式,而且同一时候支持block和no-block socket.在这样的做法中,内核告诉你一个文件描写叙述符是否就绪了,然后你能够对这个就绪的fd进行IO操作。假设你不作不论什么操作,内核还是会继续通知你的,所以,这样的模式编程出错误可能性要小一点。传统的select/poll都是这样的模型的代表.

    ET(edge-triggered)是快速工作方式,仅仅支持no-block socket。在这样的模式下,当描写叙述符从未就绪变为就绪时。内核通过epoll告诉你。然后它会如果你知道文件描写叙述符已经就绪,而且不会再为那个文件描写叙述符发送很多其它的就绪通知,直到你做了某些操作导致那个文件描写叙述符不再为就绪状态了(比方,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。可是请注意,假设一直不正确这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送很多其它的通知(only once),只是在TCP协议中,ET模式的加速效用仍须要很多其它的benchmark确认(这句话不理解)。

    在很多測试中我们会看到假设没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高非常多。可是当我们遇到大量的idle- connection(比如WAN环境中存在大量的慢速连接),就会发现epoll的效率大大高于select/poll。

    (未測试)



    另外,当使用epoll的ET模型来工作时。当产生了一个EPOLLIN事件后。
    读数据的时候须要考虑的是当recv()返回的大小假设等于请求的大小。那么非常有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还须要再次读取
    while(rs)
    {
      buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
      if(buflen < 0)
      {
        // 因为是非堵塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读
        // 在这里就当作是该次事件已处理处.
        if(errno == EAGAIN)
         break;
        else
         return;
       }
       else if(buflen == 0)
       {
         // 这里表示对端的socket已正常关闭.
       }
       if(buflen == sizeof(buf)
         rs = 1;   // 须要再次读取
       else
         rs = 0;
    }


    还有。假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),因为是非堵塞的socket,那么send()函数尽管返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(參考man send),同一时候,不理会这次请求发送的数据.所以,须要封装socket_send()的函数用来处理这样的情况,该函数会尽量将数据写完再返回,返回-1表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试.这样的方式并不非常完美,在理论上可能会长时间的堵塞在socket_send()内部,但暂没有更好的办法.

    ssize_t socket_send(int sockfd, const char* buffer, size_t buflen)
    {
      ssize_t tmp;
      size_t total = buflen;
      const char *p = buffer;

      while(1)
      {
        tmp = send(sockfd, p, total, 0);
        if(tmp < 0)
        {
          // 当send收到信号时,能够继续写,但这里返回-1.
          if(errno == EINTR)
            return -1;

          // 当socket是非堵塞时,如返回此错误,表示写缓冲队列已满,
          // 在这里做延时后再重试.
          if(errno == EAGAIN)
          {
            usleep(1000);
            continue;
          }

          return -1;
        }

        if((size_t)tmp == total)
          return buflen;

        total -= tmp;
        p += tmp;
      }

      return tmp;

    }


    详细请看代码:

    #include <iostream>
    #include <fcntl.h>
    #include <assert.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <sys/epoll.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <netinet/in.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    #include <errno.h>
    using namespace std;
    
    void setnonblocking(int fd)//设置fd为非堵塞
    {
    	int flag = fcntl(fd,F_GETFL);
    	if(flag < 0)printf("fcntl error
    ");
    	flag = fcntl(fd,F_SETFL,flag|O_NONBLOCK);
    	if(flag < 0)printf("fcntl error
    ");
    }
    void addfd(int epollfd,int fd,bool UES_ET)//加入fd到epoll中。UES_ET表示是否使用ET模式
    {
    	struct epoll_event event;
    	event.data.fd = fd;
    	event.events = EPOLLIN;
    	if(UES_ET)event.events |= EPOLLET;
    	epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
    	setnonblocking(fd);
    }
    void lt(epoll_event* events,int num,int epollfd,int listenfd)//水平模式
    {
    	int i;
    	for(i = 0;i < num;i++)
    	{
    		int fd = events[i].data.fd;
    		if(fd == listenfd)//新连接到达
    		{
    			struct sockaddr_in client;
    			socklen_t client_len = sizeof(client);
    			int connfd = accept(listenfd,(struct sockaddr*)&client,&client_len);
    			assert(connfd != -1);
    			addfd(epollfd,connfd,false);
    		}
    		else
    		{
    			int buffer_len = 10;
    			char buffer[buffer_len];
    			if(events[i].events & EPOLLIN)
    			{
    				int count = recv(fd,buffer,buffer_len-1,0);
    				if(count < 0)
    				{
    					printf("recv error
    ");
    					close(fd);
    				}
    				else if(count == 0)
    				{
    					printf("client close
    ");
    					close(fd);
    				}
    				else
    				{
    					buffer[count] = '';
    					printf("receive %d byte char ,there are :%s
    ",count,buffer);
    				}
    			}
    		}
    	}
    }
    void et(epoll_event* events,int num,int epollfd,int listenfd)
    {
    	int i;
    	for(i = 0;i < num;i++)
    	{
    		int fd = events[i].data.fd;
    		if(fd == listenfd)
    		{
    			int connfd = accept(listenfd,NULL,NULL);
    			assert( connfd > 0);
    			addfd(epollfd,connfd,true);//开启ET模式
    		}
    		else
    		{
    			if(events[i].events & EPOLLIN)
    			{
    				int buffer_len = 10;
    				char buffer[buffer_len];
    				while(true)//读全然部数据
    				{
    					int count = recv(fd,buffer,buffer_len-1,0);
    
    					if(count < 0)
    					{
    						if(errno == EAGAIN || errno == EWOULDBLOCK)
    							printf("there is no left data
    ");
    						else
    						{
    							printf("receive error
    ");
    							close(fd);
    						}
    						break;
    					}
    					else if(count == 0)
    					{
    						close(fd);
    						printf("client close
    ");
    						break;
    					}
    					else
    					{
    						buffer[count] = '';
    						printf("receive %d byte char ,there are :%s
    ",count,buffer);
    					}
    				}
    			}
    		}
    	}
    }
    int main(int argc,char* argv[])
    {
    	if(argc != 3)printf("usage %s ip_address port
    ",basename(argv[0]));
    	struct sockaddr_in server;
    	bzero(&server,sizeof(server));
    	server.sin_family = AF_INET;
    	server.sin_port = htons(atoi(argv[2]));
    	inet_pton(AF_INET,argv[1],&server.sin_addr);
    	int listenfd = socket(AF_INET,SOCK_STREAM,0);
    	assert(listenfd >= 0);
    	int opt = 1;
    	int res = setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//设置地址重用
    	assert(res == 0);
    	socklen_t addrlen = sizeof(server);
    	res = bind(listenfd,(struct sockaddr *)&server,addrlen);
    	assert(res != -1);
    	res = listen(listenfd,100);
    	assert(res != -1);
    	int epollfd = epoll_create(100);
    	addfd(epollfd,listenfd,true);//连接仅仅会到达一次,所以用ET模式
    	int MAX_EVENTS = 10;
    	epoll_event events[MAX_EVENTS];
    	while(true)
    	{
    		int num = epoll_wait(epollfd,events,MAX_EVENTS,-1);
    		if(num < 0)break;
    		//lt(events,num,epollfd,listenfd);
    		et(events,num,epollfd,listenfd);
    	}
    }
    客户端能够使用telnet模拟。telnet ip port 登陆后,输入数据就可以

  • 相关阅读:
    Python使用SMTP模块、email模块发送邮件
    harbor搭建及使用
    ELK搭建-windows
    ELK技术栈之-Logstash详解
    【leetcode】1078. Occurrences After Bigram
    【leetcode】1073. Adding Two Negabinary Numbers
    【leetcode】1071. Greatest Common Divisor of Strings
    【leetcode】449. Serialize and Deserialize BST
    【leetcode】1039. Minimum Score Triangulation of Polygon
    【leetcode】486. Predict the Winner
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5197579.html
Copyright © 2011-2022 走看看