I/O复用——poll&epollu

poll系统调用和select类似,也是在指定的时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。

int poll(struct pollfd *fds,int nfds,int timeout); 

  • fds:是一个pollfd结构类型的数组,它指定所有用户感兴趣的文件描述符上发生的可读、可写和异常等事件。传入的是数组的首地址 。

pollfd结构体定义:

struct pollfd
{
    int fd;           //用户关注的文件描述符
    short events;     //用户关注的事件
    short revents;    //表示实际发生的事件,由内核修改
};

fd成员指定文件描述符;events成员告诉poll监听fd上的哪些事件,它是一系列事件的按位或;revents成员则是由内核修改,以通知应用程序fd上实际发生了那些事件。

  • nfds:数组fds的长度,元素的个数,用户关注的文件描述符的个数
  • timeout:指定poll的超时时间;当timeout为-1 时,poll调用将一直阻塞,直到某个事件发生;当timeout为0时,poll调用将立即返回。

与select的对比:

  1. 用户关注的事件类型更多
  2. 内核修改的用户关注的分开表示,每次调用不需要重新设置
  3. 文件描述符不再是按位来表示,直接用int类型
  4. 用户关注的文件描述符的值可以更大
  5. 用户关注的文件描述符的个数由用户数组决定,所以个数会更多
  6. poll返回时,也是将用户关注的所有文件描述符返回
  7. poll检测就绪文件描述符的时间复杂度O(n),poll返回后,用户程序依旧需要循环检测哪些文件描述符就绪

poll的优缺点

优点:

  • 将用户关注的文件描述符的事件单独表示,可关注更多的事件类型
  • 将用户传递和内核修改分开,每次调用poll之前,不需要重新设置
  • poll函数没有最大文件描述符的限制

缺点:

  • 每次调用都需要将用户空间数组拷贝到内核空间
  • 每次返回都需要将所有的文件描述符拷贝到用户空间数组中,无论是否就绪
  • 返回的是所有的文件描述符,搜索就绪文件描述符的时间复杂度为O(n)

                                                          poll事件类型

poll的实现

#define _GNU_SOURCE
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<stdio.h>
#include<poll.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#define SIZE 100
void Init_Fds(struct pollfd *fds)
{
	int i=0;
	for(;i<SIZE;++i)
	{
		fds[i].fd=-1;
		fds[i].events=0;
		fds[i].revents=0;
	}
}

void Insert_Fd(struct pollfd *fds,int fd,short event)
{
	int i=0;
	for(;i<SIZE;++i)
	{
		if(fds[i].fd==-1)
		{
			fds[i].fd=fd;
			fds[i].events=event;
			break;
		}
	}
}

void Delete_Fd(struct pollfd *fds,int fd)
{
	int i=0;
	for(;i<SIZE;++i)
	{
		if(fds[i].fd==fd)
		{
			fds[i].fd=-1;
			fds[i].events=0;
			break;
		}
	}
}
int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd!=-1);
	struct sockaddr_in ser,cli;
	memset(&ser,0,sizeof(ser));
	ser.sin_family=AF_INET;
	ser.sin_port=htons(6000);
	ser.sin_addr.s_addr=inet_addr("127.0.0.1");
	int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
	assert(res!=-1);
	listen(sockfd,5);

	struct pollfd fds[SIZE];
	Init_Fds(fds);
	Insert_Fd(fds,sockfd,POLLIN);

	while(1)
	{
		int n=poll(fds,SIZE,-1);
		if(n<=0)
		{
			printf("poll error\n");
			continue;
		}
		int i=0;
		for(;i<SIZE;++i)
		{
			if(fds[i].fd!=-1)
			{
				int fd=fds[i].fd;
				if(fds[i].revents&POLLRDHUP)
				{
					printf("%d will close\n",fd);
					close(fd);
					Delete_Fd(fds,fd);
				}
				else if(fds[i].revents&POLLIN)//文件描述符就绪
				{
					if(fd==sockfd)
					{
						int len=sizeof(cli);
						int c=accept(fd,(struct sockaddr*)&cli,&len);
						if(c<0)
						{
							continue;
						}
						Insert_Fd(fds,c,POLLIN | POLLRDHUP);
					}
					else
					{
						char buff[128]={0};
						recv(fd,buff,127,0);
						printf("%d: %s\n",fd,buff);
						send(fd,"OK",2,0);
					}
				}
			}
		}
	}

}

 

epoll——Linux独有

epoll是Linux特有的I/O复用函数。它在实现和使用上与select、poll有很大的差异。首先,epoll使用一组函数来完成任务,而不是单个函数。其次,epoll吧用户关心的文件描述符上的事件放在内核里的一个事件表示中,从而不需要像select和poll那样每次调用都需要重复传入文件描述符集或事件集。但epoll需要使用一个额外的文件描述符,来标识唯一内核中的这个事件表。

int epoll_create(int size)://创建内核事件表,内核事件表底层是由红黑树来维护的

size:只是给内核一个提示,告诉它事件表需要多大

返回值:返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核时间表。

int epoll_ctl(int epollfd,int cmd,int fd,struct epoll_event *event);//设置(添加、修改、删除)内核事件表中的文件描述符上的事件

epollfd:epoll_create创建的内核事件表的标记

cmd:EPOLL_CLT_ADD(添加)      EPOLL_CLT_MOD(修改)      EPOLL_CLT_DEL(删除)

fd:需要操作的是哪个文件描述符

返回值:成功返回0,失败返回-1,并设置error

int epoll_wait(int epollfd,struct epoll_event *revents,int maxevents,int timeout);

当活动链接比较多的时候,epoll_wait的效率未必比select和poll高,因为,此时回调函数被触发的过于频繁,所有epoll_wait适用于连接数量多但活动连接较少的情况。

revent是:只返回所有就绪的文件描述符(数组)

maxevents:指定最多监听多少个事件(数组的长度)

返回值:成功返回就绪的文件描述符个数,失败返回-1

 

struct epoll_event
{
    __uint32_t events;//用户关注的事件
    epoll_data_t data;//用户数据
}

typedef union epoll_data
{
    void *ptr;
    int fd;   //用户关注的文件描述符
    uint32_t u32;
    unit64_t u64;
}epoll_data_t;

epoll的优点:

  1. 文件描述符的范围和文件描述符的个数都会扩大
  2. 事件类型会更多
  3. 用户关注的事件由内和维护,每次调用epoll_wait时,不需要将用户空间的数据拷贝到内核空间
  4. 每次epoll只会返回就绪的文件描述符
  5. 用户程序检测就绪文件描述符的效率O(1)
  6. epoll内核实现比select和poll高效
  7. selecthepoll,内核监听采用轮询方式,epoll采用回调方式
  8. epoll支持高效的ET模式

epoll的实现

#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<stdio.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#define SIZE 100

int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd!=-1);
	struct sockaddr_in ser,cli;
	memset(&ser,0,sizeof(ser));
	ser.sin_family=AF_INET;
	ser.sin_port=htons(6000);
	ser.sin_addr.s_addr=inet_addr("127.0.0.1");
	int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
	assert(res!=-1);
	listen(sockfd,5);

	int epollfd=epoll_create(5);
	assert(epollfd!=-1);

	struct epoll_event event;
	event.events=EPOLLIN;
	event.data.fd=sockfd;
	epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event);

	while(1)
	{
		struct epoll_event events[SIZE];
		int n=epoll_wait(epollfd,events,SIZE,-1);	
		if(n<=0)
		{
			printf("epoll wait error\n");
			continue;			
		}	
		int i=0;
		for(;i<n;++i)
		{
			int fd=events[i].data.fd;
			if(fd==sockfd)
			{
				int len=sizeof(cli);
				int c=accept(fd,(struct sockaddr*)&cli,&len);
				if(c<0)
				{
					continue;
				}
				event.events=EPOLLIN | EPOLLRDHUP;
				event.data.fd=c;
				epoll_ctl(epollfd,EPOLL_CTL_ADD,c,&event);
			}		
			else if(events[i].events&EPOLLRDHUP)
			{
				epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,NULL);
				close(fd);
				printf("%d was over\n",fd);
			}
			else if(events[i].events&EPOLLIN)
			{
				char buff[128]={0};
				recv(fd,buff,127,0);

				printf("%d :%s\n",fd,buff);

				send(fd,"OK",2,0);
			}
		}
	}
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值