网络 IO 核心(同步/异步)概念笔记

网络 IO 流程

一次完整的网络 IO 操作,整体可以划分为数据准备数据读写拷贝两个核心阶段,所有网络读写行为都围绕这两个阶段展开。

日常开发中经常使用int size = recv(sockfd,buf,1024,0);函数读取套接字数据,该接口默认工作在阻塞读取模式下。函数执行后会返回实际读取到的数据字节大小,我们可以依靠返回值判断当前网络连接与数据状态。

如果主动将 socket 套接字设置为非阻塞模式,即便当前内核缓冲区没有任何待接收数据,recv函数也不会卡住线程,而是会立刻返回结果。针对返回值有一套固定判断逻辑:

  1. size == -1:代表本次接收操作出现异常,需要进一步排查错误类型
  2. size == -1errno == EAGAIN:属于正常业务状态,含义是当前远端暂无数据发送过来
  3. size == 0:代表通信对端主动关闭了网络连接
  4. size > 0:成功读取到有效数据,数值即为本次读取的数据长度

从数据流转层面来看,应用程序调用 recv 函数时,本质是从操作系统内核的 TCP 接收缓冲区中拉取数据,将内核缓冲区的数据不断拷贝存入我们自定义的应用层缓冲区 buf 当中。只要返回值大于 0,就说明缓冲区中已经存入有效数据,业务代码便可访问 buf 处理业务数据。

根据 IO 两个阶段的执行特性,又可以划分出同步 IO异步 IO两种模型。

同步 IO:当数据准备阶段完成、内核缓冲区数据就绪后,后续的数据读写拷贝动作,需要由应用程序主动执行完成,整个数据拷贝的耗时过程都会占用应用程序自身线程时间。

异步 IO :应用程序向操作系统发起 IO 请求之后,不会原地等待数据处理,转而继续执行自身业务逻辑。整个数据接收、数据存入缓冲区的过程全部交由操作系统内核全权处理,当内核完成全部数据读写工作后,会通过 SIGIO 信号等指定方式通知应用程序,告知数据处理完毕。典型的异步 IO 接口包含aio_readaio_write等。

我们熟知的 Node.js 高性能服务框架,底层就是依托异步非阻塞的 IO 模式实现高并发处理。

这里存在极易混淆的知识点:代码层面的阻塞、非阻塞模式,本质上都归属于同步 IO 范畴。只有调用操作系统专门提供的异步类 API,才能算作真正意义上的异步 IO。日常高频使用的 epoll 多路复用技术,从 IO 模型本质上来说,依旧属于同步 IO。

除了内核 IO 层面的定义,从业务代码逻辑角度,也有同步、异步的通俗区分逻辑。 业务逻辑中的同步:A 执行某项操作后,必须暂停自身流程,等待 B 操作全部执行完毕并拿到返回结果,才能继续往后执行自身后续逻辑。 业务逻辑中的;

异步:A 只需提前告知 B 自身关注的事件类型、事件触发后的通知方式,之后 A 不会停留等待,直接继续运行自身业务代码。当 B 监测到对应事件发生后,再主动向 A 发送通知,A 收到通知后再执行对应的数据处理逻辑。

阻塞、非阻塞、同步、异步四个词汇,都是用来描述网络 IO 运行时的不同状态属性。结合 IO 两阶段再完整梳理一遍执行逻辑: 一次标准网络 IO 分为数据准备、数据读写拷贝两大阶段。数据准备阶段主要监测远端设备是否下发数据,判断内核 socket 对应的 TCP 接收缓冲区是否存在可读数据。

当 socket 文件描述符工作在阻塞模式时,一旦应用程序调用 recv 读取数据,若此时缓冲区数据尚未就绪,当前调用线程就会被阻塞挂起,无法执行其他任务。

当 socket 文件描述符工作在非阻塞模式时,调用 recv 函数不会阻塞线程,函数会立即给出返回结果,开发者依靠返回值就能判定当前数据状态。返回值大于 0 代表成功接收远端数据;返回值等于 0 代表对端断开网络连接;返回值为 - 1 且错误码匹配 EAGAIN,是非阻塞 IO 下无数据可读的正常反馈。

一旦远端数据完成传输、内核缓冲区数据就绪,应用程序再次发起 recv 调用就可以正常执行。数据会从内核缓冲区拷贝至应用层自定义缓冲区,整个拷贝过程会消耗应用程序线程资源,程序必须等待拷贝动作结束,recv 函数才会正式返回,这整套流程就是同步 IO 的运行逻辑。

而异步 IO 的执行逻辑截然不同,程序需要调用系统提供的异步IO接口(如aio_recv),传入套接字描述符、数据缓冲区、事件通知规则等参数,后续数据监测、数据接收、数据拷贝全部由内核独立完成。内核处理完全部 IO 流程后,再主动推送通知给到应用程序即可。

总结:

一个典型的网络 IO 接口调用,分为两个阶段,分别是 "数据就绪" 和 "数据读写",数据就绪阶段分为阻塞和非阻塞,表现得结果就是,阻塞当前线程或是直接返回。

同步表示 A 向 B 请求调用一个网络 IO 接口时(或者调用某个业务逻辑 API 接口时),数据的读写都是由请求方 A 自己来完成的(不管是阻塞还是非阻塞);异步表示 A 向 B 请求调用一个网络 IO 接口时(或者调用某个业务逻辑 API 接口时),向 B 传入请求的事件以及事件发生时通知的方式,A 就可以处理其它逻辑了,当 B 监听到事件处理完成后,会用事先约定好的通知方式,通知 A 处理结果。

四大 IO 模型

1. 同步阻塞

int size = recv(fd,buf,1024,0);

总结:线程原地死等数据,等到了自己亲自读取,全程卡住不动

逻辑:标准阻塞模式,没数据就卡死线程,直到网络数据到达

2. 同步非阻塞

int size = recv(fd,buf,1024,0);(仅修改 socket 为非阻塞模式)

总结 :线程不等待、立刻返回,需要自己反复调用recv轮询检查数据

逻辑:非阻塞但同步,没数据直接返回,自己不停问 "有没有数据"

3. 异步阻塞

总结逻辑矛盾,现实中完全不存在

逻辑:异步 = 内核通知(不让你等),阻塞 = 原地死等,二者天生冲突

4. 异步非阻塞

int size = recv(fd,buf,1024,0);(仅修改 socket 为异步非阻塞模式)

总结 :线程不等待、立刻去做别的事,内核监听数据,就绪后主动通知你,再调用recv读取

逻辑:最高效模式,代码和同步阻塞一致,靠模式区分,全程不浪费资源

相关推荐
cui_ruicheng17 小时前
Linux网络编程(八):基于TCP实现CommandServer
linux·服务器·网络·tcp/ip
张小凡vip17 小时前
python的__init__.py说明
开发语言·前端·python
努力努力再努力wz17 小时前
【Redis入门系列】:从 hashtable到 listpack:深入理解 Hash 底层编码、字段级过期、核心命令与缓存应用
开发语言·数据结构·数据库·c++·redis·算法·缓存
小黑随笔17 小时前
Python asyncio 模块学习总结:从“等着”到“切出去干点别的”
开发语言·python·学习
wo32586614517 小时前
浪潮元脉网络交换机配置UDP组播路由,三层组播通信配置。使用VLC播放器搭建组播视频源,SocketTool测试工具收发测试不通后排错过程
网络·udp·音视频
qq_2949405517 小时前
Python环境搭建
开发语言·python
你觉得脆皮鸡好吃吗17 小时前
XSS渗透 COOKIE
网络·http·okhttp·网络安全学习
暗冰ཏོ17 小时前
《Vue + React + Java + PHP 项目部署到服务器完整指南》
java·服务器·vue.js·react.js·项目部署
夹芯饼干17 小时前
Linux第十三周配置网络
linux·运维·服务器