高性能服务器编程-----I/O多路复用(poll)

I/O复用的第二个系统调用poll

poll  API详解

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

poll  API原型

#include<poll.h>
int poll(struct pollfd* fds,nfds_t nfds,int timeout);
        //返回  -1出错    0超时    >0就绪文件描述符个数

fds:一个pollfd结构体类型的数组,指定所有我们感兴趣的文件描述符以及其上发生的事件类型

nfds:指定被监听事件集合fds的大小(数组元素个数) nfds_t即unsigned long int类型(可以直接用int)

timeout:设置poll的超时时间(单位是毫秒)。timeout为-1阻塞,直到有事件发生才返回;timeout为0,poll立即返回。

对于struct pollfd结构体定义如下:

struct pollfd
{
    int fd;        //要监听的文件描述符
    short events;  //注册的关注的事件
    short revents; //实际发生的事件,由内核填充
};

fd指定文件描述符;

events告诉poll监听该文件描述符上的哪些事件,可以是一系列事件的按位或

revents由内核修改,通知应用程序fd上实际发生的事件

poll支持的事件类型如下表

可以看出他将可读事件和可写事件划分的更加详细了,但是Linux不完全支持。

之前我们判断客户端是否断开连接通常是用recv的返回值是否等于0来判断,实际上这是不太可靠的,对于断开连接的情况,GNU为poll增加了一个POLLRDHUP事件,他在socket上接收到对端关闭连接触发,但是这个事件触发必然会引起EPLLIN(读)事件,注意这一点,否则在编写代码时有可能会出错。另外,使用EPOLLRDHUP事件需要定义_GNU_SOURCE

与select的比较

(1)同select一样,我们要监听哪些文件描述符就需要将用户注册的事件拷贝给内核,只不过在select是拷贝的fd_set结构,而poll拷贝的是struct pollfd数组。

(2)poll可以关注的事件比select更多,而且更加的细化了事件的分类

(3)内核给用户返回就绪的文件描述符时,select是将所有的文件描述符都拷贝给用户传入的关注事件fd_set结构,而对于poll内核只是将struct_pollfd数组中的每一个元素的revents拷贝给原来传入的struct_pollfd数组中的revents成员,不会更改也没有必要修改用户所关注的事件events(因为传入和传出的值是不会变的),用户就不必在poll调用之前像select一样重置关注的事件,因为poll将用户注册的需要关注的文件描述符由内核填充的就绪的与未就绪的文件描述符定义了两个不相干的变量(即内核修改的与用户关注的分开定义),所以互不影响,不用调用前重置。

(4)从pollfd可以看出poll中的文件描述符是直接用int定义的,而不是用位来表示,所以用户关注的文件描述符的值可以更大,另外,关注的文件描述符数组大小是用户定义的,所以用户可以关注的文件描述符的数量是不限制的。

(5)由第三点可以看出poll只是将用户关注的事件与内核返回的事件分开,但是内核返回的事件也是全部返回,并不是只返回就绪的文件描述符,这点其实同select一样,用户判断那个文件描述符上有事件发生依旧得循环检测,时间复杂度依旧是O(n)。

代码实现

#define _GNU_SOURCE   //POLLRDHUP使用的包含,写在最前边
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<poll.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#define SIZE 100
//初始化fds数组
void InitFds(struct pollfd* fds)
{
	int i=0;
	for(;i<SIZE;i++)
	{
		fds[i].fd=-1;
		fds[i].events=0;
		fds[i].revents=0;
	}
}
//往pollfd中插入监听的fd及fd上所关系的事件
void InsertFd(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;
		}
	}
}

//将已断开链接,不再监听的文件描述从fds中删除
void DeleteFd(struct pollfd* fds,int fd)
{
	int i=0;
	for(;i<SIZE;i++)
	{
		if(fd==fds[i].fd)
		{
			fds[i].fd=-1;
			break;
		}
	}
}

//对于poll中,我们常用的事件就是读,写与TCP连接被对方关闭
//POLLRDHUP发生的同时,读事件肯定发生,所以尽量将POLLRDHUP事件先处理或加控制
int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd!=-1);

	struct sockaddr_in ser,cli;
	ser.sin_family=AF_INET;
	ser.sin_port=htons(7700);
	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];
	InitFds(fds);
	InsertFd(fds,sockfd,POLLIN);  //对于sockfd就算接受客户端连接,即读事件
	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)  //因为POLLRDHUP触发会触发到POLLIN,所以提前处理这种特殊的,放在POLLIN事件之后可能会出错
				{
					close(fd);  //因为我们根据recv是否等于0来判断连接是否断开是不可靠的,所以poll专门提供了连接断开的事件POLLRDHUP
					DeleteFd(fds,fd);
					printf("link break\n");
					continue;
				}
				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;
						InsertFd(fds,c,POLLRDHUP|POLLIN);//将监听套接字监听,触发的事件是关闭连接及可读事件发生
					}
					else   
					{
						char buff[128]={0};
						recv(fd,buff,127,0);
						printf("%d:%s\n",fd,buff);
						send(fd,"ok",2,0);
					}
				}
				else  //对于事件未发生的套接子及其他事件不处理
				{
					continue;
				}
			}
			else
			{
				continue;
			}
		}

	}
}

猜你喜欢

转载自blog.csdn.net/Eunice_fan1207/article/details/84822363
今日推荐