关于Linux的I/O复用中select,poll,epoll的使用

I/O复用使得程序可以监听多个文件描述符,能够用提高程序的性能;

select

selcet的系统调用的用途是:在一段时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。

#include<sys/select.h>

int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);

1)nfds参数:指被监听的文件描述符的总数;它通常被设置为selcet监听的所有文件描述符中的最大值加1,因为文件描述符是从0开始计数的。

2)readfds、writefds、exceptfds:分别指的是可读、可写和异常事件等事件对应的文件描述符集合。应用程序调用select函数时,通过这三个参数来传入自己感兴趣的文件描述符。

3)struct timeval * timeout 参数是设置select的超时时间。

select的返回值:成功时返回所关注的就绪的文件描述符的总数。如果超时时间内没有文件描述符就绪,select将返回0;

select失败时返回-1并设置errno。如果在select等待期间,程序接收到信号,则select立即返回-1,并设置errno为EINTR。

关于fd_set的结构体实际上是一个32位的整型数组,该数组每一位(bit)表示一个文件描述符,32x4x8 = 1024,所以最多可以表示1024个文件描述符;且1024位select所能关注的文件描述符的上限个数,如果想修改这个值,需要重新编译内核,这是select的缺点之一。

同时由于位操作繁琐,linux提供了一些列宏来设置fd_set中的位

#include<sys/select.h>

FD_ZERO(fd_set * fdset);                 //清除 fd_set的所有位

FD_SET(int fd,fd_set * fdset);        //设置fd_set中的对应位的fd为1

FD_CLR(int fd,fd_set * fdset);        //清除fd_set中的对应位的fd为0

int FD_ISSET(int fd,fd_set * fdset);//测试fd_set的对应位fd是否被设置

如果给timeout传递0,则selet会直接返回,如果给timeout传递NULL,则select将一直阻塞,直到有文件描述符就绪。

在这给出一个服务器采用select的例子:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/select.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<pthread.h>
#include<semaphore.h>

void Init_fds(int * fds, int len)
{
	int i = 0;
	for(;i< len ;++i)
	{
		fds[i] = -1;
	}
}

void Insert_fd(int *fds,int fd,int len)
{
	int i = 0;
	for(;i<len ;++i)
	{
		if(fds[i] = -1);
		{
			fsd[i] = fd;
			break;
		}
	}
}

void Delete_fd(int *fds,int fd,int len);
{
	int i = 0;
	for(;i<len ;i++)
	{
		if(fds[i] = fd)
		{
			fds[i] = -1;
			break;
		}
	}
}

int main()
{
	int socketfd = socket(AF_INET,SOCK_STREAM,0);
	assert(socketfd != 0);
	
	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(socketfd,(struct sockaddr*)&ser,sizeof(ser));
	assert(res != -1);
	
	listen(socketfd,5);
	
	fd_set readfds;
	int fsd[100] ;//数组用来保存文件描述符
	Init_fds(fds,100);//初始化数组,
	Insert_fd(fds,socketfd,fd);//将文件描述符插入数组
	
	
	while(1)
	{
		int maxfd = -1;
		FD_ZERO(&readfds);
		int i = 0;
		for(;i<len ;++i)
		{
			if(fds[i] != -1)
			{
				if(fds[i] > maxfd)//保存文件描述符最大的
				{
					maxfd = fds[i];
				}
				FD_SET(fds[i],&readfds);
			}
		}
		int n = select(maxfd + 1,&readfds,NULL,NULL,NULL);
		if(n <= 0)
		{
			printf("select fail\n");
			continue;
		}
		
		for(i= 0;i < 100;i++)
		{
			if(fds[i] != -1 && FD_ISSET(fds[i],&readfds))
			{
				if(fds[i] == socketfd)
				{
					socklen_t clilen =sizeof(cli); 
					int c = accept(socketfd,(struct sockaddr*)&cli,&clilen);
					if(c < 0)
					{
						continue;
					}
				}
				Insert_fd(fds,c,100);
			}
			else
			{
				int fd = fds[i];
				char buff[128] = {0};
				int n = recv(fd,buff,127,0);
				if(n <= 0)
				{
					close(fd);
					Delete_fd(fds,c,100);
					continue;
				}
				printf("%d: %s\n",fd,buff);
				send(fd,"ok",2,0);
			}
		}
	}
	
	return 0;
}

poll

#include<poll.h>

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

fds参数指的是一个pollfd结构体类型的一个数组,他指定所有我们感兴趣的文件描述符上发生的可读、可写和异常事件。

struct pollfd
{
    int fd;        //文件描述符
    short events;  //注册的事件
    short revents; //实际发生的事件
}

events成员记录poll应该监听发的上的哪一些事件,它是一些系列事件按位或表示;

revents成员是由内核修改,来通知程序fd上实际发生了哪些事件。

poll支持的事件表如下:

nfds参数指的是被监听事件集合的fds的大小。是一个无符号long int类型的。

timeout参数指的是poll的超时时间,如果timeout= -1;poll将永远阻塞,直到有事件发生;当timeout为0时,poll调用立即返回。

返回值与select相同;

这给出服务器用poll的例子:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/select.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<poll.h>

void Init_Fds(struct pollfd *fds)
{
	for(int i = 0; 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_Fds(struct pollfd *fds,int fd)
{
	int i= 0;
	for(;i < SIZE;i++)
	{
		if(fds[i].fd == fd)
		{
			fds[i].fd = -1;
			fds.events = 0;
		}
	}
}

int main()
{
	int socketfd = socket(AF_INET,SOCK_STREAM,0);
	assert(socketfd != 0);
	
	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(socketfd,(struct sockaddr*)&ser,sizeof(ser));
	assert(res != -1);
	
	listen(socketfd,5);
	
	struct pollfd fds[100];
	Init_Fds(fds);
	Insert_Fd(fds,socketfd,POLLIN);
	
	while(1)
	{
		int n  = poll(fds,SIZE,-1);
		if(n <= 0)
		{
			printf("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)
				{
					close(fd);
					Delete_Fds(fds,fd);
				}
				else if(fd[i].events & POLLIN)
				{
					if(fd = socketfd)
					{
						socklen_t clilen =sizeof(cli); 
						int c = accept(socketfd,(struct sockaddr*)&cli,&clilen);
						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

epoll是Linux特有的一种i/o复用,它与select、poll有很大差异;epoll用一组函数来完成任务,不是单个函数,其次epoll把用户关心的文件描述符上的事件放在内核的事件表中,从而无需像select和poll一样每次都要调用都要重复传入文件描述符集,或事件集。但epoll需要使用一个额外的文件描述符,来唯一标识内核维护的事件表。

这个文件描述符使用epoll_create函数来创建时:

#include<sys/epoll.h>

int epoll_create(int size);        //size为创建该事件表的大小;

int epoll_ctl(int epfd,int op,int fd,struct epoll_events);

fd 是要操作的文件描述符,op参数则指定操作类型,

EPOLL_CLT_ADD,往事件表上添加事件

EPOLL_CLT_MOD,修改事件表上的事件

EPOLL_CLT_DEL,删除事件表上的事件

events指定是贱,它是epoll_events结构体指针类型;定义如下:

struct epoll_events
{
    _uint32_t events;
    epoll_data_t data;
};

events成员描述事件类型。epoll支持的事件与poll基本相同。表示epoll事件类型的宏是在poll对应的宏前加上"E",比如epoll的数据可读事件是EPOLLIN。epoll有两个额外的事件类型——EPOLLET 和EPOLLONESHOT。

epoll_ctl成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno。

epoll的系统调用的主要接口是epoll_wait,

epoll_wait(int epfd,struct epoll_event *events, int maxevents, int timeout);

maxevents指定最多监听的事件,必须大于0;

events用来表示内核返回就绪的文件描述符。不用像select和poll那样一直进程轮询来查看哪一个文件描述符就绪,由内核直接将就绪的文件描述符返回,这就极大的提高了效率。

epoll的例子:

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

int main()
{
	int socketfd = socket(AF_INET,SOCK_STREAM,0);
	assert(socketfd != 0);
	
	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(socketfd,(struct sockaddr*)&ser,sizeof(ser));
	assert(res != -1);
	
	listen(socketfd,5);
	while(1)
	{
		struct epoll_event events[SIZE];
		int n = epoll_wait(epollfd,events,SIZE,-1);
		if(n <=0 )
		{
			stf::cout<<"fail"<<std::endl;
			continue;	
		}
		int i = 0;
		for(;i<n;i++)
		{
			int fd = events[i].data.fd;
			if(fd==socketfd)
			{
				socklen_t clilen = sizeof(clilen);
				int c = accept(socketfd,(struct sockaddr*)&cli,&clilen);
				if(c<0)
				{
					std::cout<<"accept fail"<<std::endl;
					continue;
				}
				event.events = EPOLLIN | EPOLLRDHUP;
				event.data.fd = c;
				epoll_clt(epollfd,EPOLL_CTL_ADD,fd,NULL);
			}
			else if(events[i].events &EPOLLRDHUP)
			{
				epoll_clt(epollfd,EPOLL_CLT_DEL,fd,NULL);
				close(fd);
				std::cout<<fd;
				std::cout<<"is over"<<std::endl;
			}
			else if(events[i].events & EPOLLIN)
			{
				char cmd[128] = {0};
				recv(fd,cmd,127,0);
				std::cout<< fd;
				std::cout<<" " <<cmd<<std::endl;
				send(fd,"ok",2,0);
			}
		}
	}
	
}

以上select,poll,epoll的例子都为简单的实现。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值