Poll
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。
这个过程经历了多次无谓的遍历。
它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:
1、大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
2、poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。
poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。
调用形式
api
1
2
3#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
参数说明
fds:存放需要被检测状态的Socket描述符;与select不同(select函数在调用之后,会清空检测socket描述符的数组),每当调用这个函数之后,系统不会清空这个数组,而是将有状态变化的描述符结构的revents变量状态变化,操作起来比较方便;
nfds:用于标记数组fds中的struct pollfd结构元素的总数量;
timeout:poll函数调用阻塞的时间,单位是MS(毫秒)
struct pollfd结构
【在源码文件poll.h文件中】
1
2
3
4
5struct pollfd {
int fd; /* poll 的文件描述符. */
short events; /* fd 上感兴趣的事件(等待的事件).*/
short revents; /* fd 上实际发生的事件. */
};
这个结构中
fd表示文件描述符,
events表示请求检测的事件位掩码,
1
2
3
4
5
6
7
8
9
10
11常量 说明
POLLIN 普通或优先级带数据可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级数据可读
POLLOUT 普通数据可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件
注意:后三个只能作为描述字的返回结果存储在revents中,而不能作为测试条件用于events中。
revents表示检测之后返回的事件位掩码,如果当某个文件描述符有状态变化时,revents的值就不为空。
返回值
大于0:表示数组fds中有socket描述符的状态发生变化,或可以读取、或可以写入、或出错。
并且返回的值表示这些状态有变化的socket描述符的总数量;此时可以对fds数组进行遍历,以寻找那些revents不空的socket描述符,然后判断这个里面有哪些事件以读取数据。
等于0:表示没有socket描述符有状态变化,并且调用超时。
小于0:此时表示有错误发生,此时全局变量errno保存错误码。
源码
服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/time.h>
#include <arpa/inet.h>
#include <poll.h>
#define backlog 5
#define BUFFSIZE 1024
int create_listenfd(const char *ip, unsigned short port)
{
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
if(-1 == listenfd)
{
perror("socket");
return -1;
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = (ip == NULL ? INADDR_ANY :inet_addr(ip));
if(-1 == bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)))
{
perror("bind");
close(listenfd);
return -1;
}
printf("bind success!\n");
if(-1 == listen(listenfd, backlog))
{
perror("listen");
close(listenfd);
return -1;
}
printf("listening ...\n");
return listenfd;
}
int main(int argc, char *argv[])
{
int listenfd = create_listenfd(NULL,atoi(argv[2]));
if(-1 == listenfd)
{
return 0;
}
char buff[BUFFSIZE];
int recvbytes, sendbytes;
struct pollfd eventfds[BUFFSIZE];
memset(eventfds, 0, sizeof(eventfds));
eventfds[0].fd = STDIN_FILENO;
eventfds[0].events = POLLIN;
eventfds[1].fd = listenfd;
eventfds[1].events = POLLIN;
while(1)
{
int ret = poll(eventfds, BUFFSIZE, 3*1000);
if(-1 == ret)
{
perror("poll");
break;
}
else if(0 == ret)
{
printf("timeout...\n");
}
else
{
for(int i = 0; i <= BUFFSIZE; i++)
{
int fd = eventfds[i].fd;
if(eventfds[i].revents & POLLIN)
{
if(fd == STDIN_FILENO)
{
fgets(buff, sizeof(buff), stdin);
printf("gets:%s",buff);
}
else if(fd == listenfd)
{
int connectfd = accept(listenfd, NULL, NULL);
if(-1 == connectfd)
{
perror("accept");
close(listenfd);
return 0;
}
printf("A new client(fd=%d) is connect success!\n", connectfd);
eventfds[connectfd - listenfd + 1].fd = connectfd;
eventfds[connectfd - listenfd + 1].events = POLLIN;
}
else
{
recvbytes = recv(fd, buff, sizeof(buff), 0);
if(recvbytes < 0)
{
perror("recv");
eventfds[i].events = 0;
eventfds[i].fd = -1;
close(fd);
break;
}
if(recvbytes == 0)
{
printf("client(fd=%d) is closed!\n", fd);
eventfds[i].events = 0;
eventfds[i].fd = -1;
close(fd);
break;
}
printf("server recv from client(fd=%d):%s", fd, buff);
}
}
}
}
}
close(listenfd);
return 0;
}
客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#define backlog 5
#define BUFFSIZE 1024
int main(int argc, char *argv[])
{
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
exit(EXIT_FAILURE);
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(atoi(argv[2]));
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
if(-1 == connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))
{
perror("connect");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("connect success!\n");
char buff[BUFFSIZE];
int recvbytes, sendbytes;
while(1)
{
fgets(buff, sizeof(buff), stdin);
if(0 == strncmp(buff, "quit", 4))
{
printf("client quit!\n");
break;
}
sendbytes = send(sockfd, buff, strlen(buff)+1, 0);
if(sendbytes <= 0)
{
perror("send");
break;
}
}
close(sockfd);
return 0;
}
运行
1
2
3./server 127.0.0.1 8888
./client 127.0.0.1 8888
./client 127.0.0.1 8888
个人收获
-
技术是和语言无关的,最底层的原理,还是 c 语言更加强大。
-
要知道原理首先知道大概的方式,然后通过阅读代码的方式,而不是直接阅读源码。
-
这些内容在《linux 网络编程》中肯定都是有的,内容大同小异。