网络模型之阻塞 I/O

同步I/O、异步I/O、阻塞I/0、非阻塞I/O、五种I/O模型、高性能网络模型 面试最常问问题之二:五种I/O模型及区别 ​ 阅读详情

一、什么是阻塞 I/O?

所谓阻塞 I/O(Blocking I/O):
当线程调用 recv() / accept() / send() 时,如果条件不满足,线程会被挂起(阻塞),直到操作完成才返回。

简单说就是:

没数据 → 等
有数据 → 拷贝 → 返回

线程在等待期间什么都不能做。

1.内核层到底发生了什么?
我们以 recv() 为例。当你调用:

recv(sock, buf, 1024, 0);

内核内部流程:

 - 进入内核态
️2. 检查 socket 接收缓冲区
️3. 如果没有数据
       → 线程进入等待队列
       → CPU切走
️3. 网络数据到达
️4. 网卡中断
️5. 数据进入内核缓冲区
️6. 唤醒等待线程
️7. 内核把数据拷贝到用户缓冲区
️8. 返回

重点:

  • 线程被挂起
  • CPU 可以调度其他线程
  • 不是忙等

2.阻塞 I/O 时序图

用户线程
   │
   │ recv()
   ▼
内核
   │
   │ 没数据
   ▼
线程睡眠(阻塞)
   │
   │ 数据到达
   ▼
唤醒线程
   │
   │ 拷贝数据
   ▼
返回

3.从TCP发送数据的流程理解阻塞 I/O
在这里插入图片描述

以两个应用程序通讯为例,来了解一下当“A”向"B" 发送一条消息,流程如下:
第一步:应用A把消息发送到 TCP发送缓冲区。
第二步: TCP发送缓冲区再把消息发送出去,经过网络传递后,消息会发送到B服务器的TCP接收缓冲区。
第三步:B再从TCP接收缓冲区去读取属于自己的数据。

根据上图我们基本上了解消息发送要经过 应用A、应用A对应服务器的TCP发送缓冲区、经过网络传输后消息发送到了应用B对应服务器TCP接收缓冲区、然后最终B应用读取到消息。

应用之间发送消息是间断性的,也就是说在上图中TCP缓冲区还没有接收到属于应用B该读取的消息时,那么此时应用B向TCP缓冲区发起读取申请,TCP接收缓冲区是应该马上告诉应用B, 现在没有你的数据,还是说让应用B在这里等着,直到有数据再把数据交给应用B。

把这个问题应用到第一个步骤也是一样,应用A在向TCP发送缓冲区发送数据时,如果TCP发送缓冲区已经满了,那么是告诉应用A现在没空间了,还是让应用A等待着,等TCP发送缓冲区有空间了再把应用A的数据访拷贝到发送缓冲区。

所谓阻塞IO就是当应用B发起读取数据申请时,在内核数据没有准备好之前,应用B会一直处于等待数据状态,直到内核把数据准备好了交给应用B才结束。用术语描述就是在应用调用recvfrom读取数据时,其系统调用直到数据包到达且被复制到应用缓冲区中或者发送错误时才返回,在此期间一直会等待,进程从调用到返回这段时间内都是被阻塞的称为阻塞IO。

流程:

  1. 应用进程向内核发起recfrom读取数据。
  2. 准备数据报(应用进程阻塞)。
  3. 将数据从内核负责到应用空间。
  4. 复制完成后,返回成功提示。

在这里插入图片描述

4.Windows 阻塞 I/O 服务端完整代码
服务端代码:

#include <winsock2.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    // 1️⃣ 创建socket
    SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8888);
    serverAddr.sin_addr.s_addr = INADDR_ANY;

    // 2️⃣ 绑定
    bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr));

    // 3️⃣ 监听
    listen(listenSock, 5);

    std::cout << "Server listening..." << std::endl;

    // 4️⃣ 阻塞等待客户端连接
    SOCKET clientSock = accept(listenSock, NULL, NULL);

    std::cout << "Client connected!" << std::endl;

    char buffer[1024];

    while (true)
    {
        // 5️⃣ 阻塞等待数据
        int ret = recv(clientSock, buffer, sizeof(buffer), 0);

        if (ret <= 0)
        {
            std::cout << "Client disconnected." << std::endl;
            break;
        }

        buffer[ret] = '\0';
        std::cout << "Received: " << buffer << std::endl;

        // 6️⃣ 阻塞发送
        send(clientSock, buffer, ret, 0);
    }

    closesocket(clientSock);
    closesocket(listenSock);
    WSACleanup();

    return 0;
}

客户端代码:

#include <winsock2.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8888);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 阻塞连接
    connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr));

    char sendBuf[1024] = "Hello Server";

    // 阻塞发送
    send(sock, sendBuf, strlen(sendBuf), 0);

    char recvBuf[1024];
    int ret = recv(sock, recvBuf, sizeof(recvBuf), 0);

    recvBuf[ret] = '\0';
    std::cout << "Server says: " << recvBuf << std::endl;

    closesocket(sock);
    WSACleanup();

    return 0;
}

二、阻塞 I/O + 多线程模型

核心思想:
每个客户端连接 → 创建一个线程 → 在线程中使用阻塞 I/O

结构图:

主线程
   │
   │ accept()
   ▼
创建新线程
   │
   ▼
线程1 —— recv()
线程2 —— recv()
线程3 —— recv()

每个线程:

while(true)
{
    recv();
    send();
}

执行流程:
主线程

1. listen
2. accept (阻塞)
3.️ 创建线程
️4. 回到 accept

工作线程

️1. recv (阻塞)
️2. 处理数据
️3. send (阻塞)
️4. 循环

服务端代码:

#include <winsock2.h>
#include <windows.h>
#include <iostream>
#include <thread>

#pragma comment(lib, "ws2_32.lib")

void ClientHandler(SOCKET clientSock)
{
    char buffer[1024];

    while (true)
    {
        int ret = recv(clientSock, buffer, sizeof(buffer), 0);

        if (ret <= 0)
        {
            std::cout << "Client disconnected." << std::endl;
            break;
        }

        buffer[ret] = '\0';
        std::cout << "Received: " << buffer << std::endl;

        send(clientSock, buffer, ret, 0);
    }

    closesocket(clientSock);
}

int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8888);
    serverAddr.sin_addr.s_addr = INADDR_ANY;

    bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(listenSock, 10);

    std::cout << "Server listening..." << std::endl;

    while (true)
    {
        SOCKET clientSock = accept(listenSock, NULL, NULL);

        if (clientSock != INVALID_SOCKET)
        {
            std::cout << "New client connected." << std::endl;

            std::thread t(ClientHandler, clientSock);
            t.detach();   // 分离线程
        }
    }

    closesocket(listenSock);
    WSACleanup();

    return 0;
}

为什么性能会崩?
假设:

2000 个连接

就意味着:

2000 个线程

问题来了:
内存爆炸

2000 × 1MB = 2GB 栈空间

CPU 上下文切换
线程多了:

频繁 context switch

CPU 大量时间花在线程调度。

三、和阻塞单线程对比

在这里插入图片描述
多线程阻塞比单线程强很多,但不够优雅。

常见改进:线程池 + 阻塞 I/O
可以改成:

主线程 accept
↓
把 socket 放入任务队列
↓
固定数量线程处理

但注意:阻塞 I/O 在线程池里意义不大;因为 recv() 会卡住线程。它是用线程数量换并发能力;但代价是内存和 CPU 调度开销。

阻塞IO、非阻塞IO和IO复用有啥区别? 本文详细介绍了阻塞IO、非阻塞IO和IO复用这三种不同的IO模型。阻塞IO在进行IO操作时会阻塞进程,非阻塞IO允许程序在进行IO操作时继续执行其他任务,而IO复用可以同时监听多个文件描述符的IO事件。了解这些概念和区别对于进行网络编程或系统开发都非常重要,希望本文能够帮助读者更好地理解和应用阻塞IO、非阻塞IO和IO复用。在进行网络编程或系统开发时,经常会遇到阻塞IO、非阻塞IO和IO复用这些概念。本文将详细解释阻塞IO、非阻塞IO和IO复用的概念、特点及适用场景,帮助读者更好地理解和应用它们。 阅读详情

相关推荐

AI图像识别在临电配电箱安全检测中的应用与实践

计算机视觉与AI图像识别技术正逐步改变传统工业检测方式。通过深度学习模型如YOLOv5和ResNet,系统能自动识别设备元件并分析其状态,大幅提升检测效率和准确性。这种技术特别适用于需要严格遵循行业标准的场景,如临电配电箱的安全检测。将《施工现场临时用电安全技术规范》等条文转化为可量化的算法,AI系统能自动检查PE线连接、漏保功能等关键项目。实际应用表明,此类解决方案可使电气事故率下降90%以上,同时检测耗时缩短80%,为工地安全提供了智能化保障。

weixin_29042035的博客 326

阻塞IO与非阻塞IO

学习地址:https://www.cnblogs.com/xiaoxi/p/6525396.html 在《Unix网络编程》一书中提到了五种IO模型,分别是:阻塞IO、非阻塞IO、多路复用IO、信号驱动IO以及异步IO。 1、阻塞IO模型   最传统的一种IO模型,即在读写数据过程中会发生阻塞现象。   当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数...

Chen4852010的博客 1万+

基于PWM的汽车仪表步进电机微步驱动优化实践

本文深入探讨了基于PWM的微步驱动技术,用于优化汽车仪表步进电机的运行性能。通过将传统的分步驱动升级为PWM微步细分驱动,利用正弦/余弦查表法合成旋转磁场,有效消除了指针抖动与噪声,实现了指针的平滑、精准运动。文章以STM32和VID2905电机为例,提供了详细的硬件连接、代码实现及高级优化技巧,为提升汽车仪表体验提供了实用方案。

weixin_29169505的博客 510

阻塞IO详解

阻塞IO详解

zgg1985658560的博客 1062

Understanding Lockup Cells

工具会分析扫描链和EDT逻辑之间的控制时序元素的时钟的时序关系,当必须要同步时钟并保持数据完整性时插入边沿触发寄存器(lockup cells)。可以使用report_edt_lockup_cells命令来展示工具已经插入的lockup cells的详细报告。

weixin_44954910的博客 1080

PSIM+PIL+CCS三剑客:手把手教你搞定DSP28335的软硬件联合调试

本文详细介绍了如何利用PSIM、PIL与CCS三款工具,实现DSP28335的软硬件联合调试。通过搭建兼容环境、从PSIM仿真生成PIL代码,再到实战联合调试与问题诊断,手把手指导开发者构建从虚拟模型到实际硬件的完整验证闭环,有效提升电力电子与电机驱动等领域的开发效率与可靠性。

weixin_29183637的博客 185

阻塞 IO

进程的状态 在操作系统中,进程至少要有三种基本状态:运行状态,就绪状态和等待状态。 1)就绪——执行:对就绪状态的进程,当进程调度程序按一种选定的策略从中选中一个就绪进程,为之分配了处理机后,该进程便由就绪状态变为执行状态; 2)执行——等待:正在执行的进程因发生某等待事件而无法执行,则进程由执行状态变为等待状态,如进程提出输入/输出请求而变成等待外部设备传输信息的状态,进程申请资源(主存空间或外部设备)得不到满足时变成等待资源状态,进程运行中出现了故障(程序出错或主存储器读写错等)变成等待干预状态等等;

qq_29890089的博客 1178

RT-Thread stm32h7使用串口DMA

stm32h7在rtthread中无法使用串口DMA,通过更改驱动代码使其支持串口DMA

qq_35141454的博客 2284

IO-阻塞IO

BIO即阻塞型IO,是jdk1.4以前使用的IO模型。。

2311

使用TensorFlow Lite Micro流程记录(带源码)

记录如何使用tensorflow lite micro,以及期间解决的问题

ZhaoDongyu_AK47的博客 6214

matlab 点云均值、中值、高斯滤波【2024最新版】

点云均值滤波、中值滤波、高斯滤波的matlab自带函数实现。博客长期更新,本文最近一次更新时间为:2024年11月9日。

点云侠的博客 5257

网络模型之非阻塞 I/O

本文介绍网络模型之非阻塞 I/O的原理和使用

TechNomad的博客 356

069、自动曝光(AE)测光策略:全局/中心/点测光与曝光时间增益分配

本文深入探讨了自动曝光(AE)测光策略的技术细节与实际应用。作者通过多个真实案例(手机前置摄像头、车载ADAS、安防监控等)分析了全局测光、中心测光和点测光的优缺点,指出不同场景应采用不同策略。重点剖析了曝光时间与增益分配的"跷跷板效应",提出要根据传感器特性选择最优分配方案。文章强调测光策略应动态调整,并给出5条实用建议:结合场景切换测光模式、关注传感器增益拐点、点测光需防抖处理、全局测光不宜迷信、调试要观察亮度直方图。最后指出AE调试需要结合具体硬件特性进行实测优化,没有通用解决方案。

zzfmlxs的博客 100

I/O网络模型

概述 本文从I/O网络模型出发,介绍目前主流的几种网络模型,然后对同步阻塞I/O、同步非阻塞I/O、I/O多路复用的机制和流程做了详细的阐述,最后通过一个生活中的例子加深对这3中网络模型机制的理解。 I/O操作 网络IO的本质是socket的读取,socket在linux中被抽象为流,IO操作可以理解为对流的操作。为了操作系统的安全性等考虑,进程是无法直接操作I/O设备的,其必须通过系统调用请求内...

r91987的专栏 512

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Go爬虫学习笔记(四)_i o 可以分为磁盘 i o 与网络 i o,你知道 go 在处理二者时的区别吗

Reactor 本身有反应堆的意思,表示对监听的事件做出相应的反应。信号驱动 I/O 是一种相对异步的方式,当 Socket 准备就绪后,它通过中断、回调等机制来通知调用者继续调用后续对应的 I/O 操作,而后续的调用常常是堵塞的。Go 语言高效,是因为在同步处理的表象下,Go 运行时封装 I/O 多路复用,灵巧调度协程,实现了异步的处理,也实现了对 CPU 等资源的充分利用。Go 是以同步的方式来处理网络 I/O 的,它会等待网络 I/O 就绪后,才继续下面的流程,这是符合开发者直觉的处理方式。

2401_86637143的博客 1003

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

深入理解Unix下的五种I/O网络模型

Unix下的五种I/O网络模型分别为: (1)阻塞式I/O (2)非阻塞式I/O (3)I/O多路复用 (4)信号驱动式I/O (5)异步I/O 为什么会有这5种I/O模型? 明确两点: 网络I/O会涉及到两个系统对象: (1) 用户空间 调用的IO进程/线程; (2) 内核空间 的内核系统; I/O操作会经历两个阶段: (1) 等待数据准备就绪; (2) 将数据从内核拷贝到进程或者线程; 上述两个步骤是针对read操作,如果是write,则I/O的两个阶段为: (1) 等待写缓冲区可写; (2)

ArtAndLife的博客 285

同步异步、阻塞和非阻塞、网络模型

同步异步、阻塞和非阻塞是两套概念,容易混淆。 同步异步是进程和内核之间 阻塞和非阻塞是进程在访问数据时候,根据IO操作的就绪状态来采取的不同方式 1.同步和异步 同步和异步是针对应用程序和内核的交互来说的,同步指的是用户进程触发IO操作并等待或者轮询的去查看IP操作是否就绪,而异步是指用户进程触发IO操作以后便开始做自己的事情,交给OS,而当IO操作完成时会得到IO完成的通知。 ...

jadeshu的博客 817

JavaIO网络模型——阻塞IO(Blocking I/O)

目录简介过程特点应用简单实现 简介 过程 特点 应用 简单实现

qq_43722914的博客 637

网络模型同步异步与I/O模型同步异步

理解 I/O模型的同步与异步 网络 I/O 的同步与异步跟网络模型的同步与异步不同。在网络 I/O 接口中,有阻塞、非阻塞、多路复用、信号驱动、异步I/O五中I/O模型。网络I/O分为俩步,第一步等待条件就绪,第二步进行真正的数据I/O也就是把socket接受的数据搬移到应用层buffer中。 那么这五中I/O模型中,前四种的第一步骤各有不同,但是第二个步骤都一样的都需要我们自己去I/O,但是异步...

我喜欢雨天的清新的博客 892

Redis 网络模型

内核将数据写入 内核缓冲区,通过系统调用把 内核缓冲区中的数据拷贝的用户缓冲区中:用户把数据写入 用户缓冲区,通过系统调用把 用户缓冲区中的数据写入到内核缓冲区中。

java_beautiful的博客 726

linux网络模型-阻塞i/o代码实现

0x00 环境和编辑工具 linux os:ubuntu 16.04 editer:vim 编译工具:g++ 0x01 代码实现 #include #include #include #include #include #include #include #include #include using namespace std; #define ErrFla

破瓜棚 376

Redis网络I/O模型

Redis处理网络I/O

蔡林浩的博客 1077

从BIO到Netty(1)- Linux网络模型简介

从BIO到Netty(1)- Linux网络模型简介前言Linux网络模型简介阻塞I/O非阻塞I/OI/O复用模型信号驱动I/O异步I/O总结参考资料 前言 其实我一直以来都有做笔记的习惯,但是呢却很少写博客。一方面我之前觉得,如果一篇博客文章仅仅只是从其他书籍或者资料中摘录一通然后拼接而成,而不添加一些自己的内容的话,好像意义不大。另一方面,如果我添加了自己的内容,但是内容不完善甚至是错误的理解怎么办,那不是坑别人了么?而且还很容易被人喷。因此我对写博文这事情一直畏畏缩缩的,当然懒也是重要原因。 但是最近

curePony 275

linux网络模型非阻塞i/o模型代码实现

#include #include #include #include #include #include #include #include #include #include #define ErrFlag -1 #define ShowErrMsg(msg)\ do{ perror(msg); exit(1); }while(0) void* myFunc

破瓜棚 423

Redis系列学习文章分享---第十七篇(Redis原理篇--数据结构,网络模型)

String类型可以存储任意类型的数据,包括文本、数字等,常用于缓存、计数器等场景。Redis也支持非阻塞I/O模型,通过设置非阻塞标志,使得网络请求不会阻塞等待处理完成,而是立即返回处理结果。Redis采用epoll函数实现IO多路复用,允许同时监视多个文件描述符的状态,以处理多个连接的IO操作。Redis采用poll函数实现IO多路复用,允许同时监视多个文件描述符的状态,以处理多个连接的IO操作。Redis采用I/O多路复用技术,可以同时监视多个文件描述符的状态,实现了高效的事件处理。

私聊前往站内信:https://i.youkuaiyun.com/#/msg/chat/weixin_44976692 2万+

I/O 模型全解析:从阻塞到异步的演进之路

1. 创建实例:调用 epoll_create,内核建 eventpoll 结构体,它有红黑树 rb_root 存监听 FD,链表 list_head 存就绪 FD。2. 添加监听:用 epoll_ctl,把 FD 等信息加到红黑树,设置回调,FD 就绪时回调会把它移到就绪链表。3. 等待就绪:通过 epoll_wait 检查就绪链表,有就绪 FD 就返回数量等信息,供程序处理。这样就结合红黑树和链表这些结构。

weixin_47125004的博客 1956
上一篇: 大数据topk问题-大小根堆算法实现
下一篇: 网络模型之非阻塞 I/O
TechNomad
博客等级 码龄2年 787粉丝 183原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值