《unix网络编程》(15)poll函数以及使用poll的客户服务器程序

poll和select对比

       select存在一个明显缺陷:<sys/select.h>中定义的FD_SETSIZE常值是数据类型fd_set中描述符总数,其值通常为1024。当今的Unix版本允许每个进程使用无限数目的描述符(受限于内存总量和管理性),然而增加描述符集大小的唯一方法是先增大FD_SETSIZE,而要重新编译内核才行

        此外,在文章《unix网络编程》(14)使用select、shutdown的客户服务器程序中提到,文章中的服务器程序要分配一个client数组以及一个rset的描述符集。

        而poll可以避免select存在的这两个问题。对于poll来说,分配一个pollfd结构的数组并把该数组中元素的数目通知内核成了调度者的责任。内核不再需要知道类似fd_set的固定大小的数据类型。


poll函数

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
//返回:如有就绪描述符就返回其数目,超时返回0,若出错返回-1.
参数:

(1)第一个参数是指向一个结构数组第一个元素的指针。每个元素是一个pollfd结构,用于指定测试某个给定描述符fd的条件。其后两个成员避免了使用值-结果参数,select的中间三个参数都是值-结果参数。下图是指定这两个参数的一些常值。

         poll识别三类数据:普通(normal)、优先级带(priority band)、高优先级(high priority)

//pollfd结构体
struct pollfd
{
    int   fd;         /* file descriptor :要测试的描述符*/
    short events;     /* requested events: 要测试的事件 */
    short revents;    /* returned events :  返回该描述符的状态*/
};

        然而,在我的Ubuntu系统中给出的如下图的解释,因此我没在本文后面的程序中使用POLLRDNORM常值,而使用了POLLIN(定义为POLLRDNORM和POLLRDBAND的逻辑或)。

(2)第二个参数nfds:指定第一个参数结构数组中元素的个数。

(3)第三个参数timeout:poll函数返回前等待的时间,是毫秒数的正数。


如果不关心某个特定描述符,可以把它对应的pollfd结构的fd成员设为负值,这样poll函数将忽略这样的pollfd结构的events成员,返回时将revents置为0. 


使用poll的服务器端程序

//该版本使用poll代替select
#include "myheader.h"
#define OPEN_MAX 1024 //《Unix网络编程》上说该值在<limits.h>中定义但我的系统未定义

int main(int argc, char **argv)
{
  int i, maxi, listenfd, connfd, sockfd;
  int nready;
  ssize_t n;
  char buf[MAXLINE];
  char cliip[MAXLINE]; //作为inet_ntop的第三个参数,返回点分十进制ip
  socklen_t clilen;
  
  //我们声明在pollfd结构数组中存在OPEN_MAX个元素
  //确定一个进程任何时刻打开的描述符最大值并不容易,
  //方法之一就是以参数_SC_OPEN_MAX调用POSIX的sysconf函数,
  //然后动态分配一个合适大小的数组。但是该函数的返回可能是
  //“indeterminate”(不确定),因此这里使用POSIX的OPEN_MAX常值。
  struct pollfd client[OPEN_MAX];
  struct sockaddr_in cliaddr, servaddr;
  
  listenfd = Socket(AF_INET, SOCK_STREAM, 0);
  
  memset(&servaddr, 0, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(SERV_PORT);
  
  Bind(listenfd, (const struct sockaddr*)&servaddr, sizeof(servaddr));
  
  Listen(listenfd, LISTENQ);
  
  //将client数组第一项用于监听套接字,其他项描述符置为-1;
  //将第一项设置我POLRDNORM事件,这样有新的连接准备好被接受时poll会通知我们。
  client[0].fd = listenfd;
  client[0].events = POLLIN;
  for (i = 1; i < OPEN_MAX; i++)
    client[i].fd = -1;
  maxi = 0; //数组client[]正在使用下标的最大值
  
  for ( ; ; ) {
    //调用poll以等待新连接或现有连接上有数据可读
    //当一个新连接被接受后,在client中查找第一个描述符成员为负的可用项
    //从下标1开始,因为client[0]固定用于监听套接字。找到一个可用项后,
    //将新连接的描述符保存在其中,并设置POLLIN事件。
    nready = Poll(client, maxi + 1, -1);
    if (client[0].revents & POLLIN) {
      clilen = sizeof(cliaddr);
      connfd = Accept(listenfd, (const struct sockaddr*)&cliaddr, &clilen);
      printf("new client: %s, port %d \n",
	    Inet_ntop(AF_INET, &cliaddr.sin_addr, cliip, sizeof(buf)),
             ntohs(cliaddr.sin_port));
	     
      for (i = 1; i < OPEN_MAX; i++) {
        if (client[i].fd < 0) {
	  client[i].fd = connfd;
	  break;
	}
      }
      if (i == OPEN_MAX)
	err_quit("too many clients");
      client[i].events = POLLIN;
      if (i > maxi)
	maxi = i;
      if (--nready <= 0)
	continue;
    }
    
    //检查两个返回事件:POLLIN和POLLERR。没有在event成员中
    //设置第二个事件,因为它在条件成立时总是返回。我们检查POLLERR
    //的原因是:有些实现在一个连接上接收到RST时返回的是POLLERR事件,而
    //其他知识POLLIN事件。不论哪种情形,都调用read,当错误发生时,
    //read将返回这个错误。当一个现有连接由客户终止时,将其fd成员置为-1.
    for (i = 1; i <= maxi; i++) {
      if ((sockfd = client[i].fd) < 0)
	continue;
      if (client[i].revents & (POLLRDNORM| POLLERR)) {
        if ((n = read(sockfd, buf, MAXLINE)) < 0) {
	  if (errno == ECONNRESET) { //connection reset by client
	    Close(sockfd);
	    client[i].fd = -1;
	  }
	  else
	    err_sys("read error");
	}
	else if (n == 0) { //connection closed by client
	  Close(sockfd);
	  client[i].fd = -1;
	}
	else
	  Writen(sockfd, buf, n);
	
	if (--nready <= 0)
	  break;
      }
    }
  }
}


完整源码下载

http://download.csdn.net/detail/u013074465/8567889

github源码:

服务器端  https://github.com/liyangddd/linux_practice/blob/master/network_programming/tcpsrvepoll.c

客户端   https://github.com/liyangddd/linux_practice/blob/master/network_programming/tcpclipoll.c

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值