二、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 8192int 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) {// 初始化bufmemset(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);}
仿真: