socket编程:多路复用I/O服务端客户端之poll

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了socket编程:多路复用I/O服务端客户端之poll相关的知识,希望对你有一定的参考价值。

一. 关于poll

    对于IO复用模型,其优点无疑是免去了对一个个IO事件就绪的等待,转而代之的是同时对多个IO数据的检测,当检测等待的事件中至少有一个就绪的时候,就会返回告诉用户进程“已经有数据准备好了,快看看是哪个赶紧处理”,而对于IO复用的实现,除了可以用select函数,另外一个函数仍然支持这种复用IO模型,就是poll函数;



二. poll函数的用法

    虽然同样是对多个IO事件进行检测等待,但poll和select多少还是有些不同的:

技术分享

函数参数中,

先来说nfds,这个是指当前需要关心的文件描述符的个数;

timeout同样是设置超时时间,只是和select的timeout是一个结构体不一样,这里只是一个整型类型,且含义是毫秒

fds是一个结构体指针,如下:

技术分享

结构体中,

fd表示所要关心的文件描述符;

events表示该文件描述符所关心的事件,这是一个输入型参数,要告诉操作系统这个文件描述符对应的事件所关心的操作事件是什么,比如读或写;

revents是一个输出型参数,表示当poll返回时告诉用户什么操作事件是就绪的,比如如果POLLIN是就绪的,那么返回时revent的值就是POLLIN,告诉用户fd事件的POLLIN是就绪的;

events和revents的值可以为如下:

技术分享

选项其实不止这三个,只是这里的讨论中这三个选项是最常用的;

events设置为POLLIN表示fd所需要读取数据,而revents若返回POLLIN则表示data已经ready可以读取了;

同样,events设置为POLLOUT表示fd所关心数据的写入,而revents返回POLLOUT则表示写事件就绪可以进行数据的写入;

至于POLLPRI,后面的解释是作为紧急选项来设置的,在TCP协议报文中有个URG的紧急指针是表示先从紧急数据的地方开始读取,这里也是这个意思;


与select不同的是,reads与writes是输入输出参数,我上一篇博客中设置中:

 else if(fds[i] > 0 &&                                FD_ISSET(fds[i],&reads))    //正常事件,但是是非监听时间,也就代表时新建立的new_sock。
                                {
                                //  char buf[1024];
                                    ssize_t s = read(fds[i],buf,sizeof(buf) -1);
                                    if(s > 0)
                                    {
                                        buf[s] = ‘\0‘;
                                    //  printf("client : %s\n",buf);
                                        printf("client : %s",buf);
                                        FD_SET(fds[i],&writes);
                                    //  write(fds[i],buf,sizeof(s)+1);
                                    }
                                    else if(s == 0)
                                    {
                                        printf("client quit...\n");
                                        close(fds[i]);
                                        fds[i] = -1;
                                    }
                                    else{}
                                }
                        else{}
 
                        if(fds[i] > 0&&                                FD_ISSET(fds[i],&writes))
                        {
                            write(fds[i],buf,sizeof(buf));
                        }

在获取读事件时对将fds[i],设置到写事件中,而在poll中,读事件判断中修改设置为写事件。需要等待下一次循环,才能够获取到写事件,然后还必须将events属性在设置回来。


poll与select不同在于描述符存储方式不同和参数类型不同。

1.结构体数组的管理:当每次有需要关心的描述符时,将其放入结构体中,每次有无效的描述符后,将其描述符置-1,下次poll函数会忽略它。当有新的描述符加入时,从头遍历结构体,将为-1的元素设为要关心的描述符事件状态。切记:当新的描述符加到结构体数组末尾时要更新关心描述符个数,即poll第二个参数。

2.每次调用poll后,结构体元素revents会存储就绪事件状态,当每次重新调用poll之前时,系统会自己设置其为0,重新监听关心事件(不需要用户重新置0)

3.poll中参数不是输入,输出型,因此timeout,struct pollfd *fds参数不需重置,nfds看情况(参照第一点),而select函数是输入输出类型,每次调用前需重置。


下面看服务端的代码,客户端就不写了:

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

#define _BACKLOG_ 5
#define _SIZE_ 64

static void usage (const char *proc)
{
	printf("%s [ip][prot]\n",proc);
}
 
static int start(char *ip,int port)	
{
	int sock = socket(AF_INET,SOCK_STREAM,0);
	if(sock < 0)
	{
		perror("socket");
		exit(1);
	}
	
	int opt = 1;
	if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)) < 0)
	{
		perror("setsockopt");
		exit(3);
	}

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

	

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

	if(listen(sock,_BACKLOG_) < 0)
	{
		perror("listen");
		exit(2);
	}
	return sock;
}



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

	int port = atoi(argv[2]);
	char *ip = argv[1];

	int listen_sock = start(ip,port);

	//pollfd arrays
	struct pollfd polls[_SIZE_];
	int index = 0;
	int timeout = 5000; //check question time
	int i = 0;
	polls[0].fd = listen_sock;
	polls[0].events = POLLIN;
	polls[0].revents = 0;
	++index;
	//init polls.fd
	for(i = 1;i < _SIZE_;++i)
	{
		polls[i].fd = -1;
	}
	char buf [1024];
	ssize_t s = 0;
	struct sockaddr_in client;
	socklen_t len = sizeof(client);
	int done = 0;
	int max_fd = 1;
	while(!done)
	{
		switch(poll(polls,max_fd,timeout))
		{
			case -1:
				perror("poll");
				break;
			case 0:
				printf("timeout\n");
				break;
			default:
				{
					size_t i = 0;
					for(;i<_SIZE_;++i)
					{
						if((polls[i].fd == listen_sock) && (polls[i].revents == POLLIN))
						{
							int accept_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
							if(accept_sock < 0)
							{
								perror("accept");
								continue;
							}
							printf("connet success\n");
							for(;i < _SIZE_;++i)
							{
								if(polls[i].fd == -1)
								{
									polls[i].fd = accept_sock;
									polls[i].events = POLLIN;
									++max_fd;
									break;
								}
							}
							if(i == _SIZE_)
							{
								close(accept_sock);
							}
						}
						else if((polls[i].fd > 0) &&(polls[i].revents == POLLIN) )
						{
							ssize_t size = read(polls[i].fd,buf,sizeof(buf)-1);
							if(size < 0)
							{
								perror("read");
								continue;
							}
							else if(size == 0)
							{
								printf("client close \n");
								struct pollfd tmp = polls[i];
								polls[i] = polls[max_fd -1];
								polls[max_fd -1] = tmp;

								close(polls[max_fd - 1].fd);
								polls[max_fd - 1].fd = -1;
							}
							else
							{
								buf[size] = ‘\0‘;
								printf("client # %s",buf);
								polls[i].events = POLLOUT;
							}
						}
						else if(polls[i].fd > 0&& 								polls[i].revents == POLLOUT)
						{
							write(polls[i].fd,buf,sizeof(buf));
							polls[i].events = POLLIN;
						}
					}
				}
				break;
		}
	}
	return 0;
}

运行结果:

技术分享

ok

本文出自 “剩蛋君” 博客,请务必保留此出处http://memory73.blog.51cto.com/10530560/1784093

以上是关于socket编程:多路复用I/O服务端客户端之poll的主要内容,如果未能解决你的问题,请参考以下文章

socket编程:多路复用I/O服务端客户端。

一举拿下 I/O 多路复用

I/O多路复用技术

unix网络编程——I/O多路复用之epoll

I/O多路复用之——select

深入Socket网络编程与I/O多路复用