Linux多路IO复用:poll

1. poll

使用较少。

类似于select,但多了如下优点

        ① 无最大文件描述符数目的限制;

        ② 若select需要监听文件描述符x的读事件、文件描述符y的写事件、文件描述符z的异常事件,那么它就需要为读、写、异常事件分别设置文件描述符位图(总共3个),3个文件描述符位图在用户空间、内核空间来回拷贝次数更多,开销更大;

             而poll将使用pollfd结构体数组(见下面poll API),一个文件描述符对应一个pollfd结构体,可为一个文件描述符设置多种需要监听的事件(按位或),而无需像select那样每个事件都设置一个位图;这使得poll在需要监听多种事件时大大减少拷贝次数。

        缺点

        ① 仍需应用程序自己遍历整个pollfd数组;

        ② 仍需将pollfd数组在用户空间、内核空间来回拷贝。


2. poll API

#include<poll.h>

int poll(struct pollfd* fds, nfds_t nfds, int timeout);
/*
功能:
    监听多个文件描述符的属性(读、写、异常)变化。
参数:
    fds:监听的数组的地址;
    nfds:数组元素个数;
    timeout:
            > 0:监听超时时间(多久监听一次);
            0:无文件描述符变化则立即返回;
            -1:阻塞监听到有文件描述符变化才返回
返回值:
    > 0:变化的文件描述符的个数;
    = 0:没有文件描述符发生变化;
    -1 :调用发生错误,会设置errno。
*/

struct pollfd {
    int fd;        // 需要监听的文件描述符
    short events;  // 需要监听的事件
    short revents; // 返回监听到的事件
}
/*
参数 events、revents:
    POLLIN:读事件
    POLLPRI:写事件
*/

poll实现简单并发服务器示例:

#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
#include <netinet/in.h> 
#include <sys/time.h> 
#include <sys/ioctl.h> 
#include <unistd.h> 
#include <stdlib.h>
#include <poll.h>
#include <string.h>
#include <arpa/inet.h>

#define MAX_FD 8192
struct pollfd fds[MAX_FD];
int cur_max_fd = 1;

void setMaxFD(int fd) {
    if (fd >= cur_max_fd) {
        cur_max_fd = fd + 1;
    }
}

int main() {
    int server_sockfd, client_sockfd;
    int server_len, client_len;
    struct sockaddr_in server_address;
    struct sockaddr_in* client_address = NULL;
    int result;

    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(8888);
    server_len = sizeof(server_address);

    int opt = 1;
    setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 设置端口复用

    bind(server_sockfd, (struct sockaddr*)&server_address, server_len);
    listen(server_sockfd, 5); //监听队列最多容纳5个 

    fds[server_sockfd].fd = server_sockfd;
    fds[server_sockfd].events = POLLIN;
    fds[server_sockfd].revents = 0;
    setMaxFD(server_sockfd);
    while (1) {
        char ch;
        int i, fd;
        int nread;
        /*有事件发生  返回revents域不为0的文件描述符个数
         *超时:return 0
         *失败:return  -1   错误:errno
         */
        result = poll(fds, cur_max_fd, 2000);
        if (result < 0) {
            perror("server5");
            exit(1);
        }
        /*扫描所有的文件描述符*/
        for (i = 0; i < cur_max_fd; i++) {
            /*找到相关文件描述符 如果是该fd发生事件则revent返回事件类型*/
            if (fds[i].revents) {
                fd = fds[i].fd;
                /*判断是否为服务器套接字,是则表示为客户请求连接。*/
                if (fd == server_sockfd) {
                    client_address = malloc(sizeof(struct sockaddr_in));
                    client_len = sizeof(struct sockaddr_in);
                    //记录客户端的fd
                    client_sockfd = accept(server_sockfd,
                        (struct sockaddr*)client_address, &client_len);
                    //将客户端socket加入到集合中
                    fds[client_sockfd].fd = client_sockfd;
                    fds[client_sockfd].events = POLLIN;
                    fds[client_sockfd].revents = 0;
                    setMaxFD(client_sockfd);
                    char ip[16] = "";
                    printf("客户端%s到来,分配fd=%d\n",
                        inet_ntop(AF_INET, &(client_address->sin_addr.s_addr), ip, 16),
                        client_sockfd);
                    free(client_address);
                } else {
                    /*如果是触发客户端fd的读事件*/
                    if (fds[i].revents & POLLIN) {
                        nread = read(fd, &ch, 1);  //读一个字节,返回数据量
                        /*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 如果数据量为0表示已经请求完毕*/
                        if (nread == 0) {
                            char ip[16] = "";
                            close(fd);  //关闭套接字
                            //去掉关闭的fd,其实将event直接设置为0也可以
                            memset(&fds[i], 0, sizeof(struct pollfd));
                            printf("有客户端断开连接\n");
                        } else {  /*处理客户数据请求*/
                            sleep(5);
                            printf("处理客户端数据,fd = %d,receive:%c\n", fd, ch);
                            ch++;
                            fds[i].events = POLLOUT;
                        }
                    } else if (fds[i].revents & POLLOUT) {  //数据可写
                        write(fd, &ch, 1);
                        fds[i].events = POLLIN;  //继续监听读数据
                    }
                }
            }
        }
    }

    return 0;
}

运行结果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

伟大的马师兄

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

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

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

打赏作者

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

抵扣说明:

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

余额充值