UNIX网络编程-Poll模型学习

1、相关接口介绍

1.1 poll

----------------------------------------------------------------------

#include <poll.h>

int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);

返回:准备好描述字的个数,0—超时,-1—出错。

----------------------------------------------------------------------

参数说明:

fdarray: 是一个pollfd结构类型的指针,指向一个链表,pollfd结构由三部分组成:文件描述符以及要检查的条件和检查后返回的结果;

nfds: 要检查的文件描述符的个数,也就是以上链表中元素的个数;

timeout: 超时时间,单位为毫秒,若为INFTIM则表示永远等待,若为0表示立即返回。

1.2 pollfd

pollfd为一个结构体:

1 struct pollfd
2 {
3     int fd; /* descriptor to check */
4     short events; /* events of interest on fd */
5     short revents; /* events that occurred on fd */
6 };

一下是events和revents可能的值:


常量


能作为events的输入吗?


能作为revents的结果吗?


解释


POLLIN


yes


yes


普通或优先级波段数据可读


POLLRDNORM


yes


yes


普通数据可读


POLLRDBAND


yes


yes


优先级波段数据可读


POLLPRI


yes


高优先级数据可读


POLLOUT


yes


yes


普通或优先级波段数据可写


POLLWRNORM


yes


yes


普通数据可写


POLLWRBAND


yes


yes


优先级波段数据可写


POLLERR


yes


发生错误


POLLHUP


yes


发生挂起


POLLNVAL


yes


描述字不是一个打开的文件

上图可分为三部分:四个处理输入的常量;三个处理输出的常量;三个处理错误的常量。

poll识别三个类别的数据:普通(normal)、优先级波段(priority band)、高优先级(high priority)。术语来自流的概念。

poll接口返回说明:

所有正规TCP数据和UDP数据都被认为是普通数据;

TCP的带外数据被认为是优先级带数据;

当TCP连接的读这一半关闭时(如接收了一个FIN),这也认为是普通数据,且后续的读操作将返回0;

TCP连接存在错误既可以认为是普通数据,也可以认为是错误(POLLERR)。无论哪种情况,后续的读操作将返回-1,并将errno置为适当的值,这就处理了诸如接收到RST或超时等条件;

在监听套接口上新连接的可用性既可认为是普通数据,也可以认为是优先级带数据,大多数实现都将其作为普通数据考虑。

如果不关心某个特定的描述字,可将其pollfd结构的fd成员置为一个负值,这样就可以忽略成员events,且返回时将成员revents的值置为0。

poll没有select存在的最大描述字数目问题。但可移植性select要好于poll。

2、poll的工作流程跟select差不多,这里直接跳过,来看相应的demo

  1 #include <stdio.h>
  2 #include <sys/socket.h>
  3 #include <netinet/in.h>
  4 #include <strings.h>
  5 #include <poll.h>
  6
  7 #define PORT 8080
  8 #define LISTENQ 5
  9 #define MAXLINE 1024
 10 #define OPEN_MAX 1024
 11
 12 #define IS_ERROR(condition)  13     if(condition)  14     {  15         printf("Error in func[%s] and line[%d]!\n",  16             __PRETTY_FUNCTION__, __LINE__);  17         return 0;  18     }
 19
 20 #ifndef INFTIM
 21 #define INFTIM (-1)
 22 #endif
 23
 24 int main(int argc, char *argv[])
 25 {
 26     struct sockaddr_in addrSer;
 27     struct sockaddr_in addrCli;
 28     int listenSock;
 29     int connSock;
 30     struct pollfd clientSock[OPEN_MAX];
 31
 32     int sumSock;     //sum of client sockets - 1
 33     int nCliLen;     //len of addrCli
 34     int nReady;      //the num of ready sockets
 35     char buf[MAXLINE];
 36     int nRet;
 37     int i;
 38
 39     /*create listen socket*/
 40     listenSock = socket(AF_INET, SOCK_STREAM, 0);
 41     IS_ERROR(listenSock == -1);
 42
 43     /*bind listen port*/
 44     bzero(&addrSer, sizeof(addrSer));
 45     addrSer.sin_family      = AF_INET;
 46     addrSer.sin_addr.s_addr = htonl(INADDR_ANY);
 47     addrSer.sin_port        = htons(PORT);
 48     nRet = bind(
 49         listenSock,
 50         (struct sockaddr *)&addrSer,
 51         sizeof(struct sockaddr_in)
 52     );
 53     IS_ERROR(nRet == -1);
 54
 55     /*listen port*/
 56     nRet = listen(listenSock, LISTENQ);
 57     IS_ERROR(nRet == -1);
 58
 59     /*init*/
 60     clientSock[0].fd = listenSock;
 61     clientSock[0].events = POLLRDNORM;
 62     for (i=1; i<OPEN_MAX; ++i)
 63     {
 64         clientSock[i].fd = -1;
 65     }
 66
 67     sumSock = 0;
 68
 69     /*request*/
 70     while (1)
 71     {
 72         nReady = poll(clientSock, sumSock+1, INFTIM);
 73
 74         /*accept*/
 75         if (clientSock[0].revents & POLLRDNORM)
 76         {
 77             nCliLen = sizeof(addrCli);
 78             connSock = accept(clientSock[0].fd, (struct sockaddr *)&addrCli, &nCliLen);
 79
 80             for (i=1; i<OPEN_MAX; ++i)
 81             {
 82                 if (clientSock[i].fd < 0)
 83                 {
 84                     clientSock[i].fd = connSock;
 85                     break;
 86                 }
 87             }
 88
 89             if (i == OPEN_MAX)
 90             {
 91                 printf("too many clients!\n");
 92                 return 0;
 93             }
 94
 95             clientSock[i].events = POLLRDNORM;
 96             sumSock = (sumSock < i) ? i : sumSock;
 97
 98             if (--nReady <= 0)
 99             {
100                 continue;
101             }
102         }
103
104         /*send and recv*/
105         for (i=1; i<=sumSock; ++i)
106         {
107             if (clientSock[i].fd < 0)
108             {
109                 continue;
110             }
111
112             if (clientSock[i].revents & (POLLRDNORM | POLLERR))
113             {
114                 nRet = recv(clientSock[i].fd, buf, MAXLINE, 0);
115
116                 if (nRet == 0 || nRet == -1)
117                 {
118                     printf("read sock %d err, nRet = %d!\n", clientSock[i], nRet);
119                     close(clientSock[i].fd);
120                     clientSock[i].fd = -1;
121                 }
122                 else if (-1 == send(clientSock[i].fd, buf, nRet, 0))
123                 {
124                     printf("write sock %d err!\n", clientSock[i]);
125                     close(clientSock[i].fd);
126                     clientSock[i].fd = -1;
127                 }
128
129                 if (--nReady <= 0)
130                 {
131                     break;
132                 }
133             } //if (clientSock[i].revents & (POLLRDNORM | POLLERR))
134         } //for (i=1; i<=sumSock; ++i)
135     } //while (1)
136
137     return 0;
138 }

3、poll和select对比

3.1 poll和select的优缺点

和select()不一样,poll()没有使用低效的三个基于位的文件描述符set,而是采用了一个单独的结构体pollfd数组,由一个指针指向这个组。

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程跟select一样,经历了多次无谓的遍历。

poll比select好的一点是,没有最大连接数限制,原因是它是基于链表来存储的,而select则会有最大描述符的限制。

3.2 poll和select相对于的点

每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码。内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。

events和revents两个域可设置的事件掩码在1.2中都有介绍。

POLLIN | POLLPRI等价于select()的读事件,POLLOUT | POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM | POLLRDBAND,而POLLOUT则等价于POLLWRNORM。

例如,要同时监视一个文件描述符是否可读和可写,我们可以设置events为POLLIN | POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。

时间: 2024-10-20 14:52:58

UNIX网络编程-Poll模型学习的相关文章

UNIX网络编程-Select模型学习

1.相关接口介绍 1.1 select ---------------------------------------------------------------------- #include <sys/select.h> #include <sys/time.h> int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeou

Unix网络编程-poll模型echo服务器

poll函数和select函数差不多.以下是一个简单的回显服务器 #include <iostream> using namespace std; #include <poll.h> #include <limits.h> #define OPEN_MAX 64 int main() { int i, maxi, listenfd, connfd, sockfd; int nready; ssize_t n; socklen_t clilen; struct pollf

UNIX网络编程——网络I/O模型

在学习UNIX网络编程的时候.一開始分不清 同步 和 异步,所以还是总结一下,理清下他们的差别比較好. IO分类 IO依据对IO的调度方式可分为堵塞IO.非堵塞IO.IO复用.信号驱动IO.异步IO. IO操作整个流程分为 可操作推断 和 实际IO操作 两个区间,我们能够称之为两个半程,前半程推断是否可操作,后半程进行实际操作. 当中堵塞IO.非堵塞IO.IO复用.信号驱动IO由于其[实际的IO操作是同步堵塞]的,所以一般把他们归为同步IO,异步IO的实际IO操作是在独立的线程中完毕的,所以称为

【unix网络编程第三版】阅读笔记(五):I/O复用:select和poll函数

本博文主要针对UNP一书中的第六章内容来聊聊I/O复用技术以及其在网络编程中的实现 1. I/O复用技术 I/O多路复用是指内核一旦发现进程指定的一个或者多个I/O条件准备就绪,它就通知该进程.I/O复用适用于以下场合: (1) 当客户处理多个描述符(一般是交互式输入或网络套接字),必须适用I/O复用 (2) 当一个客户处理多个套接字时,这种情况很少见,但也可能出现 (3) 当一个TCP服务器既要处理监听套接字,又要处理已连接套接字,一般就要使用I/O复用 (4) 如果一个服务器既要适用TCP,

Unix网络编程中的五种I/O模型_转

转自:Unix网络编程中的的五种I/O模型 下面主要是把unp第六章介绍的五种I/O模型. 1. 阻塞I/O模型 例如UDP函数recvfrom的内核到应用层.应用层到内核的调用过程是这样的:首先把描述符.接受数据缓冲地址.大小传递给内核,但是如果此时 该与该套接口相应的缓冲区没有数据,这个时候就recvfrom就会卡(阻塞)在这里,知道数据到来的时候,再把数据拷贝到应用层,也就是传进来的地址空 间,如果没有数据到来,就会使该函数阻塞在那里,这就叫做阻塞I/O模型,如下图: 2. 非阻塞I/O模

0730------Linux网络编程----------服务器端模型(迭代,多进程,多线程,select,poll,epoll 等)

1.迭代服务器模型 1.1 迭代服务器是处理多个请求时一种最简单直接的思路,即使用while循环,它不具有并发能力,即必须一个一个的处理客户的请求. 1.2 程序示例. #include "def.h" int listenfd_init(); //返回一个处于监听状态的套接字描述符 void do_service(int peerfd); // 处理客户端的请求 int main(int argc, const char *argv[]) { if(signal(SIGPIPE, S

【实习记】2014-08-25版本管理svn与git学习对比+看书UNIX网络编程

git也算中等熟练了,对其哲学也明白.但是svn一直半桶水. 上网搜索了几篇svn的好文,做一下总结: <svn分支开发与主干合并(branch & merge) >http://blog.csdn.net/bbirdsky/article/details/24620155 TortoiseSVN图形操作,适合初学者. <SVN中Branch的创建与合并>http://www.cnblogs.com/huang0925/p/3254243.html命令行的svn,适合真正要

Unix网络编程学习日记

今天开始拜读<Unix网络编程>.找到的源代码在Linux下有各种问题,最后决定还是自己从头写比较好. 从第一个时间服务程序开始学习.今天先看一下主要的头文件的作用. 在common.h中(参照 unp.h 自己写的,包含常用头文件和一些常量定义,用着方便),有以下的头文件: sys/types.h 此头文件是系统类型的定义,如:int8_t int16_t int32_t int64_t等等 sys/socket.h 这是socket的接口,在其中引入bits/socket.h,其中定义了各

《UNIX网络编程 卷1》之&quot;学习环境搭建&quot;(CentOS 7)

<UNIX网络编程 卷1>的源码可以从www.unpbook.com下载得到.解压之后的目录为unpv13e.  1. 编译 进入unpv13e目录,按如下步骤编译: 1 ./configure 2 3 cd lib 4 make // 可能遇到问题:redefinition of ‘struct in_pktinfo’ 5 6 cd ../libfree 7 make 8 9 cd ../libroute 10 make //这一步可能会出错,可忽略,只是表示你的系统不支持 4.4BSD,并