网络编程(五)IO模型:阻塞IO、非阻塞IO、IO多路复用

一、 阻塞IO

(一)阻塞IO模式

写操作阻塞:
写操作有阻塞的,当缓冲区满时,写操作就会阻塞
直到缓冲区中腾出足够的空间容纳的下本次写操作时,写操作解除阻塞。

读操作阻塞:
当程序执行到读操作时,如果缓冲区有内容,就读走继续向下执行。
如果缓冲区中没有内容,程序就会进入休眠态,直到缓冲区中有内容了,
由内核唤醒当前进程,读走内容继续向下执行。

(二)示例

以有名管道通信为例:同时开启一个读端和三个写端,三个写端分别向各自的管道文件写数据,读端使用循环分别读出管道的内容并打印

read.c

#include <my_head.h>

int main(int argc, char const *argv[])
{
    int fd1 = 0, fd2=0, fd3=0;
    if(-1 == (fd1 = open("fifo1",O_RDONLY))){
        ERR_LOG("open error");
    } 
    if(-1 == (fd2 = open("fifo2",O_RDONLY))){
        ERR_LOG("open error");
    } 
    if(-1 == (fd3 = open("fifo3",O_RDONLY))){
        ERR_LOG("open error");
    } 

    char buff[128];
    int ret=0;
    while(1){
        if(0 < (ret = read(fd1,buff,sizeof(buff)))){
            printf("fifo1:[%s];ret = %d\n",buff,ret);
            memset(buff,0,sizeof(buff));
        }

        if(0 < (ret = read(fd2,buff,sizeof(buff)))){
            printf("fifo2:[%s];ret = %d\n",buff,ret);
            memset(buff,0,sizeof(buff));
        } 

        if(0 < (ret =read(fd3,buff,sizeof(buff)))){
            printf("fifo3:[%s];ret = %d\n",buff,ret);
            memset(buff,0,sizeof(buff));
        }
    }
    close(fd1);
    close(fd2);
    close(fd3);
    return 0;
}

write.c(三个写端除了打开的管道文件不同,其余相同)

#include <my_head.h>

int main(int argc, char const *argv[])
{
    int fd = open("fifo1",O_WRONLY);

    char buff[128];
    while(1){
        scanf("%s",buff);
        if(-1 == write(fd,buff,sizeof(buff))){
            ERR_LOG("write error");
        }
    }
    close(fd);
    return 0;
}
  • 注:
  • 阻塞IO会相互影响:以上例说明,在写入fifo1并输出后,就会在读fifo2文件内容时阻塞等待,此时如果fifo1或者fifo3中有内容输入也不会处理。
  • 此时read进程处于休眠状态,等待用户输入内容,不会占用CPU

二、非阻塞IO

(一)特点

读操作为例:
当程序执行到读操作时,如果缓冲区有内容,就读走继续向下执行。
如果缓冲区中没有内容,程序会立即返回一个错误,而不会休眠。
这种操作一般需要一个循环来不停地测试是否有数据可读,
这种操作十分浪费CPU资源 一般不推荐使用。

有一部分函数 是自带非阻塞标志位的
如:recv和recvfrom的 MSG_DONTWAIT O_NONBLOCK 、waitpid的 W_NOHANG

(二)fcntl

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:操作文件描述符
参数:
    fd:要操作的文件描述符
    cmd:操作方式
        F_GETFL  获取文件描述符的属性  arg被忽略
        F_SETFL  设置文件描述符的属性  arg是int类型
            其中 O_NONBLOCK 就是非阻塞的标志位
    ...:可变参
返回值:
    F_GETFL 成功 返回文件状态信息  失败  -1  重置错误码

(三)示例

将文件设置为非阻塞。
read.c

#include <my_head.h>

int main(int argc, char const *argv[])
{
    int fd1 = 0, fd2=0, fd3=0;
    int flag=0;
    if(-1 == (fd1 = open("fifo1",O_RDONLY))){
        ERR_LOG("open error");
    } 
    flag=fcntl(fd1,F_GETFL);
    flag |= O_NONBLOCK;
    if(-1 == fcntl(fd1,F_SETFL,flag)){
        ERR_LOG("fcntl error");
    }
    
    if(-1 == (fd2 = open("fifo2",O_RDONLY))){
        ERR_LOG("open error");
    } 
    flag=fcntl(fd2,F_GETFL);
    flag |= O_NONBLOCK;
    if(-1 == fcntl(fd2,F_SETFL,flag)){
        ERR_LOG("fcntl error");
    }

    if(-1 == (fd3 = open("fifo3",O_RDONLY))){
        ERR_LOG("open error");
    } 
    flag=fcntl(fd3,F_GETFL);
    flag |= O_NONBLOCK;
    if(-1 == fcntl(fd3,F_SETFL,flag)){
        ERR_LOG("fcntl error");
    }
    char buff[128];
    int ret=0;
    while(1){
        if(0 < (ret = read(fd1,buff,sizeof(buff)))){
            printf("fifo1:[%s];ret = %d\n",buff,ret);
            memset(buff,0,sizeof(buff));
        }

        if(0 < (ret = read(fd2,buff,sizeof(buff)))){
            printf("fifo2:[%s];ret = %d\n",buff,ret);
            memset(buff,0,sizeof(buff));
        } 

        if(0 < (ret =read(fd3,buff,sizeof(buff)))){
            printf("fifo3:[%s];ret = %d\n",buff,ret);
            memset(buff,0,sizeof(buff));
        }
        sleep(1);
    }
    close(fd1);
    close(fd2);
    close(fd3);

    return 0;
}
  • 注:
  • 非阻塞IO不会相互影响:以上例说明,不会再阻塞等待用户输入,在哪个管道读到数据,就直接输出。
  • 此时read进程处于运行状态,不会阻塞等待用户输入内容,会一直占用CPU资源进行遍历

三、IO多路复用

(一)实现原理

构造一个关于文件描述符的表,将所有要监视的文件描述符都放在这个表中
将这个表交给一个函数:select / poll / epoll
这个函数默认也是阻塞的,直到监视的文件文件描述符中有一个或多个准备就绪
这个函数会返回,返回时,会告诉调用者哪些文件描述符已经就绪,
调用者对已经就绪的文件描述符操作,就不会阻塞了。

(二)select函数

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
          fd_set *exceptfds, struct timeval *timeout);
功能:
    实现多路IO复用
参数:
    nfds:要监视的最大文件描述符+1
    readfds:要监视的读文件描述符集合(监视它是否可读) 不关心可以传NULL
    writefds:要监视的写文件描述符集合(监视它是否可写) 不关心可以传NULL
    exceptfds:要监视的异常文件描述符集合 不关心可以传NULL
    timeout:超时时间 传 NULL 表示永久阻塞 直到有就绪的文件描述符为止
返回值:
    成功  返回就绪的文件描述符的个数

void FD_CLR(int fd, fd_set *set);//将文件描述符在集合中删除
int  FD_ISSET(int fd, fd_set *set);//测试文件描述符是否还在集合中
                                    //在 返回真 不在 返回假
void FD_SET(int fd, fd_set *set);//将文件描述符添加到集合中
void FD_ZERO(fd_set *set);//清空集合
  • 注:
    1. select只能监视小于FD_SETSIZE(1024)的文件描述符
    2. 我们一般只关心读文件描述符集合 其他两个传NULL即可
    3. select每次返回时会将没有就绪的文件描述符在集合中擦除
      所以在循环中使用select时每次需要重置集合

(三)使用示例

#include <my_head.h>

int main(int argc, char const *argv[])
{
    int fd1 = 0, fd2=0, fd3=0;

    if(-1 == (fd1 = open("fifo1",O_RDONLY))){
        ERR_LOG("open error");
    } 
    
    if(-1 == (fd2 = open("fifo2",O_RDONLY))){
        ERR_LOG("open error");
    } 
    
    if(-1 == (fd3 = open("fifo3",O_RDONLY))){
        ERR_LOG("open error");
    } 
    //创建
    fd_set readfds;//用来保存需要监视的文件描述符的母本
    fd_set tempfds;
    int max_fd=0;
    FD_ZERO(&readfds);
    FD_ZERO(&tempfds);

    FD_SET(fd1,&readfds);
    max_fd = max_fd > fd1 ? max_fd : fd1;
    FD_SET(fd2,&readfds);
    max_fd = max_fd > fd2 ? max_fd : fd2;
    FD_SET(fd3,&readfds);
    max_fd = max_fd > fd3 ? max_fd : fd3;

    char buff[128];
    int num = 0;
    while(1){
        tempfds = readfds;
        num = select(max_fd+1,&tempfds,NULL,NULL,NULL);
        
        for(int i=0; i<max_fd+1 && num; i++){
            if(FD_ISSET(i,&tempfds)){
                memset(buff,0,sizeof(buff));
                if(0 < read(i,buff,sizeof(buff))){
                    printf("fd%d:[%s]\n",i,buff);
                }
                num--;
            }
        }
    }
    close(fd1);
    close(fd2);
    close(fd3);
    return 0;
}

(四)关于select函数的理解

  1. fd_set 实际是一个长度为1024的bit数组,
typedef struct{
	long ss[16]; //8*8*16=1024
}fd_set;

但是C语言最小单位是字节,因此才用这种方式表示;

  1. select的集合是基于数组实现的,最多监视1024个文件
  2. 本质是一个数组,包一个结构体是为了复制方便
  3. 四个宏定义的本质就是封装好的方便用户操作集合的位运算
  • 21
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值