linux编程基础|网络编程之epoll边沿模式的非阻塞方法

本文供自己复习使用,更加详细的信息可以参见:
大丙哥文章:IO多路转接(复用)之epoll
大丙哥视频:Linux编程 - 网络篇【IO多路转接 - 提升】

epoll边沿触发模式ET

边沿模式可以简称为ET模式,ET(edge-triggered)是高速工作方式只支持no-block socket。在这种模式下,当文件描述符从未就绪变为就绪时,内核会通过epoll通知使用者。然后它会假设使用者知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知(only once)如果我们对这个文件描述符做IO操作,从而导致它再次变成未就绪,当这个未就绪的文件描述符再次变成就绪状态,内核会再次进行通知,并且还是只通知一次。ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高

边沿模式的特点:

  • 这里重点介绍读事件:当读缓冲区有新的数据进入,读事件被触发一次,没有新数据不会触发该事件
    • 如果有新数据进入到读缓冲区,读事件被触发,epoll_wait()解除阻塞
    • 读事件被触发,可以通过调用read()/recv()函数将缓冲区数据读出
      • 如果数据没有被全部读走,并且没有新数据进入,读事件不会再次触发,只通知一次
      • 如果数据被全部读走或者只读走一部分,此时有新数据进入,读事件被触发,并且只通知一次

但是边沿触发模式一定要进行非阻塞处理,比如我们每次在与服务器建立连接后,如果服务器代码的buf不够大,如下代码所示:

while(1){
	int num = epoll_wait(epfd, evs, size, -1);
  for(int i=0; i<num; ++i)
  {
      int curfd = evs[i].data.fd;
      if(curfd == lfd){
          int cfd = accept(curfd, NULL, NULL);
          struct epoll_event ev;
          ev.events = EPOLLIN | EPOLLET;   
          ev.data.fd = cfd;
          ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
          if(ret == -1){
              perror("epoll_ctl-accept");
              exit(0);
          }
      }else{
        char buf[5];
        int len = recv(cfd, buf, sizeof(buf), 0);
        if(len == 0) {
          printf("断开链接\n");
          epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
          close(curfd);
        }
        else if(len > 0){
          printf("客户端say: %s\n", buf);
          send(curfd, buf, len, 0);
         }else{
             perror("recv");
             exit(0);
         	}
      	}	
  	}
  }

如果我们在客户端输入“hello world oh my god !”

服务端这里只能读出“hello”

会导致服务器无法把读缓冲区的数据全部读干净,只有你再次从客户端写到服务器端到读缓冲区,他就会把上次剩的再读出来。" worl"

那么我们如何才能解决这个问题呢?

首先一个我们可以把buf改大一点,但是这个治标不治本,而且如果申请的空间太大,内核也并不会给我们分配这么大的空间。

下面介绍第二个解决方法:

epoll边沿模式的非阻塞处理并且在非阻塞情况下,接受数据的代码也是比较有特点的

epoll边沿模式的非阻塞处理

在介绍边沿模式的非阻塞处理前,我先介绍一个别的方法,那就是把读缓冲区的代码放到一个循环里边。

char buf[5];
while(1) {
  int len = recv(cfd, buf, sizeof(buf), 0);
}

然而,我们的recv()函数是阻塞的,如果我们读完所有的数据之后,文件描述符读缓冲区为NULL,也就是说我们读完第一次,程序就会一直阻塞到这里。程序就不能再干别的事情了,这种情况并不是我们想看到的。

那这又该如何解决呢?

我们把文件描述符设置为非阻塞,等读完数据后,我们继续往下执行,判断是否已经读完缓冲区的数据,读完跳出循环,这样我们就可以继续做别的事情了。

for (int i = 0; i < num; ++i) {	//找到被触发的文件描述符
		int fd = evs[i].data.fd;
  	if(fd == lfd){
  		int cfd = accept(fd, NULL, NULL);
      //为文件描述符设置非阻塞属性
      int flag = fcntl(cfd, F_GETFL);
      flag |= O_NONBLOCK;
      fcntl(cfd, F_SETFL, flag);

      ev.events = EPOLLIN | EPOLLET;
      ev.data.fd = cfd;
      epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
    }else{
      	char buf[5];
    
      	while(1) {
          	//接受数据部分请看下一节
        }
    }
}

epoll在边沿模式下非阻塞接收数据

recv的错误号EAGAIN表示我们操作过程中读缓冲区是没有数据的,所以为了防止我们操作空的读缓冲区导致代码报错,我们把错误号等于EAGAIN的时候作为数据被读完的标志。

while(1) {
	int len = recv(fd, buf, sizeof(buf), 0);
  if (len == -1) {
    if (error == EAGAIN) {
      printf("数据已经读完了");
      break;
    }else{
      perror("recv error");
    	exit(1);
    } 
  }
  else if (len == 0) {
    printf("客户端已断开链接。。。\n");
    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
    close(fd);
    break;
  }
  printf("read buf = %s\n", buf);
  //小写转大写
  for (int i = 0; i < len; ++i) {
    buf [i] = toupper(buf[i]);
  }
  //这样输出在终端没有乱码,因为我们指定了buf的长度
  write(STDOUT_FILENO, buf, len);
  //printf("%s", buf) 这样会有乱码,因为没有地方存“\0”
  
  //大写串发送给客户端
  if (send(fd, buf, strlen(buf) + 1, 0) == -1) {
    perror("send error");
    exit(1);
  }
}
  • 25
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值