I/O多路复用之poll

poll


poll和select类似,不过在一些方面改善了select的弊端。它也是在指定的时间进行轮询文件描述符,查看是否有就绪时间发生。

和上次一样,我们先来看一下poll系统调用。

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

fds是一个pollfd的结构体数组。

struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这就是这个结构体数组每个元素。fd用来记录对应的文件描述符,events用来表示poll所监听的事件,这个由用户来设置。revents用来表示返回的事件。revents是通过内核来进行操作修改。

这里提供了一些合法事件。

事件 说明
POLLIN 普通或优先级带数据可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级数据可读
POLLOUT 普通数据可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件

后面的三个参数在events无意义,只能作为返回结果存储在revents。

另外,这里需要说的,这些参数如何设置给events,这些宏相当于每一个占用一个比特位,我们可以去想一下位图,所以,如果我们要进行设置两个事件,就使用|操作,另外,当我们去查看事件是否发生的时候,这个时候我们可以使用revents&事件,如果事件发生了,那么结果大于1。这就是一个简单的位运算的,相信你仔细想想就能够理解。

第二个参数nfds,用来监视的文件描述符的数目。

第三个参数是timeout,用来设置超时时间。

参数 说明
-1 poll将永远阻塞,等待知道某个时间发生
0 立即返回
大于0的值 设置正常时间值

返回值 
poll返回revents不为0的文件描述符的个数。 
失败返回-1

总结


poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。

poll使用了events和revents分流的特点,这样可以使得对关心事件只进行注册一次。

poll基于链表进行存储,没有最大连接数的限制,只取决于内存大小。

poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。

poll的缺点


1、大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样是不是有意义。

2、poll依然需要进行轮询,所消耗的时间太多。

3、水平触发,效率低


代码:

#include<stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<poll.h>
#include<string.h>


static void usage(const char *str)
{
	printf("Usage:%s [serv_ip] [serv_port]\n",str);
}

static int startup(const char *ip,int port)
{
	int sock = socket(AF_INET,SOCK_STREAM,0);
	if(sock < 0)
	{
		perror("socket");
		exit(1);
	}

	int opt = 1;
	setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

	struct sockaddr_in serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = inet_addr(ip);
	serv_addr.sin_port = htons(port);

	int ret  = bind(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
	if(ret < 0)
	{
		perror("bind");
		exit(2);
	}

	ret = listen(sock,128);
	if(ret < 0)
	{
		perror("listen");
		exit(3);
	}
	return sock;
}

//       poll函数第一个参数类型。
//struct pollfd {
//	int   fd;         /* file descriptor */
//	short events;     /* requested events */
//	short revents;    /* returned events */
//};
//

int main(int argc ,char *argv[])
{
	if(argc != 3)
	{
		usage(argv[0]);
		return 1;
	}

	int sock = startup(argv[1],atoi(argv[2]) );
	struct pollfd peerfd[1024]; //当有就绪事件发生的话会写到这个数组里面。

	peerfd[0].fd = sock;  //首先监听连接套接字。监听它的读事件。
	peerfd[0].events = POLLIN;

	int timeout = -1;  //表示阻塞式等待。
	int i = 1;

	for(; i < 1024; ++i)
	{
		peerfd[i].fd = -1;    //表示这个位置么有被占用。
	}

	while(1)
	{				//int poll(struct pollfd *fds, nfds_t nfds, int timeout);
		int ret = 0;
		switch(ret = poll(peerfd,1024,timeout))
		{
			case 0:
				printf("timeout...\n");
				break;
			case -1:
				perror("poll");
				break;
			default:
				{
					int i = 0;
					for(i = 0; i < 1024; ++i)//遍历数组查看有哪些就绪事件发生了。
					{
						if(i == 0 && (peerfd[i].revents & POLLIN)  )//有新的连接请求。
						{
							struct sockaddr_in client;
							socklen_t len = sizeof(client);
							int new_sock = accept(sock,(struct sockaddr*)&client,&len);
							if(new_sock < 0)
							{
								perror("accept");
								continue;
							}
							printf("get a new client\n");
							int j = 1;
							for(; j < 1024; ++j)
							{
								if(peerfd[j].fd < 0)  //找最小的未被使用的位置。
								{
									peerfd[j].fd = new_sock;
									peerfd[j].events = POLLIN;
									break;
								}
							}

							if(j == 1024 )
							{
								printf("too many client...\n");
								close(new_sock);
							}
						} //if
						else if(i != 0)
						{
							if(peerfd[i].revents & POLLIN) //客户端有读事件发生。
							{
								char buf[1024];
								ssize_t s =  read(peerfd[i].fd,buf,sizeof(buf) - 1);
								if(s > 0)
								{
									buf[s] = 0;
									printf("clinet say:%s\n",buf);
									peerfd[i].events = POLLOUT; //读完后监听写事件。
								}
								else if(s <= 0)
								{
									close(peerfd[i].fd);
									peerfd[i].fd = -1;
								}
							}
							else if(peerfd[i].revents & POLLOUT)//写事件就绪。
							{
								/* 客户端写事件发生 */
								/* 使用浏览器测试,写回到客户端,浏览器会解析字符串,显示 Hellp Epoll! */
								const char* msg = "HTTP/1.1 200 OK\r\n\r\n<html><br/><h1>Hello poll!</h1></html>";
								write(peerfd[i].fd, msg, strlen(msg));
								
								//写完后关闭套接字。
									close(peerfd[i].fd);
									peerfd[i].fd = -1;
							}

						} //else if

					} //for

				} //default

		} //switch()
	} //while(1)

	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值