I/O多路复用(select,poll)函数与回射服务器实现

I/O多路复用

在网络编程中,不得不提的一个概念就是I/O多路复用机制,采用I/O多路复用机制,使得内核一旦发现进程指定的一个或多个I/O条件就绪,就通知进程进行处理,能够同时处理多个描述符。
目前Linux上的I/O多路复用机制主要有三种select,poll,epoll,因为select与poll比较相似,因此这篇文章先讲select和poll函数。
接下来分别从函数、结构、注意点分别进行说明:
CSDN博客地址:https://blog.csdn.net/aa123248135/article/details/96729425

select函数

在这里插入图片描述

select的异步阻塞I/O模型

  • 应用进程向内核进行系统调用,由于内核中接受缓冲区没有可读内容,因此返回EAGEIN错误;
  • 应用进程阻塞与select调用,等到多个套接字中的任意一个变为可读;
  • 当内核的接收缓冲区中由数据读入时,内核向进程返回可读条件,应用程序再次进行系统调用,内核收到调用后,将内核接受缓冲区的数据复制到应用缓冲区
  • 内核向应用进程返回数据复制成功消息。

select的函数及结构

函数结构

int select(int maxfdp1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout);
返回:若有就绪描述符则为其数目,超时则为0,若出错则为-1

函数参数及含义:

1.maxfdp1参数指定待测试的描述符个数,它的值是带测试的最大描述符加1。(在中限定了select的最大描述符个数,一般为1024,poll和epoll可以说是没有文件描述符的限制)
2.readfds,writefds,exceptfds指定我们要让内核测试读、写、异常的文件描述符;(fd_set是select特有的的数据结构,可以简单的理解为按位标记具柄的队列),select在处理fd_set是常用的四个宏如下所示

FD_ZERO(int fd, fd_set* fds)             /*clear all bits in fdset*/
FD_SET(int fd, fd_set* fds)              /*turn on the bit for fd in fdset*/
FD_ISSET(int fd, fd_set* fds)            /*turn off the bit for fd in fdset*/ 
FD_CLR(int fd, fd_set* fds)              /*is the bit for fd in fdset?*/

3.timeout参数内核等待的时间,timeval的结构如下,这个参数可以有以下三种情况

 struct timeval{
    long tv_sec;         /*seconds*/
    long tv_usec;        /*microseconds*/
}
  • 永远等待(阻塞):当timeout为空指针时,仅当文件描述符准备好I/O才返回,否则将一直阻塞;
  • 等待固定时间:当timeout设置为具体的时间,且不为0,当文件描述符准备好I/O,且不超过设定的时间;
  • 不等待(非阻塞):timeout为0,检查文件描述符后理解返回,非阻塞轮询模式。

    select的tcp回射服务器程序

/**server端
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <poll.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define IPADDRESS   "127.0.0.1"
#define PORT        8787
#define MAXLINE     1024
#define LISTENQ     5
#define OPEN_MAX    1000
#define INFTIM      -1
//函数声明
//创建套接字并进行绑定
static int socket_bind(const char* ip,int port);

//IO多路复用select
static void do_select(int listenfd);

//处理多个连接
static void handle_connection(int* connfds, fd_set &allset, fd_set &rset, int num);

int main(int argc,char *argv[])
{
    int listenfd,connfd,sockfd;
    struct sockaddr_in cliaddr;
    socklen_t cliaddrlen;
    listenfd = socket_bind(IPADDRESS,PORT);
    listen(listenfd,LISTENQ);
    do_select(listenfd);
    return 0;
}

static int socket_bind(const char* ip,int port)
{
    int  listenfd;
    struct sockaddr_in servaddr;
    listenfd = socket(AF_INET,SOCK_STREAM,0);
    if (listenfd == -1){
        perror("socket error:");
        exit(1);
    }
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&servaddr.sin_addr);
    servaddr.sin_port = htons(port);
    if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1){
        perror("bind error: ");
        exit(1);
    }
    return listenfd;
}

static void do_select(int listenfd)
{
    int  connfd,sockfd;
    struct sockaddr_in cliaddr;
    socklen_t cliaddrlen;
    int maxfd, maxi, i, nready, client[FD_SETSIZE];
    fd_set rset, allset;
    //添加监听描述符
    //初始化客户连接描述符
    maxfd = listenfd;
    maxi = -1;
    for(int i=0;i<FD_SETSIZE;++i)
        client[i] = -1;
    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);
    //循环处理
    for ( ; ; )
    {
        rset = allset;
        //获取可用描述符的个数
        nready = select(maxfd+1, &rset, NULL, NULL, NULL);
        if (nready == -1){
            perror("select error:");
            exit(1);
        }
        //测试监听描述符是否准备好
        if (FD_ISSET(listenfd, &rset)){
            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);
            FD_SET(connfd, &allset);
            if(connfd > maxfd)
                maxfd = connfd;
            //将新的连接描述符添加到数组中
            //这个是找到最小的一个没有用的文件描述符
            for (i = 1;i < FD_SETSIZE;i++){
                if (client[i] < 0){
                    client[i] = connfd;
                    break;
                }
            }
            if (i == FD_SETSIZE){
                fprintf(stderr,"too many clients.\n");
                exit(1);
            }
            //记录客户连接套接字的个数
            maxi = (i > maxi ? i : maxi);
            if (--nready <= 0)
                continue;
        }
        //处理客户连接
        handle_connection(client, allset, rset, maxi);
    }
}

static void handle_connection(int* connfds,fd_set &allset ,fd_set &rset, int num) {
    int i, n;
    char buf[MAXLINE];
    memset(buf, 0, MAXLINE);
    for (i = 1; i <= num; i++) {
        if (connfds[i] < 0)
            continue;
        //测试客户描述符是否准备好
        if (FD_ISSET(connfds[i], &rset)) {
            //接收客户端发送的信息
            n = read(connfds[i], buf, MAXLINE);
            /*n == 0 就是说明子机与母机断开连接了,子机在那边不输入,直接敲回车,这边也是可以收到回车符的*/
            if (n == 0) {
                close(connfds[i]);
                FD_CLR(connfds[i], &allset);
                char buf2[20] = {"client closed"};
                write(STDOUT_FILENO, buf2, strlen(buf2));
                continue;
            }
            // printf("read msg is: ");
            write(STDOUT_FILENO, buf, n);
            //向客户端发送buf
            write(connfds[i], buf, n);
        }
    }
}
/**client端
*/
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include<arpa/inet.h>

#define MAXLINE     1024
#define IPADDRESS   "127.0.0.1"
#define SERV_PORT   8787

#define max(a,b) (a > b) ? a : b

static void handle_connection(int sockfd);

int main(int argc,char *argv[])
{
    int                 sockfd;
    struct sockaddr_in  servaddr;
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);
    connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    //处理连接描述符
    handle_connection(sockfd);
    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;
    for (; ;){
        poll(pfds,2,-1);
        if (pfds[0].revents & POLLIN){
            n = read(sockfd,recvline,MAXLINE);
            if (n == 0){
                fprintf(stderr,"client: server is closed.\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函数的异步阻塞I/O模型与select的类似。但是poll与select不同的是,poll 不是为每个条件构造一个描述符集,而是构造一个 pollfd 结构体数组,每个数组元素指定一个描述符标号及其所关心的条件。

poll的函数及结构

函数结构

#include 

/*  poll函数
 *  fds指向监控pollfd的首指针
 *  nfds是poll监听的最大文件描述符的个数,使用时传入最大文件描述符+1
 *  timeout设置超时模式
 *  返回值:0表示没有套接字发生变化  n(n>0)表示有n个套接字有变化  -1有错误
 */
int poll(struct pollfd* fds, unsigned long nfds, int timeout);
返回:若有就绪描述符则为其数目,超时则为0,若出错则为-1

函数参数及含义:

1.fds参数是指向一个pollfd结构的第一个元素的指针。其中每个元素都为polled结构,用于定于及返回poll感兴趣的文件描述符。

struct pollfd{
    int fd;           /*descriptor to check*/
    short events;     /*events of interest on fd*/
    short revents;    /*events that occurred on fd*/
};

其中events和reverts标志是系统定义的一些常值,常用的如下:

2.nfds参数指定pllfd中参数的个数;
3.timeout指定poll等待的时间,为一个毫秒级的正数;

poll的tcp回射服务器程序

/* server端
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <poll.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define IPADDRESS   "127.0.0.1"
#define PORT        8787
#define MAXLINE     1024
#define LISTENQ     5
#define OPEN_MAX    1000
#define INFTIM      -1

//函数声明
//创建套接字并进行绑定
static int socket_bind(const char* ip,int port);
//IO多路复用poll
static void do_poll(int listenfd);
//处理多个连接
static void handle_connection(struct pollfd *connfds, int num, int nready);

int main(int argc,char *argv[])
{
    int listenfd,connfd,sockfd;
    struct sockaddr_in cliaddr;
    socklen_t cliaddrlen;
    listenfd = socket_bind(IPADDRESS,PORT);
    listen(listenfd,LISTENQ);
    do_poll(listenfd);
    return 0;
}

static int socket_bind(const char* ip,int port)
{
    int  listenfd;
    struct sockaddr_in servaddr;
    listenfd = socket(AF_INET,SOCK_STREAM,0);
    if (listenfd == -1){
        perror("socket error:");
        exit(1);
    }
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&servaddr.sin_addr);
    servaddr.sin_port = htons(port);
    if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1){
        perror("bind error: ");
        exit(1);
    }
    return listenfd;
}

static void do_poll(int listenfd)
{
    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;
    //循环处理
    for ( ; ; ){
        //获取可用描述符的个数
        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;
        }
        //处理客户连接
        handle_connection(clientfds, maxi, nready);
    }
}

static void handle_connection(struct pollfd *connfds, int num, int nready)
{
    int i,n;
    char buf[MAXLINE];
    memset(buf,0,MAXLINE);
    for (i = 1;i <= num;i++){
        if (connfds[i].fd < 0)
            continue;
        //测试客户描述符是否准备好
        if (connfds[i].revents & POLLIN){
            //接收客户端发送的信息
            n = read(connfds[i].fd,buf,MAXLINE);
            /*n == 0 就是说明子机与母机断开连接了,子机在那边不输入,直接敲回车,这边也是可以收到回车符的*/
            if (n == 0){
                close(connfds[i].fd);
                connfds[i].fd = -1;
                char buf2[1024] = {"client closed"};
                write(STDOUT_FILENO, buf2, strlen(buf2));
                continue;
            }
            // printf("read msg is: ");
            write(STDOUT_FILENO,buf,n);
            //向客户端发送buf
            write(connfds[i].fd,buf,n);
            if(--nready <= 0)
                break;
        }
    }
}
/**client端
*/
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include<arpa/inet.h>

#define MAXLINE     1024
#define IPADDRESS   "127.0.0.1"
#define SERV_PORT   8787

#define max(a,b) (a > b) ? a : b

static void handle_connection(int sockfd);

int main(int argc,char *argv[])
{
    int                 sockfd;
    struct sockaddr_in  servaddr;
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);
    connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    //处理连接描述符
    handle_connection(sockfd);
    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;
    for (; ;){
        poll(pfds,2,-1);
        if (pfds[0].revents & POLLIN){
            n = read(sockfd,recvline,MAXLINE);
            if (n == 0){
                fprintf(stderr,"client: server is closed.\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);
        }
    }
}
#C/C++##笔记#
全部评论

相关推荐

一颗宏心:华为HR晚上过了十二点后还给我法消息。
点赞 评论 收藏
分享
11-14 16:13
已编辑
重庆科技大学 测试工程师
Amazarashi66:不进帖子我都知道🐮❤️网什么含金量
点赞 评论 收藏
分享
点赞 49 评论
分享
牛客网
牛客企业服务