网络编程:阻塞IO和非阻塞IO(fcntl函数说明)

本文对比了阻塞和非阻塞I/O模式,通过实例阐述了阻塞I/O在读写操作中的行为,并介绍了如何通过fcntl函数实现非阻塞I/O。重点讨论了阻塞IO的效率问题和非阻塞IO的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一 阻塞IO

1.1概念:

阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。

缺省情况下,套接字建立后所处于的模式就是阻塞I/O 模式。

前面学习的很多读写函数在调用过程中会发生阻塞。

读操作中的read、recv、recvfrom

写操作中的write、send

其他操作:accept、connect

以读阻塞为例:

当进程执行到读函数的时候

如果缓冲区里面有内容,程序读取完内容之后就继续向下执行,

如果缓冲区里面没有内容,进程就会进入休眠态,直到缓冲区里面有内容了

内核会唤醒该进程,然后进程过来读取缓冲区的内容,然后继续向下执行。

写操作也是会阻塞的,当写缓冲区满的时候就会阻塞

1.2代码实现:

1.2.1读端

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(){
    int fd1 = open("./fifo1", O_RDONLY);
    int fd2 = open("./fifo2", O_RDONLY);
    int fd3 = open("./fifo3", O_RDONLY);

    char buff1[128] = {0};
    char buff2[128] = {0};
    char buff3[128] = {0};

    while(1){
        memset(buff1, 0, 128);
        read(fd1, buff1, 128);
        printf("buff1:[%s]\n", buff1);

        memset(buff2, 0, 128);
        read(fd2, buff2, 128);
        printf("buff2:[%s]\n", buff2);

        memset(buff3, 0, 128);
        read(fd3, buff3, 128);
        printf("buff3:[%s]\n", buff3);
    }

    close(fd1);
    close(fd2);
    close(fd3);

    return 0;
}

1.2.2写端(三个写端都是一样的,只不过名字不一样)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(){
    int fd = open("./fifo1", O_WRONLY);

    char buff[128] = {0};

    while(1){
        memset(buff, 0, 128);
        fgets(buff, 128, stdin);
        buff[strlen(buff)-1] = '\0';
        write(fd, buff, 128);
    }

    close(fd);

    return 0;
}

二非阻塞IO

2.1概念

以读阻塞为例:

当进程执行到读函数的时候

如果缓冲区里面有内容,程序读取完内容之后就继续向下执行,

如果缓冲区里面没有内容,进程就不进入休眠态,而是立即返回一个错误

这种情况下就需要我们轮询去执行操作,这种操作是十分占用CPU的

一般不推荐使用

一般阻塞函数都有一个能设置非阻塞方式的选项

如 recv 和 recvfrom 的 MSG_DONTWAIT

waitpid 的 WNOHONG

O_NONBLOCK

对于read这种函数,本身是没有非阻塞选项的,这是可以使用 fcntl 函数来设置非阻塞

2.2fcntl函数的说明:

int flag = fcntl(fd, F_GETFL);//获取原来的状态
flag |= O_NONBLOCK;//添加非阻塞属性
fcntl(fd, F_SETFL, flag);//再设置回去

2.3代码说明:

2.3.1读端:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(){
    int fd1 = open("./fifo1", O_RDONLY);
    int fd2 = open("./fifo2", O_RDONLY);
    int fd3 = open("./fifo3", O_RDONLY);

    char buff1[128] = {0};
    char buff2[128] = {0};
    char buff3[128] = {0};

    //将文件描述符都设置成非阻塞
    int flag = fcntl(fd1, F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(fd1, F_SETFL, flag);

    flag = fcntl(fd2, F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(fd2, F_SETFL, flag);

    flag = fcntl(fd3, F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(fd3, F_SETFL, flag);

    while(1){
        memset(buff1, 0, 128);
        read(fd1, buff1, 128);
        printf("buff1:[%s]\n", buff1);

        memset(buff2, 0, 128);
        read(fd2, buff2, 128);
        printf("buff2:[%s]\n", buff2);

        memset(buff3, 0, 128);
        read(fd3, buff3, 128);
        printf("buff3:[%s]\n", buff3);

        //sleep(1);//此处的sleep(1) 是为了防止刷屏
            //能看到现象的,正常应该是没有的
            //如果没有这个sleep  使用 top可以看到
            //该进程 基本已经把CPU占满了
    }

    close(fd1);
    close(fd2);
    close(fd3);

    return 0;
}

2.3.2写端和上面一样

<think>好的,我现在需要回答用户关于Unix环境下阻塞IO非阻塞IO的区别及用法的问题。首先,我要回顾一下用户提供的参考资料,确保我的回答准确。 根据引用[1],同步IO异步IO的区别在于数据从内核拷贝到用户空间时是否阻塞进程,而阻塞IO非阻塞IO的区别在于发起IO请求时是否阻塞。引用[4]提到在网络编程中,阻塞IO非阻塞IO的概念涉及socket的处理方式。 首先,我需要明确两者的基本区别。阻塞IO在数据未就绪时会一直等待,线程被挂起,直到操作完成。而非阻塞IO会立即返回一个错误码,如EAGAIN或EWOULDBLOCK,线程可以继续执行其他任务,需要轮询检查状态。 接下来是实现的区别。用户可能需要知道如何设置非阻塞模式,比如使用fcntl函数设置O_NONBLOCK标志,或者使用select、poll、epoll等IO多路复用技术来管理多个非阻塞socket。这部分需要结合Unix系统调用来解释。 然后是关于使用场景。阻塞IO适合简单的应用程序,逻辑简单但并发能力差。非阻塞IO适合高并发场景,如网络服务器,但代码复杂度高。这里可以举例说明,比如Web服务器如何处理多个连接。 还需要注意两者的性能差异。非阻塞IO通过避免线程阻塞,提高了资源利用率,但需要更多的系统调用复杂的错误处理。而阻塞IO虽然效率低,但编程模型简单。 最后,用户可能需要知道如何在实际中实现这两种模式。比如,在socket编程中,创建socket时默认是阻塞的,如何更改为非阻塞,并配合多路复用技术使用。同时,要提到常见的函数fcntl、select等。 检查是否有遗漏点,比如是否需要提到异步IO(但用户主要问阻塞非阻塞)。不过引用[1]提到了同步异步的区别,可以适当关联,但重点在阻塞非阻塞。 总结结构:先区别定义,再实现方式,然后使用场景,最后注意事项。确保引用正确,比如引用[1][4]中的内容,自然地插入引用标识。同时避免使用Markdown格式,保持自然的中文口语化。</think>### Unix 环境下阻塞 IO非阻塞 IO 的区别及实现方式 #### 一、核心区别 1. **阻塞 IO** - **定义**:当进程发起 IO 操作时,若数据未就绪(例如网络数据未到达或磁盘未响应),进程会被挂起(阻塞),直到数据就绪并完成拷贝到用户空间后才会继续执行[^1][^4]。 - **特点**:编程模型简单,但会占用线程资源,导致并发能力受限。 2. **非阻塞 IO** - **定义**:进程发起 IO 操作时,若数据未就绪,内核立即返回错误码(如 `EAGAIN` 或 `EWOULDBLOCK`),进程可继续执行其他任务,需通过轮询或事件通知机制主动检查数据状态[^1][^4]。 - **特点**:提高了资源利用率,但需要更复杂的逻辑处理(如重试、多路复用)。 #### 二、实现方式 1. **阻塞 IO 的实现** - **默认模式**:Unix 中大多数文件描述符(如 socket)默认是阻塞模式。 - **示例代码**(TCP 读取): ```c char buffer[1024]; ssize_t n = read(socket_fd, buffer, sizeof(buffer)); // 阻塞直到数据到达或出错 ``` 2. **非阻塞 IO 的实现** - **设置非阻塞标志**:通过 `fcntl` 函数为文件描述符添加 `O_NONBLOCK` 标志。 ```c int flags = fcntl(socket_fd, F_GETFL, 0); fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); ``` - **配合多路复用技术**:使用 `select`、`poll` 或 `epoll` 监听多个非阻塞描述符的就绪状态。 ```c fd_set read_fds; FD_ZERO(&read_fds); FD_SET(socket_fd, &read_fds); select(socket_fd + 1, &read_fds, NULL, NULL, NULL); // 等待数据就绪 ``` #### 三、使用场景 - **阻塞 IO**:适合简单、低并发的场景(如命令行工具)。 - **非阻塞 IO**:适合高并发服务(如 Web 服务器、实时通信系统),通过多路复用技术(如 `epoll`)管理海量连接。 #### 四、注意事项 - **性能权衡**:非阻塞 IO 减少了线程阻塞时间,但增加了系统调用次数(如轮询)代码复杂度。 - **错误处理**:非阻塞 IO 需处理 `EAGAIN` 等错误,并设计重试逻辑。 - **与异步 IO 的区别**:非阻塞 IO 仍是同步操作(需主动检查状态),而异步 IO(如 `aio_read`)由内核通知完成,无需轮询[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值