多路I/O复用:select、poll、epoll(二)

多路IO复用模型之poll

多路IO复用模型之select
多路IO复用模型之epoll

poll和select一样,都可用于执行多路复用IO。

poll函数原型

int poll(struct pollfd * fds,
		unsigned int nfds,
		int timeout);

这里还是用一张图来说明各个参数的作用

在这里插入图片描述
当poll()返回-1时,errno的值为下列中的一个:

  • EBADF:一个或多个结构体中指定的文件描述符无效;
  • EFAULTfds:指针指向的地址超出进程的地址空间;
  • EINTR:请求的事件之前产生一个信号,调用可以重新发起;
  • EINVALnfds:参数超出PLIMIT_NOFILE值;
  • ENOMEM:可用内存不足,无法完成请求。

poll实战

server.cpp

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#define PORT 6666
#define MAXLINE 1024
#define LISTENQ 5
#define OPEN_MAX 1000
#define INFTIM -1

int bind_and_listen(){
    
     //创建套接字,进行绑定和监听
    int serverfd; //监听socket:serverfd
    struct sockaddr_in my_addr; //本机的ip地址信息
    if ((serverfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    
    
        perror("socket");
        return -1;
    }
    printf("socket ok \n");
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(PORT);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(my_addr.sin_zero), 0);
    if (bind(serverfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == -1) {
    
    
        perror("bind");
        return -2;
    }
    printf("bind ok \n");
    if (listen(serverfd, LISTENQ) == -1) {
    
    
        perror("listen");
        return -3;
    }
    printf("listen ok \n");
    return serverfd;
}

void do_poll(int listenfd){
    
      //IO多路复用POLL
    int connfd,sockfd;
    struct sockaddr_in cliaddr;
    socklen_t cliaddrlen;
    struct pollfd clientfds[OPEN_MAX];
    int maxi;
    int i;
    int nready;
    clientfds[0].fd = listenfd; //添加监听描述符
    clientfds[0].events = POLLIN;
    for (i = 1; i < OPEN_MAX; i++) {
    
    
        clientfds[i].fd = -1;  //初始化客户连接描述符
    }
    maxi = 0;
    while (1) {
    
    
        nready = poll(clientfds, maxi+1, INFTIM); // 获取可用描述符的个数
        if (nready == -1) {
    
    
            perror("poll error :");
            exit(1);
        }
        if (clientfds[0].revents & POLLIN) {
    
    
            cliaddrlen = sizeof(cliaddr);
            if ((connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddrlen)) == -1) {
    
    
                if (errno == EINTR) {
    
    
                    continue;
                }
                else{
    
    
                    perror("accept error:");
                    exit(1);
                }
            }
            fprintf(stdout, "accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
            for (i = 1; i < OPEN_MAX; i++) {
    
       //将新的连接描述符加入到数组中
                if (clientfds[i].fd < 0) {
    
    
                    clientfds[i].fd = connfd;
                    break;
                }
            }
            if (i == OPEN_MAX) {
    
    
                fprintf(stderr, "too many clients. \n");
                exit(1);
            }
            clientfds[i].events = POLLIN; //将新的描述符添加到读描述符的集合中
            maxi = (i > maxi ? i : maxi); //记录连接套接字的个数
            if (--nready <= 0) {
    
    
                continue;
            }
        }
        char buf[MAXLINE]; //处理多个连接上客户端发来的包
        memset(buf, 0, MAXLINE);
        int readlen = 0;
        for (i = 1; i <= maxi; i++) {
    
    
            if (clientfds[i].fd < 0) {
    
    
                continue;
            }
            if (clientfds[i].revents & POLLIN) {
    
    
                readlen = read(clientfds[i].fd, buf, MAXLINE);  //接收客户端发送的信息
                if (readlen == 0) {
    
    
                    close(clientfds[i].fd);
                    clientfds[i].fd = -1;
                    continue;
                }
                write(STDOUT_FILENO, buf, readlen);
               // write(clientfds[i].fd, buf, readlen);  //向客户端发送buf
            }
        }
    }
}

int main(){
    
    
    int listenfd = bind_and_listen();
    if (listenfd < 0) {
    
    
        return 0;
    }
    do_poll(listenfd);
    return 0;
}

client.cpp

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <strings.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#define DEFAULT_PORT 6666
#define MAXLINE 1024
#define max(a,b) (a > b) ? a : b

static void handle_connection(int sockfd);

int main(){
    
    
    int connfd = 0;
    int cLen = 0;
    struct sockaddr_in client;
    client.sin_family = AF_INET;
    client.sin_port = htons(DEFAULT_PORT);
    client.sin_addr.s_addr = inet_addr("172.22.0.191"); //这里填服务器的ip地址
    connfd = socket(AF_INET,SOCK_STREAM, 0);
    if (connfd < 0) {
    
    
        perror("socket");
        return -1;
    }
    if (connect(connfd, (struct sockaddr*)&client, sizeof(client)) < 0) {
    
    
        perror("connect");
        return -1;
    }
    handle_connection(connfd); //处理连接描述符
    return 0;
}

static void handle_connection(int sockfd){
    
    
    char sendline[MAXLINE],recvline[MAXLINE];
    int maxfdp,stdineof;
    struct pollfd pfds[2];
    int n;
    pfds[0].fd = sockfd; //添加连接描述符
    pfds[0].events = POLLIN;
    pfds[1].fd = STDIN_FILENO; //添加标准输入描述符
    pfds[1].events = POLLIN;
    while (1) {
    
    
        poll(pfds, 2, -1);
        if (pfds[0].revents & POLLIN) {
    
    
            n = read(sockfd, recvline, MAXLINE);
            if (n == 0) {
    
    
                fprintf(stderr, "Client: server is colosed . \n");
                close(sockfd);
            }
            write(STDOUT_FILENO, recvline, n);
        }
        if (pfds[1].revents & POLLIN) {
    
    
            n = read(STDIN_FILENO, sendline, MAXLINE);
            if (n == 0) {
    
    
                shutdown(sockfd, SHUT_WR);
                continue;
            }
            write(sockfd, sendline, n);
        }
    }
}

可以看出,poll也可以让服务器具备同时处理多个客户端请求的能力。

总结

  • poll()和select()相比,它没有了最大连接数的限制(select在32位linux系统中最大连接数为1024个),可以创建特定大小的数组来保存监控的描述符,且不受文件描述符值大小的影响;

  • poll()不要求在计算最大文件描述符时+1的操作;

  • poll()在应付大数目的文件描述符的时候更快,因为select()需要内核检查大量的描述符对应的fd_set中的每一个比特位,比较费时;

  • select()对所监控的fd_set在函数返回后会发生变化,在下次进入select()的时候需要重新初始化要监控的fd_set,poll()函数将监控的输入和输出事件分开,允许被监控的文件数组被复用而不需要重新初始化;

  • select()的可移植性优于poll(),在某些unix系统上不支持poll();

  • select()对于超时值提供了更好的精度。

猜你喜欢

转载自blog.csdn.net/zxxkkkk/article/details/111058629