二、poll系统调用
2.1、API
poll系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。
#include <poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
fds参数是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件。pollfd结构体的定义如下:
struct pollfd {
int fd;/*文件描述符*/
short events;/*注册的事件*/
short revents;/*实际发生的事件,由内核填充*/
};
fd
成员指定文件描述符;
events
成员告诉poll监听fd上的哪些事件,它是一系列事件的按位或;
revents
成员则由内核修改,以通知应用程序fd上实际发生了哪些事件。
poll支持的事件类型如下
- POLLIN 数据(包括普通数据和优先数据)可读,可以作为输入,可以作为输出
- POLLRDNORM 普通数据可读,可以作为输入,可以作为输出
- POLLRDBAND 优先级带数据可读(Linux不支持),可以作为输入,可以作为输出
- POLLPRI 高优先级数据可读,比如TCP外带数据,可以作为输入,可以作为输出
- POLLOUT 数据(包括普通数据和优先数据)可写,可以作为输入,可以作为输出
- POLLWRNORM 普通数据可写,可以作为输入,可以作为输出
- POLLWRBAND 优先级带数据可写,可以作为输入,可以作为输出
- POLLWRDHUP TCP连接被对方关闭,或者对方关闭了写操作,可以作为输入,可以作为输出
- POLLERR 错误,不可以作为输入,可以作为输出
- POLLHUP 挂起,比如管道的写端被关闭,读端描述符上将受到POLLHUP事件,不可以作为输入,可以作为输出
- POLLNVAL 文件描述符没有打开,不可以作为输入,可以作为输出
POLLRDNORM
、POLLRDBAND
、POLLWRNORM
、POLLWRBAND
由XOPEN规范定义。它们实际上是将POLLIN事件和POLLOUT事件分得更细致,以区别对待普通数据和优先数据。但Linux并不完全支持它们。
通常,应用程序需要根据recv
调用的返回值来区分socket
上接收到的是有效数据还是对方关闭连接的请求,并做相应的处理。不过,GNU为poll系统调用增加了一个POLLRDHUP事件,它在socket上接收到对方关闭连接的请求之后触发。这为我们区分上述两种情况提供了一种更简单的方式。但使用POLLRDHUP事件时,我们需要在代码最开始处定义_GNU_SOURCE
。
nfds
参数指定被监听事件集合fds的大小。其类型nfds_t的定义如下:
typedef unsigned long int nfds_t;
timeout
参数指定poll的超时值,单位是毫秒。当timeout为-1时,poll调用将永远阻塞,直到某个事件发生;当timeout为0时,poll调用将立即返回。
poll系统调用的返回值,成功时返回就绪(可读、可写和异常)文件描述符的总数,如果在超时时间内没有任何文件描述符就绪,select将返回0。select失败时返回-1并设置errno。
2.2、仿真
服务端:
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#define PORT 8080
#define BUFFER_SIZE 8192
int main(int argc,char* argv[]) {
char buf[BUFFER_SIZE];
ssize_t bytes_read;
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(struct sockaddr_in);
// 创建socket
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 命名socket
memset(&server_addr, 0, addr_len);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_socket, (struct sockaddr *) &server_addr, addr_len) == -1) {
perror("bind failed");
close(server_socket);
exit(EXIT_FAILURE);
}
// 监听socket
if (listen(server_socket, 5) == -1) {
perror("listen failed");
close(server_socket);
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d...\n", PORT);
// 连接
client_socket = accept(server_socket, (struct sockaddr*) &client_addr,&addr_len);
if (client_socket<0) {
printf("errno is:%d\n",errno);
close(server_socket);
}
struct pollfd fds[1];
fds[0].fd = client_socket;
fds[0].events = POLLIN;
while(1) {
// 初始化buf
memset(buf,'\0',sizeof(buf));
int ret = poll(fds, 1, -1);
if (ret == -1) {
perror("Poll failed");
exit(EXIT_FAILURE);
}
// 可读事件
if (fds[0].revents & POLLIN) {
ret = recv(client_socket,buf,sizeof(buf)-1,0);
if(ret<=0) break;
printf("get %d bytes of normal data:%s",ret,buf);
}
// 外带数据
if (fds[0].revents & POLLPRI) {
ret = recv(client_socket,buf,sizeof(buf)-1,MSG_OOB);
if(ret<=0) break;
printf("get %d bytes of oob data:%s",ret,buf);
}
}
close(server_socket);
close(client_socket);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024
int main() {
int client_socket;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
// 创建socket
client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址信息
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
perror("invalid server address");
close(client_socket);
exit(EXIT_FAILURE);
}
// 连接到服务器
if (connect(client_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) == -1) {
perror("connection failed");
close(client_socket);
exit(EXIT_FAILURE);
}
printf("Connected to server\n");
// 发送消息给服务器
const char *message1 = "Hello, this is client!\n";
send(client_socket, message1, strlen(message1), 0);
sleep(1);
// 发送带外数据
const char *message2 = "Hello, this is client and data is oob!\n";
send(client_socket, message2, strlen(message2), MSG_OOB);
// 关闭连接
close(client_socket);
}
仿真: