1. 程式人生 > >基本套接字程式設計(4) -- poll篇

基本套接字程式設計(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 執行結果

伺服器端:
客戶端: