基本套接字编程(4) -- poll篇

1. poll技术

poll函数起源于SVR3,最初局限于流设备。SVR4取消了这种限制,允许poll工作在任何描述符上。poll提供的功能与select类似,不过在处理流设备时,它能够提供额外的信息。
poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

1.1 函数原型

 include <poll.h>

int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
					返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1
第一个参数:是指向一个结构数组第一个元素的指针。每个数组元素都是一个pollfd结构,用于指定测试某个给定描述符fd的条件。
struct pollfd {
intfd;/*descriptor to check  文件描述符*/
shortevents;/*events of interest on fd  等待的事件*/
shortrevents;/*events that occurred on fd  实际发生的事件*/
};
每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。

下面列出用于指定events标志以及测试revents标志的一些常值:
  •   POLLIN         普通或优先级带数据可读。
  •   POLLRDNORM       普通数据可读。
  •   POLLRDBAND      优先级带数据可读。
  •   POLLPRI        高优先级数据可读。
  •   POLLOUT           普通数据可写。
  •   POLLWRNORM     普通数据可写不会导致阻塞。
  •   POLLWRBAND     优先级带数据可写。
  •   POLLMSGSIGPOLL     消息可用。
此外,只能用于revents域中的标志还有:
  •   POLLER     指定的文件描述符发生错误。
  •   POLLHUP   指定的文件描述符挂起事件。
  •   POLLNVAL  指定的文件描述符非法。
这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。
使用poll()和select()不一样,你不需要显式地请求异常情况报告。
  • POLLIN | POLLPRI等价于select()的读事件;
  • POLLOUT |POLLWRBAND等价于select()的写事件;
  • POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM
例如,要同时监视一个文件描述符是否可读和可写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而

第二个参数:timeout参数是指定poll函数返回前等待多长时间。它是一个指定等待毫秒数的正值,可能取值为:
poll的timeout参数值
timeout参数值 说明
INFTIM
0
>0
永远等待
立即返回,不阻塞进程
等待指定数目的毫秒数







INFTIM常值被定义为一个负值。POSIX规范要求在头文件<poll.h>中定义,不过许多系统仍然把它定义在头文件<sys/stropts.h>中。如果两者均包含,依然出现未定义错误,则手动定义其为一个负值即可。
timeout参数指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。

1.2 返回值和错误代码

(1)成功时,poll()返回结构体中revents域不为0的文件描述符个数;
(2)如果在超时前没有任何事件发生,poll()返回0;
(3)失败时,poll()返回-1,并设置errno为下列值之一:
  •   EBADF         一个或多个结构体中指定的文件描述符无效。
  •   EFAULTfds   指针指向的地址超出进程的地址空间。
  •   EINTR      请求的事件之前产生一个信号,调用可以重新发起。
  •   EINVALnfds  参数超出PLIMIT_NOFILE值。
  •   ENOMEM       可用内存不足,无法完成请求。
回顾在select技术中,我们就常量FD_SETSIZE以及每个描述符集中最大描述符数目相比每个进程中最大描述符数目展开讨论。而在poll技术中,便不再有此问题,因为分配一个pollfd结构的数组并把该数组中元素的数目通知内核成了调用者的责任。内核不再需要知道类似fd_set的固定大小的数据类型。

2. TCP回射程序实例

2.1 server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <poll.h>
#include <limits.h>		/*for OPEN_MAX*/
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>


#ifndef OPEN_MAX
#define OPEN_MAX 1024
#endif

#ifndef INFTIM
#define INFTIM -1
#endif

#define PORT 8888
#define MAX_LINE 2048
#define LISTENQ 20


int main(int argc , char **argv)
{
	int i, maxi, listenfd, connfd, sockfd;

	int nready;
	
	ssize_t n, ret;
		
	struct pollfd client[OPEN_MAX];

	char buf[MAX_LINE];

	socklen_t clilen;

	struct sockaddr_in servaddr , cliaddr;

	/*(1) 得到监听描述符*/
	listenfd = socket(AF_INET , SOCK_STREAM , 0);

	/*(2) 绑定套接字*/
	bzero(&servaddr , sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(PORT);

	bind(listenfd , (struct sockaddr *)&servaddr , sizeof(servaddr));

	/*(3) 监听*/
	listen(listenfd , LISTENQ);

	/*(4) 设置poll*/
	client[0].fd = listenfd;
	client[0].events = POLLRDNORM;
	for(i=1 ; i<OPEN_MAX ; ++i)
	{
		client[i].fd = -1;
	}//for
	maxi = 0;

	/*(5) 进入服务器接收请求死循环*/
	while(1)
	{
		nready = poll(client , maxi+1 , INFTIM);
		
		if(client[0].revents & POLLRDNORM)
		{
			/*接收客户端的请求*/
			clilen = sizeof(cliaddr);

			printf("\naccpet connection~\n");

			if((connfd = accept(listenfd , (struct sockaddr *)&cliaddr , &clilen)) < 0)
			{
				perror("accept error.\n");
				exit(1);
			}//if		

			printf("accpet a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr) , cliaddr.sin_port);
			
			/*将客户链接套接字描述符添加到数组*/
			for(i=1 ; i<OPEN_MAX ; ++i)
			{
				if(client[i].fd < 0)
				{
					client[i].fd = connfd;
					break;
				}//if
			}//for

			if(OPEN_MAX == i)
			{
				perror("too many connection.\n");
				exit(1);
			}//if
	
			/*该描述符等待的事件*/
			client[i].events = POLLRDNORM;
			if(i > maxi)
				maxi = i;

			if(--nready < 0)
				continue;
		}//if
			
		for(i=1; i<=maxi ; ++i)
		{
			if((sockfd = client[i].fd) < 0)
				continue;
			/*该链接描述符实际发生的事件*/
			if(client[i].revents & (POLLRDNORM | POLLERR))
			{
				/*处理客户请求*/
				printf("\nreading the socket~~~ \n");
				
				bzero(buf , MAX_LINE);
				if((n = read(sockfd , buf , MAX_LINE)) <= 0)
				{
					close(sockfd);				
					client[i].fd = -1;
				}//if
				else{
					printf("clint[%d] send message: %s\n", i , buf);
					if((ret = write(sockfd , buf , n)) != n)	
					{
						printf("error writing to the sockfd!\n");
						break;
					}//if
				}//else
				if(--nready <= 0)
					break;
			}//if
		}//for
	}//while
	exit(0);
}

2.2 client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>

#define PORT 8888
#define MAX_LINE 2048

int max(int a , int b)
{
	return a > b ? a : b;
}

/*readline函数实现*/
ssize_t readline(int fd, char *vptr, size_t maxlen)
{
	ssize_t	n, rc;
	char	c, *ptr;

	ptr = vptr;
	for (n = 1; n < maxlen; n++) {
		if ( (rc = read(fd, &c,1)) == 1) {
			*ptr++ = c;
			if (c == '\n')
				break;	/* newline is stored, like fgets() */
		} else if (rc == 0) {
			*ptr = 0;
			return(n - 1);	/* EOF, n - 1 bytes were read */
		} else
			return(-1);		/* error, errno set by read() */
	}

	*ptr = 0;	/* null terminate like fgets() */
	return(n);
}

/*普通客户端消息处理函数*/
void str_cli(int sockfd)
{
	/*发送和接收缓冲区*/
	char sendline[MAX_LINE] , recvline[MAX_LINE];
	while(fgets(sendline , MAX_LINE , stdin) != NULL)	
	{
		write(sockfd , sendline , strlen(sendline));

		bzero(recvline , MAX_LINE);
		if(readline(sockfd , recvline , MAX_LINE) == 0)
		{
			perror("server terminated prematurely");
			exit(1);
		}//if

		if(fputs(recvline , stdout) == EOF)
		{
			perror("fputs error");
			exit(1);
		}//if

		bzero(sendline , MAX_LINE);
	}//while
}

int main(int argc , char **argv)
{
	/*声明套接字和链接服务器地址*/
    int sockfd;
    struct sockaddr_in servaddr;

    /*判断是否为合法输入*/
    if(argc != 2)
    {
        perror("usage:tcpcli <IPaddress>");
        exit(1);
    }//if

    /*(1) 创建套接字*/
    if((sockfd = socket(AF_INET , SOCK_STREAM , 0)) == -1)
    {
        perror("socket error");
        exit(1);
    }//if

    /*(2) 设置链接服务器地址结构*/
    bzero(&servaddr , sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    if(inet_pton(AF_INET , argv[1] , &servaddr.sin_addr) < 0)
    {
        printf("inet_pton error for %s\n",argv[1]);
        exit(1);
    }//if

    /*(3) 发送链接服务器请求*/
    if(connect(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0)
    {
        perror("connect error");
        exit(1);
    }//if

	/*调用消息处理函数*/
	str_cli(sockfd);	
	exit(0);
}

2.3 运行结果

服务器端:
基本套接字编程(4) -- poll篇_第1张图片
客户端:



GitHub源码网址

你可能感兴趣的:(linux,socket,poll)