Linux网络编程4--poll和epoll

Day4

一、poll函数使用

  • 函数说明
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
函数说明: 跟select类似, 委托内核监控可读, 可写, 异常事件
函数参数:
	fds: 一个struct pollfd结构体数组的首地址
	   struct pollfd {
	       int   fd;    //要监控的文件描述符,如果fd为-1, 表示内核不再监控
	       short events; //输入参数, 表示告诉内核要监控的事件, 读事件, 写事件, 异常事件  
	       short revents;//输出参数, 表示内核告诉应用程序有哪些文件描述符有事件发生    
	   };
	   events/revents:
	   	POLLIN:可读事件
	   	POLLOUT: 可写事件
	   	POLLERR: 异常事件
	nfds: 告诉内核监控的范围, 具体是: 数组下标的最大值+1 
	timeout: 
		=0: 不阻塞, 立刻返回
		-1: 表示一直阻塞, 直到有事件发生
		>0: 表示阻塞时长, 在时长范围内若有事件发生会立刻返回;
			如果超过了时长也会立刻返回
函数返回值:
	>0: 发生变化的文件描述符的个数
	=0: 没有文件描述符发生变化
	-1: 表示异常
  • 开发流程
使用poll模型开发服务端流程:
{
1 创建socket, 得到监听文件描述符lfd----socket()
2 设置端口复用----setsockopt()
3 绑定----bind()
4 监听----listen()
5 struct pollfd client[1024];
  client[0].fd = lfd;
  client[0].events = POLLIN;
  
  int maxi = 0;
  for(i=1; i<1024; i++)
  {
  	client[i].fd = -1;
  }
  
  while(1)
  {
  	nready = poll(client, maxi+1, -1);
  	//异常情况
  	if(nready<0)
  	{
  		if(errno==EINTR)  // 被信号中断
  		{
  			continue;
  		}
  		break;
  	}
  	
  	//有客户端连接请求到来
  	if(client[0].revents==POLLIN)
  	{
  		//接受新的客户端连接
  		cfd = accept(lfd, NULL, NULL);
  		
  		//寻找在client数组中可用位置
  		for(i=0; i<1024; i++)
  		{
  			if(client[i].fd==-1)
  			{
  				client[i].fd = cfd;
  				client[i].events = POLLIN;
  				break;
  			}
  		}
  		
  		//客户端连接数达到最大值
  		if(i==1024)
  		{
  			close(cfd);
  			continue;
  		}
  		
  		//修改client数组下标最大值
  		if(maxi<i)
  		{
  			maxi = i;
  		}
  		
  		if(--nready==0)
  		{
  			continue;
  		}
  	}
  	
  	//下面是有客户端发送数据的情况
  	for(i=1; i<=maxi; i++)
  	{
  		sockfd = client[i].fd;
  		//如果client数组中fd为-1, 表示已经不再让你内核监控了, 已经close了
  		if(client[i].fd==-1)
  		{
  			continue;
  		}
  		
  		if(client[i].revents==POLLIN)
  		{
  			//read 数据
  			n = read(sockfd, buf, sizeof(buf));
  			if(n<=0)
  			{
  				close(sockfd);
  				client[i].fd = -1;
  				
  			}
  			else 
  			{
  				//发送数据给客户端
  				write(sockfd, buf, n);
  			} 	
  			
	  		if(--nready==0)
	  		{
	  			break;
	  		}	
  		}  		
  	}  	
  }
 
  close(lfd); 
}

二、epoll函数

  • 函数介绍
函数介绍:
int epoll_create(int size)
函数说明:创建一个epoll树根
参数说明:size表示最大节点数(大于0)
返回值:成功:返回一个大于0的文件描述符,代表整个epoll树的树根
	   失败:返回-1,并设置error值

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数说明:将要监听的节点在epoll树上添加, 删除和修改
参数说明:epfd: epoll树根
		 op:EPOLL_CTL_ADD: 添加事件节点到树上
			EPOLL_CTL_DEL: 从树上删除事件节点
			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 events */
               epoll_data_t data;        /* User data variable */
           };
          event.events常用的有:
 				EPOLLIN: 读事件
				EPOLLOUT: 写事件
 				EPOLLERR: 错误事件
                EPOLLET: 边缘触发模式
		  event.fd: 要监控的事件对应的文件描述符
 
 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
函数说明:等待内核返回事件发生
参数说明:
		epfd: epoll树根
		events: 传出参数, 其实是一个事件结构体数组
		maxevents: 数组大小
		timeout:
			-1: 表示永久阻塞
			0: 立即返回
			>0: 表示超时等待事件
返回值:
	成功: 返回发生事件的个数
	失败: 若timeout=0, 没有事件发生则返回; 返回-1, 设置errno值, 

epoll_wait的events是一个传出参数, 调用epoll_ctl传递给内核什么值, 当epoll_wait返回的时候, 内核就传回什么值,不会对struct event的结构体变量的值做任何修改.

开发思路

使用epoll模型开发服务器流程:
{
1 创建socket, 得到监听文件描述符lfd----socket()
2 设置端口复用----setsockopt()
3 绑定----bind()
4 监听----listen()
5 创建一棵epoll树
  int epfd = epoll_create();
	
  //将监听文件描述符上树
  struct epoll_event ev;
  ev.evetns = EPOLLIN;  //可读事件
  ev.data.fd = lfd;
  epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
   
  struct epoll_event events[1024];
  while(1)
  {
  	nready = epoll_wait(epfd, events, 1024, -1);
  	if(nready<0)
  	{
  		if(errno==EINTR)//被信号中断
  		{
  			continue;
  		}
  		break;
  	}
  	
  	for(i=0; i<nready; i++)
  	{
  		sockfd = events[i].data.fd;
  		//有客户端连接请求到来  	
  		if(sockfd==lfd)
  		{
  			cfd = accept(lfd, NULL, NULL);
  			
  			//将cfd对应的读事件上epoll树
  			ev.data.fd = cfd;
  			ev.evetns = EPOLLIN; 
  			epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
  			continue;
  		}
  		
  		//有客户端发送数据过来
  		n = Read(sockfd, buf, sizeof(buf));
  		if(n<=0)
  		{
  			close(sockfd);
  			//将sockfd对应的事件节点从epoll树上删除
  			epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
  			perror("read error or client closed");
  			continue;
  		}
  		else 
  		{
  			write(sockfd, buf, n);
  		}
  	} 
  }
  
  Close(epfd);
  close(lfd);
  
  return 0;
}
  • 代码实现
#include <stdio.h>
#include <ctype.h>
#include "wrap.h"
#include <sys/epoll.h>

int main()
{
    int lfd;
    int cfd;
    int i;
    int k;
    char buf[1024];
    int nready;
    int n;
    int sockfd;


    //创建socket
    lfd = Socket(AF_INET, SOCK_STREAM, 0);
    
    //设置端口复用
    int opt;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));

    //绑定
    struct sockaddr_in serv;
    serv.sin_family = AF_INET;
    serv.sin_port = htons(8888);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    Bind(lfd, (struct sockaddr *)&serv, sizeof(struct sockaddr_in));

    //监听
    Listen(lfd, 128);

    //创建一棵epoll树
    int epfd = epoll_create(1024);
    if(epfd < 0)
    {
        perror("create epoll error");
        return -1;
    }

    //将监听文件描述符lfd上树
    struct epoll_event ev;
    ev.data.fd = lfd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);

    struct epoll_event events[1024];
    while(1)
    {
        nready = epoll_wait(epfd, events, 1024, -1);
        if(nready < 0)
        {
            if(errno == EINTR)
            {
                continue;
            }
            break;
        }

        for(i = 0; i < nready; i++)
        {
            sockfd = events[i].data.fd;
            //有客户端连接请求到来
            if(sockfd == lfd)
            {
                cfd = Accept(lfd, NULL, NULL);

                //将cfd文件描述符进行上树操作
                ev.data.fd = cfd;
                ev.events = EPOLLIN;
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
                continue;
            }            

            //有客户端数据发送
            memset(buf, 0x00, sizeof(buf));
            n = Read(sockfd, buf, sizeof(buf));
            if(n<=0)
            {
                close(sockfd);
                //将sockfd对应的文件描述符从epoll树上删除
                epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
                perror("read error or client closed");
                continue;
            }
            else
            {
                printf("n==[%d], buf==[%s]", n, buf);
                for(k = 0; k < n; k++)
                {
                    buf[k] = toupper(buf[k]);
                }
                Write(sockfd, buf, n);
            }


        }
    }
    Close(epfd);
    close(lfd);
    return 0;

}

epoll的LT和ET模式:
  1 epoll默认情况下是LT模式, 在这种模式下, 若读数据一次性没有读完,缓冲区中还有可读数据, 则epoll_wait还会再次通知
  2 若将epoll设置为ET模式, 若读数据的时候一次性没有读完, 则epoll_wait不再通知,直到下次有新的数据发来.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值