Linux网络编程(三)IO复用二 poll系统调用

二、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 文件描述符没有打开,不可以作为输入,可以作为输出

POLLRDNORMPOLLRDBANDPOLLWRNORMPOLLWRBAND由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);

}

仿真:

请添加图片描述

  • 21
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LyaJpunov

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值