IO模型(阻塞,非阻塞,多路复用......)

1、IO分类

  1. 阻塞IO:
    在进行IO操作的时候,如果资源没有准备就绪,会阻塞等待资源,如果资源准备就绪,获取资源。 常用、简单、效率很低
  2. 非阻塞IO:
    在进行IO操作的时候,如果资源没有准备就绪,返回错误信息,如果资源就绪,获取资源。 任务不会阻塞、轮询访问,CPU的消耗较大。
  3. IO多路复用:
    将所有操作的IO放到一个集合中,检测集合中准备就绪的IO,然后进行IO操作。 允许同时对多个IO进行操作。
  4. 信号驱动IO:
    资源准备就绪的时候,由内核向任务发送SIGIO信号,任务接收到信号后,去进行IO操作。

2、阻塞IO

资源没有准备就绪,会一直等待
常用的有fgets、read、write、accept......

3、非阻塞IO

应用程序在执行的时候告诉系统,如果资源准备就绪,返回资源,如果没有准备就绪,返回错误信息。 实现方式更多采用轮询(polling)
具体实现步骤:
1) 需要将IO设置为非阻塞模式;
    a, 打开的时候以非阻塞方式去得到;
        open(pathname, O_NONBLOCK | O_RDWR);
    b,在得到IO的文件描述符后,可以去设置。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
参数:
参数1:fd表示文件描述符
参数2:int cmd, ... /* arg */ 是可变参数,
返回值:
成功:
F_GETFL Value of file status flags.
F_SETFL 0;
失败返回-1,且修改errno的值。

4、IO多路复用

如果现在我来同时操作多个IO:
    1) 采用阻塞模式,IO的操作顺序由代码决定,多个IO是依次执行; 只要前面的IO没有执行,后面的IO即使资源准备就绪,也不会执行。达不到预期效果(谁准备就绪就执行谁);
    2) 非阻塞IO模式,多个IO需要进行轮询访问,导致CPU的消耗很大。不利于执行其它任务。
    3) 采用多任务的方式: 多进程,每一个任务创建一个进程,这个进程会有独立4g内存空间,通信比较麻烦。 多线程,资源共享,需要解决资源互斥和线程的同步。
IO多路复用的思路:
    1) 先创建一个文件描述符集合,将所要关心事件的描述符添加到集合中;
    2) 调用函数来检测集合,检测是否有准备就绪的事件,有事件准备就绪函数返回,没有事件准备就绪函数阻塞
    3) 得到准备就绪事件的文件描述符
    4) 对准备就绪的文件描述符进行IO操作。
具体实现机制:
1. select机制:
    将关注的IO操作放到一个集合中,通过select函数实现阻塞遍历,当关注的文件描述符有对应的资源准备就绪,select函数就会返回,返回值代表资源准备就绪的个数,所以需要通过循环遍历判断是哪一种资源准备就绪。select函数每次检测都是从0~1023遍历。集合总共128字节。
编程流程:
    1、创建集合
    2、清空集合
    3、将关注的文件描述符逐个加入集合
    4、select经数阻塞检测资源是否就绪
    5、循环遍历检测对应的资源
    6、做对应资源相关的操作
    7、回到第4步(用一个while循环)
1) 函数接口:
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数:
    参数1:nfds表示的是集合中最大文件描述符+1;
    参数2:readfds表示关心的读事件集合,不关心用NULL填充
    参数3:writefds表示关心的写事件集合,不关心用NULL填充
    参数4:exceptfds表示关心的其它事件集合,不关心用NULL填充
    参数5:timeout用来设置阻塞事件
                a, 设置为NULL,表示检测时一直阻塞,直到有事件准备就绪函数才返回
                b, 设置结构体的值
struct timeval {
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
};
1) 成员的值都0,非阻塞方式检测;
2) 成员值为非零,有阻塞时间。
返回值:
    成功返回值准备就绪事件的个数;
    返回0表示检测超时
    失败返回-1,且修改errno的值。
void FD_CLR(int fd, fd_set *set); /* 将文件描述符fd从集合set中移除 */
int FD_ISSET(int fd, fd_set *set); /* 检测文件描述符fd是否在集合set,如果在返回非0,不在返回0 */
void FD_SET(int fd, fd_set *set); /* 将文件描述符fd添加到集合set中 */
void FD_ZERO(fd_set *set); /* 清空集合set */
2) 具体实现的思路:
    a,创建读事件集合readfds,并清空集合
        fd_set readfds;
        FD_ZERO(&readfds);
    b,将文件描述符listenfd添加到读事件集合readfds中;
        FD_SET(listenfd, &readfds);
    c,检测集合中是否有准备就绪的事件(阻塞方式检测,有事件准备就绪函数返回)
        ret = select(nfds, &readfds, NULL, NULL, NULL);
    d,得到准备就绪事件的文件描述符
        for(fd = 0; fd < nfds; fd++) {
        if (FD_ISSET(fd, &readfds)) {
    e,对fd进IO操作。
        }
    }
3) 特征:
    a,可以在一个任务中对多个阻塞io进程操作,谁先准备就绪,就操作谁。
    b,如果操作事件不同,需要定义多个集合;
    c,找到准备就绪的文件描述符的时候,需要从0遍历到最大的文件描述符。效率很低。

4.1示例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */


/* 定义服务器初始化函数 */
int server_init(char *ip, short port)
{
    int ret;
    int listenfd;
    struct sockaddr_in srvaddr;

    /* 创建套接字文件 */
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) {
        perror("server_init->socket");
        return -1;
    }
    printf("listenfd = %d\n", listenfd);

    /* 绑定服务器的ip地址和端口号 */
    memset(&srvaddr, 0, sizeof(srvaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(port);
    if (ip == NULL)
        srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    else
        srvaddr.sin_addr.s_addr = inet_addr(ip);
    ret = bind(listenfd, (const struct sockaddr *)&srvaddr, sizeof(srvaddr));
    if (ret == -1) {
        perror("server_init->bind");
        return -1;
    }
    printf("bind success\n");

    /* 启动监听 */
    ret = listen(listenfd, 1024);
    if (ret == -1) {
        perror("server_init->listen");
        return -1;
    }

    return listenfd;
}

/* 定义服务器等待客户端的连接请求,建立连接 */
int server_wait_client_connect(int listenfd)
{
    int connfd;
    socklen_t addrlen;
    struct sockaddr_in cltaddr;

    //accept(listenfd, NULL, NULL);
    addrlen = sizeof(cltaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cltaddr, &addrlen);
    if (connfd == -1) {
        perror("accept");
        return -1;
    }
    printf("IP : %s connet success connfd = %d\n", inet_ntoa(cltaddr.sin_addr), connfd);

    return connfd;
}

int main()
{
    int ret;
    int listenfd;
    int connfd;
    char buf[256];
    fd_set readfds;
    fd_set rdfds;
    int nfds;
    int fd;

    /* 1. 服务器的初始化 */
    listenfd = server_init("127.0.0.1", 8888);
    //listenfd = server_init(NULL, 8888);
    if (listenfd == -1)
        exit(EXIT_FAILURE);
    printf("server init success\n");

    /* 创建集合,清空集合 */
    FD_ZERO(&readfds);

    /* 将文件描述符listenfd添加到读事件集合readfds中 */
    FD_SET(listenfd, &readfds);
    nfds = listenfd + 1;

    while(1) {
        /* c, 调用select函数,检测是否有准备就绪的事件,如果没有事件准备就绪,函数一直阻塞。如果有事件准备就绪函数返回;*/
        rdfds = readfds;
        ret = select(nfds, &rdfds, NULL, NULL, NULL);
        if (ret == -1) {
            perror("select");
            return -1;
        }
        for (fd = 0; fd < nfds; fd++) {
            /* 找到准备就绪事件的文件描述符 */
            if (!FD_ISSET(fd, &rdfds))
                continue;
            /* 准备就绪事件,进行IO操作 */
            if (fd == listenfd) { /* 监听套接字 */
                /* 2. 服务器等待客户端的连接请求,建立连接 */
                connfd = server_wait_client_connect(listenfd);
                if (connfd == -1)
                    exit(EXIT_FAILURE);
                /* 连接成功,将文件描述符connfd添加到读事件集合readfds中 */
                FD_SET(connfd, &readfds);
                nfds = (connfd >= nfds ? connfd+1 : nfds);
                continue;
            } //if (fd == listenfd) end

            /* 通信套接字 */
            /* 3. 服务器处理客户端的数据请求,并处理数据,反馈处理结果 */
            memset(buf, 0, sizeof(buf));
            ret = read(fd, buf, sizeof(buf));
            if (ret == -1) {
                perror("server->read");
                return -1;
            } else if (ret == 0) {
                /* 客户端退出的时候,需要将套接字从集合中删除 */
                FD_CLR(fd, &readfds);
                close(fd);
                break;
            }
            printf("buf : %s\n", buf);

            ret = write(fd, buf, sizeof(buf));
            if (ret == -1) {
                perror("server->write");
                return -1;
            }

        } //for (fd = 0; fd < nfds; fd++) end
    } //while(1) end

    return 0;
}


版权声明:本文为m0_72647108原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。