select、poll、epoll

select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

 

1.select

select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select的一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但 是这样也会造成效率的降低。

2.poll

不同与select使用三个位图来表示三个fdset的方式,poll使用一个 pollfd的指针实现。pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。

3.epoll

epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

参考来源:

Linux中select poll和epoll的区别
IO多路复用之select总结
IO多路复用之poll总结
IO多路复用之epoll总结


下面关于三种方法的C语言实例。

 

客户端代码采用多路复用的思路实现。

//客户端代码,可分别实现三种多路复用的形式  select、poll、epoll ;
//通过宏来实现
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <poll.h>
#include <sys/epoll.h>

#define SERV_PORT 5001
#define SERV_IP_ADDR "127.0.0.1"
#define BACKLOG 5

#define QUIT_STR "quit"

#define SELECT 0
#define POLL 0
#define EPOLL 1



void handle_select(int fd);
void handle_poll(int fd);
void handle_epoll(int fd);
void add_event(int epollfd,int fd,int state);
void handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf);

int main(int argc, const char *argv[])
{
	int fd;
	struct sockaddr_in sin;


	bzero(&sin,sizeof(sin));
	sin.sin_family = AF_INET;
	inet_pton(AF_INET,SERV_IP_ADDR,&sin.sin_addr);
	sin.sin_port = htons(SERV_PORT);

	if( -1 == (fd = socket(AF_INET,SOCK_STREAM,0)) )
	{
		perror("socket failed");
		return -1;
	}

	if( -1 == connect(fd,(struct sockaddr*)&sin,sizeof(sin)) )
	{
		perror("connect failed");
		return -1;
	}



#if SELECT
	handle_select(fd);
#endif

#if POLL
	handle_poll(fd);
#endif

#if EPOLL
	handle_epoll(fd);
#endif
	close(fd);
	return 0;
}


void handle_select(int fd)
{
	int real_read;
	char buf[64];
	fd_set fdset;
	int maxfd = -1;
	struct timeval tout;

	while(1)
	{

		FD_ZERO(&fdset);

		FD_SET(0,&fdset);
		FD_SET(fd,&fdset);
		maxfd = fd;

		tout.tv_sec = 5;
		tout.tv_usec = 0;

		select(maxfd+1,&fdset,NULL,NULL,&tout);

		if( FD_ISSET(0,&fdset) )
		{
			bzero(buf,sizeof(buf));
			do{
				real_read = read(0,buf,sizeof(buf));
			}while( real_read<0 && EINTR == errno );
			if(real_read < 0)
			{
				perror("read");
				continue;
			}
			else if( 0 == real_read )
			{
				continue;
			}


			write(fd,buf,strlen(buf)-1);

			if( 0 == (strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))) )
			{
				printf("client quit\n");
				break;
			}
		}

		if( FD_ISSET(fd,&fdset) )
		{
			bzero(buf,sizeof(buf));
			do{
				real_read = read(fd,buf,sizeof(buf));
			}while( real_read<0 && EINTR == errno );
			if(real_read < 0)
			{
				perror("read");
				continue;
			}
			else if( 0 == real_read )
			{
				break;
			}

			printf("Received: %s\n",buf);

			if( 0 == (strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))) )
			{
				printf("client quit\n");
				break;
			}
		}
	}

}



void handle_poll(int fd)
{
	char buf[64];
	int maxfdp;
	struct pollfd pfds[2];
	int real_read;

	pfds[0].fd = fd;
	pfds[0].events = POLLIN;

	pfds[1].fd = 0;
	pfds[1].events = POLLIN;

	maxfdp = 2;

	while(1)
	{
		poll(pfds,maxfdp,5);

		if (pfds[0].revents & POLLIN)
		{
			bzero(buf,sizeof(buf));
			do{
				real_read = read(fd,buf,sizeof(buf));
			}while( real_read<0 && EINTR == errno );
			if(real_read < 0)
			{
				perror("read");
				continue;
			}
			else if( 0 == real_read )
			{
				break;
			}

			printf("Received: %s\n",buf);

			if( 0 == (strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))) )
			{
				printf("client quit\n");
				break;
			}

		}
		if (pfds[1].revents & POLLIN)
		{
			bzero(buf,sizeof(buf));
			do{
				real_read = read(0,buf,sizeof(buf));
			}while( real_read<0 && EINTR == errno );
			if(real_read < 0)
			{
				perror("read");
				continue;
			}
			else if( 0 == real_read )
			{
				continue;
			}


			write(fd,buf,strlen(buf)-1);

			if( 0 == (strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))) )
			{
				printf("client quit\n");
				break;
			}

		}
	}
}

void handle_epoll(int sockfd)
{
	int epollfd;
	struct epoll_event events[100];
	char buf[64];
	int ret;
	epollfd = epoll_create(1000);
	add_event(epollfd,0,EPOLLIN);
	add_event(epollfd,sockfd,EPOLLIN);

	while(1)
	{
		ret = epoll_wait(epollfd,events,100,5);
		handle_events(epollfd,events,ret,sockfd,buf);
	}
	close(epollfd);
}


void add_event(int epollfd,int fd,int state)
{
	struct epoll_event ev;
	ev.events = state;
	ev.data.fd = fd;
	epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);
}

void
handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf)
{
	int fd;
	int i;
	int real_read;

	for (i = 0;i < num;i++)
	{
		fd = events[i].data.fd;
		if (events[i].events & EPOLLIN)
		{
			if( fd == sockfd )
			{//判断就绪fd为连接套接字sockfd
				bzero(buf,64);
				do{
					real_read = read(sockfd,buf,64);
				}while( real_read<0 && EINTR == errno );
				if(real_read < 0)
				{
					perror("read");
					continue;
				}
				else if( 0 == real_read )
				{
					break;
				}

				printf("Received: %s\n",buf);

				if( 0 == (strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))) )
				{
					printf("client quit\n");
					break;
				}
			}
			else if( 0 == fd )
			{//那么判断就绪fd 为标准输入
				bzero(buf,64);
				do{
					real_read = read(0,buf,64);
				}while( real_read<0 && EINTR == errno );
				if(real_read < 0)
				{
					perror("read");
					continue;
				}
				else if( 0 == real_read )
				{
					continue;
				}


				write(sockfd,buf,strlen(buf)-1);

				if( 0 == (strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))) )
				{
					printf("client quit\n");
					break;
				}

			}
		}

	}
}

 

服务端使用多线程的思想

//循环里处理主监听套接字listenfd,accept监听请求连接。然后将每一个accept连接成功 返回的连接套接
//字connf分配给一个线程处理。。。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <poll.h>
#include <sys/epoll.h>

#define SERV_PORT 5001
#define SERV_IP_ADDR "127.0.0.1"
#define BACKLOG 5

#define QUIT_STR "quit"

void *cli_data_handle(void* arg);

int main(int argc, const char *argv[])
{
	int listenfd,connfd;
	struct sockaddr_in servaddr,cliaddr;
	socklen_t peerlen;
	char buf[64];

	pthread_t thread;


	bzero(&servaddr,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET,SERV_IP_ADDR,(void *)&servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	if( -1 == (listenfd = socket(AF_INET,SOCK_STREAM,0)) )
	{
		perror("socket failed");
		return -1;
	}	
	printf("socket create success....\n");


	int b_reuse = 1;
	setsockopt (listenfd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));

	if(-1 == bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) )
	{
		perror("bind failed");
		return -1;
	}
	printf("bind success....\n");

	if( -1 == listen(listenfd,10) )
	{
		perror("listen failed");
		return -1;
	}
	printf("listen success....\n");

	while(1)
	{
		bzero(&cliaddr,sizeof(cliaddr));
		peerlen = sizeof(cliaddr);
		if( (connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&peerlen)) < 0)
		{
			perror("accept");
			return -1;
		}

		if( 0 != pthread_create(&thread,NULL,cli_data_handle,(void *)&connfd) )
		{
			perror("pthread_create");
			return -1;
		}

	}

	close(listenfd);
	return 0;
}



void *cli_data_handle(void* arg)
{

	char buf[64];
	int connfd = *(int *)arg;
	int real_read =0;
	while(1)
	{
			bzero(buf,sizeof(buf));
			do{
				real_read = read(connfd,buf,sizeof(buf));
			}while( real_read<0 && EINTR == errno );
			if(real_read < 0)
			{
				perror("read");
				continue;
			}
			else if( 0 == real_read )
			{
				break;
			}
			
			printf("Received:%s\n",buf);
			
			write(connfd,buf,strlen(buf));

			if( 0 == (strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))) )
			{
				printf("client quit\n");
				break;
			}
	}
	close(connfd);
}

 

结果验证