深入理解网络阻塞 I/O:BIO

在这里插入图片描述

🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者
📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代
🌲文章所在专栏:网络 I/O
🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识
💬 向我询问任何您想要的东西,ID:vnjohn
🔥觉得博主文章写的还 OK,能够帮助到您的,感谢三连支持博客🙏
😄 代词: vnjohn
⚡ 有趣的事实:音乐、跑步、电影、游戏

目录

  • 前言
  • 阻塞式 I/O 模型
  • 图解分析
  • 源码实践
    • Socket 服务端代码
    • Socket 客户端代码
    • 流程说明
  • 命令简要解析
    • strace
    • socket
    • bind
    • listen
    • accept
  • 总结

前言

Unix/Linux 下可用的 I/O 模型有以下五种:

  1. 阻塞式 I/O
  2. 非阻塞式 I/O
  3. I/O 复用(select、poll)
  4. 信号驱动式 I/O(SIGIO)
  5. 异步 I/O

在 Linux 中操作内核时,所有的无非三种操作,分别是输入、输出、报错输出

0-输入
1-输出
2-报错输出

一个输入操作通常包括两个不同的阶段:

  • 等待数据准备好
  • 从内核向进程复制数据

对于一个套接字(Socket)的输入操作,第一步通常涉及等待数据从网络中;当所等待分组到达时,它被复制到内核中的某个缓冲区,第二步就是把数据从内核缓冲区复制到应用进程缓冲区

阻塞式 I/O 模型

最流行的 I/O 模型是阻塞式 I/O (Blocking I/O) 模型,在默认的不加任何附加值的情况下,所有的套接字都是阻塞的,以数据报套接字作为例子,如下:

在这里插入图片描述

数据准备好读取的概念比较简单:要么整个数据报已经收到,要么还没有

recvfrom 函数被视为系统调用,区分应用空间、内核空间,无论它如何实现,一般都会从在应用进程空间中运行切换到在内核空间中运行,一段时间之后再切换回来

进程调用 recvfrom 其系统调用直到数据到达且被复制到应用进程的缓冲区中或者发生错误才返回。最常见的错误是系统调用被信号中断

进程从调用 recvfrom 开始到它返回的整段时间内是被阻塞的,recvfrom 成功返回后,应用进程开始处理数据报

图解分析

在这里插入图片描述

查询 TCP、Socket 网络条目信息:netstat -natp

  • 当有新的连接进来时,主线程负责执行 accept 连接客户端,clone 出一个线程去 accept/read,等待其他客户端连接时是阻塞的,读取客户端数据也是阻塞的
  • BIO 采用的处理方式:主线程阻塞去等待客户端连接,为每个客户端分配一个子线程去阻塞读取数据

在本文中,会涉及到一些函数操作,所有的函数大致操作流程如下图:

在这里插入图片描述

源码实践

Socket 服务端代码

package org.vnjohn.bio.server;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;/*** @author vnjohn* @since 2023/11/25*/
public class SocketServer {public static void main(String[] args) throws IOException {ServerSocket server = new ServerSocket(8090);System.out.println("step1: new ServerSocket(8090)");while (true) {Socket client = server.accept();System.out.println("step2:client\t" + client.getPort());new Thread(new Runnable() {Socket socket;public Runnable setSocket(Socket socket) {this.socket = socket;return this;}@Overridepublic void run() {try {InputStream inputStream = socket.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));while (true) {System.out.println(reader.readLine());}} catch (IOException e) {e.printStackTrace();}}}.setSocket(client)).start();}}
}

Socket 客户端代码

package org.vnjohn.bio.client;import java.io.*;
import java.net.Socket;/*** @author vnjohn* @since 2023/11/25*/
public class SocketClient {public static void main(String[] args) {try {Socket client = new Socket("172.16.249.10", 9090);client.setSendBufferSize(20);// false 优化,true 不优化client.setTcpNoDelay(true);client.setOOBInline(false);OutputStream out = client.getOutputStream();InputStream in = System.in;BufferedReader reader = new BufferedReader(new InputStreamReader(in));while (true) {String line = reader.readLine();if (line != null) {byte[] bb = line.getBytes();for (byte b : bb) {out.write(b);}}}} catch (IOException e) {e.printStackTrace();}}
}

流程说明

172.16.249.10 是之前作为 node1 节点所在 IP

将以上两个 java 源文件上传到 node1 虚拟节点上,所在目录:/opt/java

1、在虚拟节点上安装好 Java 环境
2、将源文件所在的 package 包名,通过 vim 命令将 package 包名删除首行.
3、将 Java 源文件进行编译为 .class 文件 > javac SocketServer.java、javac SocketClient.java

1、追踪应用程序与操作系统中的交互信息

cd /opt/java
strace -ff -o out java SocketServer

在这里插入图片描述

执行该追踪命令以后,会在 /opt/java 下生成几个 out 前缀文件,所有的 out 前缀所对应的后缀是所属的进程 pid 号

在这里插入图片描述

通过 jps 命令查看当前所运行的 SocketServer 所占用的 pid 进程,它能够对应上所输出的文件.

但实际上生成的与操作系统交互信息都不会在这个文件中,它会 clone 一个子进程去负责 accept

2、通过 vim 命令,查看对应的 out.28979 所输出的内容

在这里插入图片描述

结合以上输出的内容,我们重点是要关注 out.28980 文件的内容

在这里插入图片描述

在此处,能够发生输出的文件中出现了核心的三个网络相关函数调用,分别是:socket、bind、listen,在后一节会简要的介绍这些函数的作用

3、通过我们能构建的 node2 节点:172.16.249.11,来充当 Socket 客户端的角色,看它与服务端建立连接以后,在 out.28980 文件中会出现什么内容

在这里插入图片描述

首先是在 node2 节点通过 java 命令直接运行该 Java 程序
随即观察 node1 节点所开启的服务端窗口会出现双方建立连接成功的系统输出

在这里插入图片描述

当前 node1 服务端为其客户端分配了一个 32900 端口,进行后续两者之间的通信

out.28980 文件的内容如下:

在这里插入图片描述

通过 accept 系统调用为其客户端分配了一个 32900 端口,IP:172.16.249.11,分配的 socketfd 文件描述符为 6

4、如何观察进程的所有文件描述符信息

通过命令:ls -l /proc/28980/fd
28980 是对应的 pid 进程号

在这里插入图片描述

Server Accept:分配的 fd 为 5

Client 建立连接成功:分配的 fd 为 6

通过命令:netstat -natp 查询 Socket/TCP 网络信息

在这里插入图片描述

命令简要解析

当然,要学习 Linux 中内核一些核心参数命令的使用,可以借助 man pages 帮助文档来进行阅读

man pages:yum install man
pthread man pages:yum -y install man-pages

strace

Linux 中 strace 命令能够很方便的帮助到你追踪到一个程序所执行的系统调用信息

查看 strace 使用文档:man strace

在最简单的情况下,strace 运行指定的命令直到退出,它拦截并记录进程所调用的系统调用、进程所接收的信号
每个系统调用的名称,它的参数和返回值都会被打印到标准错误或者用 -o 参数选项输出到指定的文件中

它有很多的参数选项,如下:

  1. -a column:对齐特定列中的返回值(默认列 40)
  2. -i:在系统调用时打印指令指针
  3. -o filename:将跟踪输出写入文件的文件名中,而不是写入到 stderr 标准错误;如果同时提供了 -ff 选项,则使用 pid 文件的形式通过管道的方式进行传输写入
  4. -A:以追加的模式打开 -o 选项中提供的文件
  5. -q:抑制有关附加、分离等信息,当输出被重定向到文件并且直接运行命令而不是附加命令时,会发生这种情况
  6. -qq:如果给出两次,则抑制有关进程退出状态的消息
  7. -r:在进行每个系统调用时打印一个相对时间戳,记录了连续系统调用开始的时间差
  8. -s strsize:指定要打印的最大字符串的大小(默认为 32)
  9. -t:用挂钟时间作为每一行跟踪的前缀
  10. -tt:若给出两次,打印的时间将包括微妙
  11. -ttt:若给定三次,则打印的时间将包括微妙,并且前导部分将作为自 epoch 以来的秒数打印
  12. -T:显示花费在系统调用上的时间,这将记录每个系统调用开始和结束之间的时间差
  13. -x:以十六进制字符串格式打印所有的非 ascii 字符串
  14. -xx:以十六进制字符串格式打印所有字符串
  15. -X format:设置命名变量和标志的打印格式,支持的格式值有:

raw:未经解码的原始数字输出
abbrev:输出一个命名的常量或一组标志,而不是找到的原始数字,这是默认的字符行为
verbose:输出原始值和解码后的字符串

  1. -y:打印与文件描述符参数关联的路径
  2. -yy:打印与套接字文件描述符相关的协议特定信息,以及与设备文件描述符相关的块/字符设备号

还有一些统计指标的参数选项,可以查看帮助文档进行使用.

socket

查看 socket 命令帮助文档:man 2 socket

int socket(int domain, int type, int protocol);

包裹函数:Socket() 创建用于通信的端点并返回套接字描述符

实践部分:socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 5

bind

查看 bind 命令帮助文档:man 2 bind

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

当使用 socket 创建套接字时,它存在于名称空间中(地址族)中,但没有给它分配地址

bind() 将 addr 指定的地址分配给文件描述符 sockfd 引用的套接字,Addrlen 指定 addr 指向的地址结构大小(以字节为单位)

在传统上,bind 此操作称为 “为套接字分配名称”

实践部分:

bind(5, {sa_family=AF_INET6, sin6_port=htons(8090), inet_pton(AF_INET6, “::”, &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
5:原始套接字 sockfd
AF_INET6:协议类型
8090:原始套接字端口号

listen

查看 listen 命令帮助文档:man 2 listen
int listen(int sockfd, int backlog);
将 sockfd 引用的套接字标记为被动套接字,也就是说,将使用 accept(2) 来接受传入的连接请求

sockfd 参数是一个文件描述符,它引用 SOCK_STREAM 或 SOCK_SEQPACKET 类型的套接字

backlog 参数定义 sockfd 挂起链接队列可能增长到的最大长度,若一个连接请求在队列已满时到达,客户端可能会收到一个带有 ECONNREFUSED 指示的错误,或者,如果底层协议支持重传(TCP),请求可能会被忽略,以便稍后重试连接成功

实践部分:listen(5, 50)

监听此文件描述符,并为其分配一个长度为 50 的链接队列,队列满了以后,会有 SYN_RECV 状态的网络条目出现

accept

查看 accept 命令帮助文档:man 2 accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept() 系统调用用于基于连接的套接字类型(SOCK_STREAM、SOCK_SEQPACKET)它提取了侦听套接字 sockfd 挂起链接队列上的第一个连接请求将创建一个新连接套接字,并返回一个引用该套接字的新文件描述符;新创建的套接字不在监听范围内状态。原始套接字 sockfd 不受此调用的影响

实践部分:

accept(5, {sa_family=AF_INET6, sin6_port=htons(32900), inet_pton(AF_INET6, “::ffff:172.16.249.11”, &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, [28]) = 6
5:原始套接字 sockfd
AF_INET6:协议类型
172.16.249.11:新 sockfd 文件描述符所在地址
6:新套接字 sockfd

总结

该篇博文主要介绍的是 I/O 模型中的阻塞 I/O -> BIO,简要分析了 BIO 流程图及相关系统函数调用,通过实践代码的方式来分析阻塞 I/O 在系统调用中所涉及到的流程,最后,介绍了相关联的系统函数:strace、socket、bind、listen、accept,希望能够得到你的支持,感谢三连

四元组唯一:源 IP、源端口、目标 IP、目标端口

🌟🌟🌟愿你我都能够在寒冬中相互取暖,互相成长,只有不断积累、沉淀自己,后面有机会自然能破冰而行!

博文放在 网络 I/O 专栏里,欢迎订阅,会持续更新!

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring、MySQL,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/184494.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

零基础也可以学编程,分享中文编程工具开发软件

零基础也可以学编程,分享中文编程工具开发软件 给大家分享一款中文编程工具,零基础轻松学编程,不需英语基础,编程工具可下载。 这款工具不但可以连接部分硬件,而且可以开发大型的软件,象如图这个实例就是用…

使用功率MOSFET常见的一些问题(二)

使用功率MOSFET常见的一些问题(二) 1.栅源电压瞬变2.安全工作区3.感应导通和击穿3.1 如何避免感应导通 1.栅源电压瞬变 过大的电压瞬态会穿透薄栅源氧化层,造成永久性损坏。不幸的是,这种瞬态在电源开关电路中产生,并 …

行业研究:2023年氟化钾发展前景及细分市场分析

氟化工产品,作为化工新材料之一,在“十二五”规划被单列一个专项规划。由于产品具有高性能、高附加值,氟化 工产业被称为黄金产业。 氟是一种盐,有一种叫做钾的腐化盐,这种产品是白色结晶,易吸收&#xff0…

OSI七层参考模型及其协议和各层设备

OSI网络模型是开放系统互联(Open Systems Interconnection)参考模型,它是由国际标准化组织(ISO)制定的。这个模型将网络系统划分为七个层次,OSI网络模型的七层是:物理层、数据链路层、网络层、传…

手把手教你写IP地址规划方案

中午好,我的网工朋友。 IP地址的合理规划是网络设计的重要环节,大型计算机网络必须对IP地址进行统一规划并得到有效实施。 IP地址规划的好坏,不仅会影响到网络路由协议算法的效率,还会影响到网络的性能,网络的扩展&a…

【Spring集成MyBatis】动态sql

文章目录 1. 什么是动态sql2. 动态sql之<if>3. 动态sql之<where>4. 动态sql之<foreach>5. sql片段抽取 此篇的代码基于 【Spring集成MyBatis】MyBatis的Dao层实现&#xff08;基于配置&#xff0c;非注解开发&#xff09;续写 1. 什么是动态sql MyBatis映射…

对话特斯拉北美车主:FSD什么水平?深度用户解密V11

作者 |Amy 编辑 |德新 近期中国四部委联合印发通知&#xff0c;部署开展智能网联汽车准入和上路通行试点工作&#xff0c;要求具备量产条件L3、L4 级别智能网联车在限定区域内开展上路试点&#xff0c;并且首次明确事故责任判定。 通知下发后&#xff0c;市场传闻&#xff1a…

Antd可编辑表格初始数据为空,单元格不能编辑的解决办法

黑科技超简单: 给table表格增加行className rowClassName{() > editable-row} 然后设置可编辑表格的行样式 .editable-row:hover .editable-cell-value-wrap {border: 1px solid #d9d9d9;border-radius: 4px;padding: 4px 11px;}.editable-cell-value-wrap {padding: 5px…

Linux 代码编辑器:vim

vim 编辑器的简介 vi / vim 都是多模式编辑器&#xff0c;不同的是 vim 是 vi 的升级版本&#xff0c;他不仅兼容 vi 的所有指令&#xff0c;而且还有一些新的特性在里面。比如语法高亮&#xff0c;可视化操作不仅可以在终端运行&#xff0c;也可以在 windows&#xff0c;mac …

官宣,终于上线:Web安全测试课程重磅发布

报告给大家一个好消息!川石教育正式推出全新课程体系Web安全测试&#xff0c;下面就从安全工程师的薪资待遇、发展前景、发展路线、课程大纲、课程目标、课程特色、课程服务等方面一一介绍。   近年来&#xff0c;随着互联网时代的蓬勃发展和数字经济规模不断扩大&#xff0c…

快速入门opencv(python版)

Open Source Computer Vision Library。OpenCV是一个&#xff08;开源&#xff09;发行的跨平台计算机视觉库&#xff0c;可以运行在Linux、Windows和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C 类构成&#xff0c;同时提供了Python、Ruby、MATLAB等语言的…

知乎第三季度财报不及预期,股价今年已累计下跌26.4%

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 知乎(ZH)报告称&#xff0c;其2023财年第三季度的收入同比增长了12.1%&#xff0c;至1.401亿美元&#xff0c;但未达到市场普遍预期的1.436亿美元&#xff0c;所以在财报后其股价出现了下跌。 第三季度知乎每股美国存托股票…

基于STC12C5A60S2系列1T 8051单片机的IIC总线器件24C02实现掉电保存计时时间应用

基于STC12C5A60S2系列1T 8051单片机的IIC总线器件24C02实现掉电保存计时时间应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍液晶显示器LCD1602简单介绍IIC通信简单…

Java8 Stream API

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 Stream API与接口默认方…

Linux高级IO

文章目录 一.IO的基本概念二.钓鱼五人组三.五种IO模型四.高级IO重要概念1.同步通信 VS 异步通信2.阻塞 VS 非阻塞 五.其他高级IO六.阻塞IO七.非阻塞IO 一.IO的基本概念 什么是IO&#xff1f; I/O&#xff08;input/output&#xff09;也就是输入和输出&#xff0c;在著名的冯诺…

毕业设计单片机可以用万能板吗?

毕业设计单片机可以用万能板吗? 可以是可以&#xff0c;就是焊接起来比较麻烦&#xff0c;特别是有好几个重复连线点的时候&#xff0c;检测起来就不那么容易了&#xff0c;而且布线看起来乱糟糟的&#xff0c;如果后期一不小心把线弄断了&#xff0c;查起来就更麻烦了&#x…

内存泄漏检测工具valgrind

示例&#xff1a; class Person { public:Person(int age){//将年龄数据开辟到堆区m_Age new int(age);}//重载赋值运算符 写法2 此代码在linux测试Person& operator(Person& p){*m_Age *p.m_Age; //通过linux下valgrind工具检测&#xff0c;无内存泄漏情况。//此语…

PyBullet安装与学习

PyBullet 支持加载 URDF、SDF、MJCF 等多种机器人描述文件&#xff0c;并提供正/逆向运动学、正/逆向动力学、碰撞检测、射线相交查询等功能。 pip install pybullet 安装后会在 Python 环境的 lib/site-packages 中出现以下文件夹&#xff1a; pybullet_data&#xff1a;存放…

Java零基础——docker篇

1.【熟悉】docker简介 1.1 什么是docker Docker是一个开源项目&#xff0c;诞生于2013年初&#xff0c;最初是dotCloud公司内部的一个业余项目。它基于Google公司推出的Go语言实现。项目后来加入了Linux基金会&#xff0c;遵从了Apache2.0协议&#xff0c;项目代码在GitHub上进…

震坤行自有品牌 | 搬运存储全面打造快速选型的标准品

震坤行自有品牌 | 搬运存储全面打造快速选型的标准品 中国仓储与配送协会2023年仓储配送行业发展与趋势展望报告中指出&#xff0c;截至2022年底&#xff0c;我国营业性通用&#xff08;常温&#xff09;仓库面积约为12.2亿㎡&#xff0c;仓储业&#xff08;含装卸搬运&#x…