IO多路复用

目录

学习内容:

[1. 非阻塞型IO](#1. 非阻塞型IO)

[1.1 fcntl函数](#1.1 fcntl函数)

[2 多路文件IO](#2 多路文件IO)

[2.1 多路文件IO的工作原理](#2.1 多路文件IO的工作原理)

[2.2 select 模型](#2.2 select 模型)

[2.3 select多并发服务器](#2.3 select多并发服务器)

[2.4 select代码模型](#2.4 select代码模型)

[2.5 poll模型](#2.5 poll模型)

[2.6 poll的代码模型](#2.6 poll的代码模型)

课外作业:

ser.c

cli.c


学习内容:

1. 非阻塞型IO

1.1 fcntl函数

原型:int fcntl(int fd, int cmd, ... /* arg */ );

调用:int flag = fcntl(描述符,F_GETFL)

fcntl(描述符,F_SETFL,flag)

功能描述:设置或者获取文件的各项属性,到底如何操作由cmd决定,一般我们都会用来设置阻塞或者非阻塞IO

参数解析:

参数 fd:准备设置属性的文件的描述符

参数 cmd:文件到底设置什么属性又cmd决定

参数 ...:

F_SETFL:设置文件的flag属性

F_GETFL:获取当前文件的flag属性

2 多路文件IO

2.1 多路文件IO的工作原理

内核会监视目标套接字的缓存区变化:如果

① 缓存区发生了改变:边缘触发

② 缓存区存在数据:水平触发

内核就会通知监视者,有描述符是可读的

以下3个模型,都是上述工作方式

2.2 select 模型

原型:int select(int nfds, fd_set *readfds, fd_set *writefds,

fd_set *exceptfds, struct timeval *timeout);

调用:select(FD_SETSIZE,描述符集合,0,0,0)

FD_SETSIZE:值为1024

功能描述:以阻塞的形式监视 readfds,writefds,exceptfds 这3个描述符集合中,所有描述符,如果有任何描述符激活,则select解除阻塞

参数解析:

参数 nfds:readfds,writefds,exceptfds 这3个集合中的最大值

参数 readfds:监视描述符集合中任意的描述符是否可读,一般我们只用这个

参数 writefds:监视描述符集合中任意的描述符是否可写,一般写NULL

参数 exceptfds:监视描述符集合中任意的描述符是否发生意外,一般写NULL

注意:只要select监视到了有描述符激活,就会将激活的描述符,以覆盖的形式写入到上述3个fds里面去

参数 timeout:是一个结构体,结构如下

struct timeval {

long tv_sec; /* seconds */

long tv_usec; /* microseconds */

};

表示select函数只阻塞传入的时间长度的秒数,超过这个时间自动解除阻塞

传NULL表示:一直阻塞,不受时间影响

返回值:返回激活的描述符的数量

void FD_CLR(int fd, fd_set *set);

功能描述: 从 set 中删除描述符 fd

int FD_ISSET(int fd, fd_set *set);

功能描述:判断 set 中是否存在描述符 fd

返回值:如果存在返回1,不存在返回0

void FD_SET(int fd, fd_set *set);

功能描述:将描述符 fd 添加到 set 里面去

void FD_ZERO(fd_set *set);

功能描述:清空 set 所有描述符,相当于初始化的功能

#include<myhead.h>
int main(int argc, char const *argv[])
{
    if(access("./myfifo",F_OK) == -1)
    {
        mkfifo("./myfifo",0666);
    }
    int rp = open("./myfifo",O_RDONLY);
    fd_set readfds,tempfds;
    FD_ZERO(&readfds);

    FD_SET(0,&readfds);
    FD_SET(rp,&readfds);
    while (1)
    {
        tempfds = readfds;
        select(FD_SETSIZE,&tempfds,NULL,NULL,NULL);
        if(FD_ISSET(0,&tempfds) == 1)
        {
            printf("标准输入流激活;\n");
            char buf[128]="";
            scanf("%s",buf);
            getchar();
            printf("读取数据为%s\n",buf);
        }
        if(FD_ISSET(rp,&tempfds) == 1)
        {
            printf("读端激活;\n");
            char buf[128]="";
            bzero(buf, sizeof(buf));
            read(rp,buf,sizeof(buf));
            printf("数据为%s\n",buf);
        }
    }
    
    return 0;
}

2.3 select多并发服务器

#include<myhead.h>
#define SER_PORT 8888           //服务器端口号
#define SER_IP "192.168.2.157"  //服务器IP
void insert(int * arr,int *len ,int cin)
{
    arr[*len] = cin;
    (*len)++;
}
int find_arr(int * arr,int len ,int cin)
{
    for (int i = 0; i < len; i++)
    {
        if(arr[i] == cin){
            return i;
        }
    }
    return -1;
}
void remove_arr(int * arr,int* len ,int cin)
{
    int tar = find_arr(arr,*len,cin);
    if(tar == -1)
    {
        return ;
    }
    int i =-1;
    for (int i = tar; i < *len-1; i++)
    {
        arr[i] = arr[i+1];
    }
    arr[i] = 0;
    (*len)--;
}
int main(int argc, char const *argv[])
{
    int sfd = socket(AF_INET,SOCK_STREAM,0);
    if(sfd == -1)
    {
        perror("socket error");
        return -1;
    }
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);       //端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP);      //IP地址
    if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin)) == -1)
    {
        perror("bind error");
        return -1;
    }
    if(listen(sfd,128) == -1)
    {
        perror("listen error");
        return -1;
    }

    fd_set readfds,tempfds;
    int cin = 0;
    int cin_arr[128]={0};
    int count = 0;
    FD_ZERO(&readfds);

    FD_SET(0,&readfds);
    FD_SET(sfd,&readfds);
    char buf[128]="";
    while (1)
    {
        tempfds = readfds;
        select(FD_SETSIZE,&tempfds,0,0,0);
        // if(FD_ISSET(0,&tempfds) == 1)
        // {
        //     fgets(buf,sizeof(buf),stdin);
        //     buf[strlen(buf) - 1]=0;
        //     printf("触发了键盘输入事件:%s\n", buf);
        // }
        if(FD_ISSET(sfd,&tempfds) == 1)
        {
            if((cin = accept(sfd,0,0)) == -1)
            {
                perror("error");
                return -1;
            }
            printf("连接成功\n");
            FD_SET(cin,&readfds);
            insert(cin_arr,&count,cin);
        }
        for (int i = 0; i < count; i++)
        {
            int cin = cin_arr[i];
            if(FD_ISSET(cin,&tempfds))
            {
                char sbuf[128] = "";
                int res = read(cin,sbuf,128);
                if(res == 0)
                {
                    printf("断开连接");
                    close(i);
                    FD_CLR(cin,&readfds);
                    remove_arr(cin_arr,&count,cin);
                    break;
                }
                printf("客户端发来信息:%s\n",sbuf);
            }
        }
    }
    close(sfd);
    return 0;
}

2.4 select代码模型

fd_set readfds;

FD_ZERO(readfds)

FD_SET(想要监视的描述符,readfds)

while(1){

select()

判断/循环判断哪个描述符激活了{

调用对用的阻塞函数

例如:accept 或者 read

}

}

2.5 poll模型

工作方式和select是一样的,都是用来监视描述符是否激活,只不过操作过程不一样

因为select的不足,才会有poll模型的

① select中 fd_set 类型大小固定,1024个,如果要监视的描述符数量超过1024个,就会有问题

② select监视到激活的描述符成功后,会将激活的把readfds覆盖

poll 解决了上述2个问题

原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);

调用:poll(准备监视的struct pollfd 数组,数组的容量/实际长度,-1)

功能描述:监视 fds所指向的描述符数组中所有描述符的情况,最多监视nfds个,一般就是数组的容量

参数解析:

参数 fds:结构体数组,数组中的每一个结构体元素都是一个描述符搭配一些其他数据,结构如下

struct pollfd {

int fd; /* file descriptor */要监视的描述符

short events; /* requested events */可读、可写、意外

因为可读的原因激活:POLLIN,一般我们都写这个

因为可写的原因激活:POLLOUT

short revents; /* returned events */

fd描述符,一旦激活,用来表示具体因为什么原因激活的

};

参数 nfds:想要监视的描述符的数量,一般就是fds这个数组的实际长度

参数 timeout:poll函数阻塞时长,单位为毫秒

传 0 表示不阻塞

传 -1 表示一直阻塞,直到有描述符激活

返回值:成功返回激活的描述符的数量

注意:poll函数监视的直接是一个结构体数组,这个数组我们是可以直接操作的,不需要额外的函数去操作

注意:poll的激活方式为水平触发

水平触发:只要监视的所有套接字中,有任何套接字缓存区存在数据,则poll就会激活

#include<myhead.h>
#define SER_PORT 8888           //服务器端口号
#define SER_IP "192.168.2.157"  //服务器IP
int main(int argc, char const *argv[])
{
     int sfd = socket(AF_INET,SOCK_STREAM,0);
    if(sfd == -1)
    {
        perror("socket error");
        return -1;
    }
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);       //端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP);      //IP地址
    if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin)) == -1)
    {
        perror("bind error");
        return -1;
    }
    if(listen(sfd,128) == -1)
    {
        perror("listen error");
        return -1;
    }
    struct pollfd fds[50]={0};
    fds[0].fd = sfd;
    fds[0].events = POLLIN;

    int fd_count = 1;
    while (1)
    {
        poll(fds,fd_count,-1);
        for (int i = 0; i < fd_count; i++)
        {
            int fd = fds[i].fd;
            short revent = fds[i].revents;
            if(fd == sfd && revent == POLLIN)
            {
                
                    int cin = accept(sfd,0,0);
                    printf("有新客户连接\n");
                    fds[fd_count].fd = cin;
                    fds[fd_count].events = POLLIN;
                    fd_count++;
            }
            if(fd != sfd && revent == POLLIN)
            {
                char buf[128] = "";
                int res = read(fd,buf,128);
                if (res == 0)
                {
                    int j=-1;
                    for( j = i;j<fd_count-1;j++)
                    {
                        fds[j] = fds[j+1];
                    }
                    //memset(fds+j,0,sizeof(fds[j]));
                    fd_count--;
                    i--;
                    printf("断开连接\n");
                    close(fd);
                    break;
                }
                printf("客户端发来消息:%s\n",buf);
            }
        }
        
    }
    
    return 0;
}

2.6 poll的代码模型

struct pollfd fds[n] = {0};

fds[0].fd = 想要监视的第一个描述符

fds[0].events = POLLIN

while(1){

poll(fds,n,-1)

for(遍历 fds){

// 判断 fds中哪个描述符激活了

if(fds[i].revents == POLLIN){

调用对应的阻塞函数

例如 accept 或者 read 或者 scanf等

}

}

}


课外作业:

运行1个服务器和2个客户端

实现效果: 服务器和2个客户端互相聊天,服务器和客户端都需要使用select模型去实现

服务器要监视2个客户端是否连接,2个客户端是否发来消息以及服务器自己的标准输入流

客户端要监视服务器是否发来消息以及客户端自己的标准输入流 在不开线程的情况下,实现互相聊天

ser.c

#include<myhead.h>
#define SER_PORT 9999           //服务器端口号
#define SER_IP "192.168.2.157"  //服务器IP
void insert(int * arr,int *len ,int cin)
{
    arr[*len] = cin;
    (*len)++;
}
int find_arr(int * arr,int len ,int cin)
{
    for (int i = 0; i < len; i++)
    {
        if(arr[i] == cin){
            return i;
        }
    }
    return -1;
}
void remove_arr(int * arr,int* len ,int cin)
{
    int tar = find_arr(arr,*len,cin);
    if(tar == -1)
    {
        return ;
    }
    int i =-1;
    for (int i = tar; i < *len-1; i++)
    {
        arr[i] = arr[i+1];
    }
    arr[i] = 0;
    (*len)--;
}
// 主函数
int main(int argc, char const *argv[])
{
    // 创建套接字
    int sfd = socket(AF_INET,SOCK_STREAM,0);
    // 检查套接字创建是否成功
    if(sfd == -1)
    {
        perror("socket error");
        return -1;
    }
    // 初始化服务器地址结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);       // 设置端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP);      // 设置IP地址
    // 绑定套接字和地址
    if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin)) == -1)
    {
        perror("bind error");
        return -1;
    }
    // 监听套接字
    if(listen(sfd,128) == -1)
    {
        perror("listen error");
        return -1;
    }
    // 客户端地址结构体
    struct sockaddr_in client;
    socklen_t len_t = sizeof(client);
    // 读取事件集合
    fd_set readfds,tempfds;
    FD_ZERO(&readfds);    // 初始化读取事件集合

    FD_SET(STDIN_FILENO,&readfds);    // 将标准输入添加到读取事件集合
    FD_SET(sfd,&readfds);    // 将监听套接字添加到读取事件集合
    int cin = 0;
    int cin_arr[128]={0};
    int count = 0;
    char buf[128]="";
    // 主循环
    while (1)
    {
        tempfds = readfds;    // 复制读取事件集合
        // 执行select操作,等待就绪事件
        select(FD_SETSIZE,&tempfds,0,0,0);
        // 检查标准输入是否有事件
        if(FD_ISSET(STDIN_FILENO,&tempfds) == 1)
        {
            memset(buf, 0, sizeof(buf));
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1]=0;
            printf("输入:%s\n", buf);
            // 发送数据到客户端 
            for (int i = 0; i < count; i++) {
                    if (send(cin_arr[i], buf, strlen(buf), 0) < 0) {
                        perror("send failed");
                    }
                }
         }
        // 检查是否有新的客户端连接
        if(FD_ISSET(sfd,&tempfds) == 1)
        {
            if((cin = accept(sfd,(struct sockaddr *)&client,&len_t)) == -1)
            {
                perror("accept error");
                return -1;
            }
            printf("新客户已连接\n");
            FD_SET(cin,&readfds);    // 将新的连接添加到读取事件集合
            insert(cin_arr,&count,cin);    // 添加新的连接到数组
        }
        // 循环处理已连接的客户端
        for (int i = 0; i < count; i++)
        {
            int cin = cin_arr[i];
            if(FD_ISSET(cin,&tempfds) == 1)
            {
                char rbuf[128] = "";
                bzero(rbuf,sizeof(rbuf));
                int res = recv(cin,rbuf,sizeof(rbuf),0);
                if(res == 0)
                {
                    printf("断开连接\n");
                    close(i);
                    FD_CLR(cin,&readfds);    // 从读取事件集合中移除断开连接的客户端
                    remove_arr(cin_arr,&count,cin);    // 从数组中移除断开连接的客户端
                    i--;
                    break;
                }
                printf("客户端发来信息:%s\n",rbuf);
                for(int j=0;j<count;j++)
                {
                    if(cin_arr[j] != cin){
                        write(cin_arr[j],rbuf,sizeof(rbuf));
                    }
                }
            }
        }
        
    }
    close(sfd);
    return 0;
}

cli.c

#include<myhead.h>
#define CLI_PORT 5555           //服务器端口号
#define SER_PORT 9999           //服务器端口号
#define SER_IP "192.168.2.157"  //服务器IP
// 主函数
int main(int argc, char const *argv[])
{
    // 创建客户端套接字
    int cfd = socket(AF_INET,SOCK_STREAM,0);
    // 检查套接字创建是否成功
    if(cfd == -1)
    {
        perror("socket error");
        return -1;
    }
    // 初始化客户端地址结构
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(CLI_PORT);       // 端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP);      // IP地址
    
    // 初始化服务器地址结构
    struct sockaddr_in cin;
    cin.sin_family = AF_INET;          // 通信域
    cin.sin_port = htons(SER_PORT);      // 服务器端口号
    cin.sin_addr.s_addr = inet_addr(SER_IP);     // 服务器IP地址

    // 连接服务器
    if (connect(cfd,(struct sockaddr *)&cin,sizeof(cin)) == -1)
    {
        perror("error connect");
        return -1;
    }
    // 初始化文件描述符集合
    fd_set writefds,tempfds;
    FD_ZERO(&writefds);
    FD_SET(STDIN_FILENO,&writefds);
    FD_SET(cfd,&writefds);

    // 客户端相关变量初始化
    int client=0;
    int cin_arr[128]={0};
    int count = 0;
    char buf[128]="";
    // 主循环
    while (1)
    {
        tempfds = writefds;
        // 监听文件描述符集合上的事件
        select(FD_SETSIZE,&tempfds,0,0,0);
        // 检查标准输入是否有数据
        if (FD_ISSET(STDIN_FILENO, &tempfds) == 1) {
            memset(buf, 0, sizeof(buf));
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1]=0;
            printf("输入:%s\n", buf);
            // 发送数据到服务器
            int res = write(cfd, buf, strlen(buf));
            if (res < 0) {
                perror("write failed");
                break;
            }
        }
        // 检查是否有来自服务器的数据
        if(FD_ISSET(cfd,&tempfds) == 1)
        {
            char rbuf[128]="";
            bzero(rbuf,sizeof(rbuf));
            int res = recv(cfd,rbuf,sizeof(rbuf),0);
            if (res < 0) {
                perror("recv failed");
                break;
            } else if (res == 0) {
                printf("Server closed the connection.\n");
                break;
            }
            rbuf[res] = '\0';
            printf("服务端消息为:%s\n",rbuf);
        }
    }
    // 关闭客户端套接字
    close(cfd);
    return 0;
}
相关推荐
六月闻君9 分钟前
MySQL 报错:1137 - Can‘t reopen table
数据库·mysql
hopetomorrow9 分钟前
学习路之PHP--使用GROUP BY 发生错误 SELECT list is not in GROUP BY clause .......... 解决
开发语言·学习·php
网络安全-杰克17 分钟前
网络安全概论
网络·web安全·php
SelectDB技术团队18 分钟前
兼顾高性能与低成本,浅析 Apache Doris 异步物化视图原理及典型场景
大数据·数据库·数据仓库·数据分析·doris
小牛itbull19 分钟前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress
怀澈12221 分钟前
高性能服务器模型之Reactor(单线程版本)
linux·服务器·网络·c++
请叫我欧皇i28 分钟前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
闲暇部落30 分钟前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
inventecsh34 分钟前
mongodb基础操作
数据库·mongodb
白云如幻38 分钟前
SQL99版链接查询语法
数据库·sql·mysql