I/O多路轉接之poll 函數

poll數組

1、poll()函數:服務器

這個函數是某些Unix系統提供的用於執行與select()函數同等功能的函數,自認爲poll和select大同小異,下面是這個函數的聲明:
socket

#include <poll.h>

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

參數:函數

1.第一個參數:一個結構數組,struct pollfd:測試

        fds:是一個struct pollfd結構類型的數組,每一個數組元素都是一個pollfd結構,用於指定測試某個給定描述字fd的條件。存放須要檢測其狀態的Socket描述符;每當調用這個函數以後,系統不會清空這個數組,操做起來比較方便;特別是對於socket鏈接比較多的狀況下,在必定程度上能夠提升處理的效率;這一點與select()函數不一樣,調用select()函數以後,select()函數會清空它所檢測的socket描述符集合,致使每次調用select()以前都必須把socket描述符從新加入到待檢測的集合中;所以,select()函數適合於只檢測一個socket描述符的狀況,而poll()函數適合於大量socket描述符的狀況
spa

結構以下:code

 struct pollfd{
  int fd;          //文件描述符
  short events;    //請求的事件
  short revents;   //返回的事件
  };

  events和revents是經過對錶明各類事件的標誌進行邏輯或運算構建而成的。events包括要監視的事件(就是我須要關注的時間,是讀?是寫?仍是出錯?)poll用已經發生的事件填充revents。poll函數經過在revents中設置標誌肌膚POLLHUP、POLLERR和POLLNVAL來反映相關條件的存在。不須要在events中對於這些標誌符相關的比特位進行設置。若是fd小於0, 則events字段被忽略,而revents被置爲0.標準中沒有說明如何處理文件結束。文件結束能夠經過revents的標識符POLLHUN或返回0字節的常規讀操做來傳達。即便POLLIN或POLLRDNORM指出還有數據要讀,POLLHUP也可能會被設置。所以,應該在錯誤檢驗以前處理正常的讀操做。blog

poll函數的事件標誌符值:接口

常量 說明
POLLIN 普通或優先級帶數據可讀
POLLRDNORM 普通數據可讀
POLLRDBAND 優先級帶數據可讀
POLLPRI 高優先級數據可讀
POLLOUT 普通數據可寫
POLLWRNORM 普通數據可寫
POLLWRBAND 優先級帶數據可寫
POLLERR 發生錯誤
POLLHUP 發生掛起
POLLNVAL 描述字不是一個打開的文件

  注意:進程

        1)後三個只能做爲描述字的返回結果存儲在revents中,而不能做爲測試條件用於events中。

  2)第二個參數nfds:要監視的描述符的數目。

  3)最後一個參數timeout:是一個用毫秒表示的時間,是指定poll在返回前沒有接收事件時應該等待的時間。若是  它的值爲-1,poll就永遠都不會超時。若是整數值爲32個比特,那麼最大的超時週期大約是30分鐘。 

timeout值 說明
INFTIM 永遠等待
0 當即返回,不阻塞進程
>0
等待指定數目的毫秒數


若是是對一個描述符上的多個事件感興趣的話,能夠把這些常量標記之間進行按位或運算就能夠了;

好比:

對socket描述符fd上的讀、寫、異常事件感興趣,就能夠這樣作:

struct pollfd  fds;

fds[index].events=POLLIN | POLLOUT | POLLERR;

當 poll()函數返回時,要判斷所檢測的socket描述符上發生的事件,能夠這樣作:

struct pollfd  fds;

//檢測可讀TCP鏈接請求:
if((fds[nIndex].revents & POLLIN) == POLLIN)
{
<span style="white-space:pre">	</span>//接收數據,調用accept()接收鏈接請求
}

//檢測可寫:
if((fds[nIndex].revents & POLLOUT) == POLLOUT)
{
<span style="white-space:pre">	</span>//發送數據
}

//檢測異常:
if((fds[nIndex].revents & POLLERR) == POLLERR)
{
<span style="white-space:pre">	</span>//異常處理
}
2、實例TCP服務器的服務器程序

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

#define LISTENQ 1024
#define MAXLINE 1024
#define OPEN_MAX 50000

#ifndef INFTIM 
#define INFTIM -1 
#endif             

int start_up(char* ip,int port)  //建立一個套接字,綁定,檢測服務器
{
  //sock
  //1.建立套接字
  int sock=socket(AF_INET,SOCK_STREAM,0);   
  if(sock<0)
  {
      perror("sock");
      exit(0);
  }
  
  //2.填充本地 sockaddr_in 結構體(設置本地的IP地址和端口)
  struct sockaddr_in local;       
  local.sin_port=htons(port);
  local.sin_family=AF_INET;
  local.sin_addr.s_addr=inet_addr(ip);

  //3.bind()綁定
  if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) 
  {
      perror("bind");
      exit(1);
  }
  //4.listen()監聽 檢測服務器
  if(listen(sock,back_log)<0)
  {
      perror("sock");
      exit(1);
  }
  return sock;    //這樣的套接字返回
}
	
int main(int argc, char *argv[])
{
  int i, maxi, connfd, sockfd;
  int nready;
  ssize_t n;
  socklen_t clilen;

  struct sockaddr_in servaddr;
    socklen_t len=sizeof(servaddr); 

   char buf[BUFSIZ];
    struct pollfd client[OPEN_MAX]; // 用於poll函數第一個參數的數組,存放每次的文件描述符個數
  if( argc != 3 )
    {
       printf("Please input %s <hostname>\n", argv[0]);
	 exit(2);
    }

        int listenfd=start_up(argv[1],argv[2]);      //建立一個綁定了本地 ip 和端口號的套接字描述符
		
      client[0].fd = listenfd;         //將數組中的第一個元素設置成監聽描述字
      client[0].events = POLLIN;       //將測試條件設置成普通或優先級帶數據可讀(感興趣的事件讀、寫、出錯),此處書中爲POLLRDNORM,*/
		client[0].revents = 0;           //真正發生的事件

      for(i = 1;i < OPEN_MAX; ++i)     //數組中的其它元素將暫時設置成不可用
		{
			client[i].fd = -1;
		}
        
      maxi = 0;
       while(1)
      {
         nready = poll(client, maxi+1,INFTIM);          //將進程阻塞在poll上
         if( client[0].revents & POLLIN)                //先測試監聽描述字
         {
				connfd = accept(listenfd,(struct sockaddr*)&servaddr, &clilen);

              for(i = 1; i < OPEN_MAX; ++i)
				{
					if( client[i].fd < 0 )
                  {
                      client[i].fd = connfd;      //將新鏈接加入到測試數組中
                     client[i].events = POLLIN;  //POLLRDNORM; 測試條件普通數據可讀
                      break;
                  }
              	if( i == OPEN_MAX )
               	{
                 	 	printf("too many clients"); //鏈接的客戶端太多了,都達到最大值了
                   	exit(1);
              	}

              	if( i > maxi )
                  	maxi = i;        //maxi記錄的是數組元素的個數

              	if( --nready <= 0 )
                 	continue;            //若是沒有可讀的描述符了,就從新監聽鏈接
				}
          }

          for(i = 1; i <= maxi; i++)  //測試除監聽描述字之後的其它鏈接描述字
          {
				if( (sockfd = client[i].fd) < 0) //若是當前描述字不可用,就測試下一個
                  continue;

              if(client[i].revents & (POLLIN | POLLERR))//若是當前描述字返回的是普通數據可讀或出錯條件
              {
                 if( (n = read(sockfd, buf, MAXLINE)) < 0) //從套接口中讀數據
                 {
                      if( errno == ECONNRESET) //若是鏈接斷開,就關閉鏈接,並設當前描述符不可用
                      {
                         close(sockfd);
                         client[i].fd = -1;
                      }
                      else
                          perror("read error");
                  }
                else if(n == 0) //若是數據讀取完畢,關閉鏈接,設置當前描述符不可用
                  {
                      close(sockfd);
                      client[i].fd = -1;
                  }
                  else
                      write(sockfd, buf, n); //打印數據
               if(--nready <= 0)
                   break;
              }
          }
     }
     exit(0);
 }
賜教!
相關文章
相關標籤/搜索