深入理解网络阻塞 I/O:BIO
目录
前言
Unix/Linux 下可用的 I/O 模型有以下五种:
- 阻塞式 I/O
- 非阻塞式 I/O
- I/O 复用(select、poll)
- 信号驱动式 I/O(SIGIO)
- 异步 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;
}
@Override
public 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 网络信息
BIO 为什么速度慢?
当有新的客户端连接时,在 BIO 模型中会频繁的调用 accept 系统函数,建立理解和 clone 出新的线程去处理读写请求
BIO 最大的弊端就是阻塞、消耗资源,每当新的客户端连接进来时,都会新建一个线程去读写数据,当 CPU 核数较小时就会造成线程之间切换来切换去,造成线程任务频繁的调度
内核基于 BIO 模型即
阻塞
命令简要解析
当然,要学习 Linux 中内核一些核心参数命令的使用,可以借助 man pages 帮助文档来进行阅读
man pages:yum install man
pthread man pages:yum -y install man-pages
strace
Linux 中 strace 命令能够很方便的帮助到你追踪到一个程序所执行的系统调用信息
查看 strace 使用文档:man strace
在最简单的情况下,strace 运行指定的命令直到退出,它拦截并记录进程所调用的系统调用、进程所接收的信号
每个系统调用的名称,它的参数和返回值都会被打印到标准错误或者用-o 参数
选项输出到指定的文件中
它有很多的参数选项,如下:
- -a column:对齐特定列中的返回值(默认列 40)
- -i:在系统调用时打印指令指针
- -o filename:将跟踪输出写入文件的文件名中,而不是写入到 stderr 标准错误;如果同时提供了 -ff 选项,则使用 pid 文件的形式通过管道的方式进行传输写入
- -A:以追加的模式打开 -o 选项中提供的文件
- -q:抑制有关附加、分离等信息,当输出被重定向到文件并且直接运行命令而不是附加命令时,会发生这种情况
- -qq:如果给出两次,则抑制有关进程退出状态的消息
- -r:在进行每个系统调用时打印一个相对时间戳,记录了连续系统调用开始的时间差
- -s strsize:指定要打印的最大字符串的大小(默认为 32)
- -t:用挂钟时间作为每一行跟踪的前缀
- -tt:若给出两次,打印的时间将包括微妙
- -ttt:若给定三次,则打印的时间将包括微妙,并且前导部分将作为自 epoch 以来的秒数打印
- -T:显示花费在系统调用上的时间,这将记录每个系统调用开始和结束之间的时间差
- -x:以十六进制字符串格式打印所有的非 ascii 字符串
- -xx:以十六进制字符串格式打印所有字符串
- -X format:设置命名变量和标志的打印格式,支持的格式值有:
raw:未经解码的原始数字输出
abbrev:输出一个命名的常量或一组标志,而不是找到的原始数字,这是默认的字符行为
verbose:输出原始值和解码后的字符串
- -y:打印与文件描述符参数关联的路径
- -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、目标端口
相关文章:

TCP协议-TCP连接管理
TCP协议是 TCP/IP 协议族中一个非常重要的协议。它是一种面向连接、提供可靠服务、面向字节流的传输层通信协议。TCP(Transmission Control Protocol,传输控制协议)。

计算机网络TCP/IP协议-从双绞线到TCP
消息响应也是同理,这种带端口的消息发送方式,其实就是UDP协议,UDP简单粗暴,但是UDP存在很多问题,所以我们需要设计一个稳定可靠的协议,TCP协议,首先,网络是不稳定的,我们发送的消息很有可能会在中途丢失,所以需要设置重试机制,当消息发送失败时重新发送,为了判断是否成功,还需要要求接收方收到消息后,必须发送确认消息,这样就可以保证消息必达,另外大段的内容发送,很容易造成部分丢失,导致全部内容都要重新发送,于是我们可以将数据分包,分成多个包发送。到这,也行你会发现了,演示中的IP地址是怎么设置的呢?

Java之网络通信框架mina
mina是一个基于java nio的网络通信框架。主要屏蔽了网络通信的一些细节,对Socket进行封装,并且是NIO的一个实现架构,可以帮助我们快速的开发网络通信,常用于游戏的开发、中间件服务端的程序中。Apache的Mina(Multipurpose Infrastructure Networked Applications)是一个网络应用框架,可以帮助用户开发高性能和高扩展性的网络应用程序;它提供了一个抽象的、事件驱动的异步API,使。

CSS局限属性contain:优化渲染性能的利器
在网页开发中,优化渲染性能是一个重要的目标。CSS局限属性contain是一个强大的工具,可以帮助我们提高网页的渲染性能。本文将介绍contain属性的基本概念、用法和优势,以及如何使用它来优化网页的渲染过程。

Docker网络详解
如何自定义一个网络?查看新创键的mynet详细信息:# 创建两个使用相同自定义网络的容器# 测试容器互连^C^C。

DNS轮询解析是什么?
在其最简单的实现中,轮回DNS的工作方式是,不仅用一个潜在的IP地址来响应DNS请求,而且用一个潜在的IP地址列表来响应承载相同服务的几个服务器。传统的负载均衡技术通常需要专门的硬件或软件,但DNS轮询解析是一种负载分配、负载平衡或容错技术,通过管理域名系统(DNS)对来自客户计算机的地址请求的响应,按照适当的统计模型,提供多个冗余的互联网协议服务主机,将流量分散到多个服务器上。因此,虽然轮询DNS是一种简单有效的负载平衡方法,但它也存在一些限制和潜在的问题,需要根据实际情况进行选择和使用。

一文搞懂网络OSI网络模型
在互联网技术里,有两件事最为重要,一个是TCP/IP协议,它是万物互联的事实标准;另一个是Linux操作系统,它是推动互联网技术走向繁荣的基石。在网络编程中最重要的模型便是OSI七层网络模型和TCP/IP四层网络模型七层模型,也称为OSI(Open System Interconnection)参考模型,是国际标准化(ISO)指定的一个用于计算机或通信系统间互联的标准体系。建立七层模型的主要目的是为解决各种网络互联时遇到的兼容性问题。

常见的几种网络抓包及协议分析工具
网络工程师必备技能-抓取网络数据。在本篇博客中,我们将集中记下几个问题进行探讨:Wireshark 是免费的抓取数据包、分析数据包的工具,兼容 Windows、Linux、Mac等主流平台。使用 wireshark 抓包需要的工具是:安装了 wireshark 的 PC。wireshark 抓包的范围是:抓取安装了 wireshark 的 PC 本机的网卡上流经的数据包。其中,网卡指的是 PC 上网使用的模块,常见的包括:以太网网卡、wifi 无线网卡,PC 分别使用它们用于连接以太网、wifi 无线网络。

为什么网线接法要分交叉连接和直连连接两种方式
水晶头有两种连接方式T568A和T568B。网线的两头都使用同一标准连接就是直连线,两头使用不同的标准就是交叉线。

手机的ip地址是固定的吗,每个手机ip地址一样吗?
简单点说,路由器开启了“DHCP功能”,会自动给连接路由器网络的设备自动分配IP地址,这包括有线网络和无线网络;当设备开启了DHCP功能之后,在路由器开启了DHCP功能的前提下,就会自动接收路由器分配的IP地址,不需要用户手动设置静态IP地址,用通俗的话来形容“DHCP”,它可以省去用户手动设置IP地址的过程。

配置nginx+keepalived高可用代理数据库ip端口
需求:配置nginx+keepalived高可用反向代理数据库ip端口(数据库服务器无法增加新SCAN IP或者需要隐藏数据库IP的情况下适用)本机ip为:192.168.20.10和192.168.20.11。2.任意节点关机或重启系统,浮动ip也会自动漂移到另外节点。1.任意节点停nginx:浮动ip会自动漂移到另外节点。安装依赖包和nginx和keepalived。浮动IP为:192.168.20.20。配置keepalived.conf。两台centos7.9。

TCP服务器最多支持多少客户端连接
本文从理论和实际两个方面介绍了一个 TCP 服务器支持的最大连接数

websocket服务端本地部署
即登录cpolar官网后,点击预留,保留一个固定tcp端口地址,然后将其配置到相应的隧道中即可。这里我们用cpolar内网穿透来映射内网端口,它支持http/https/tcp协议,不限制流量,无需公网ip,也不用设置路由器,操作简单。注意:该隧道选择的是临时tcp地址和端口,24小时内会变化,如需固定tcp地址,可升级为专业套餐做tcp地址固定!cpolar安装成功后,默认会配置两个默认隧道:一个ssh隧道和一个website隧道,可自行删减或者修改。,可以查看到token码,复制并执行命令进行认证。

微信小程序之WXSS模板样式、页面配置(.json)和网络数据请求
一、WXSS 模板样式1、什么是 WXSS2、WXSS 和 CSS 的关系二、WXSS 模板样式 - rpx1、什么是 rpx 尺寸单位2、rpx 的实现原理3、rpx 与 px 之间的单位换算*三 、WXSS 模板样式 - 样式导入1、什么是样式导入2、@import 的语法格式四、WXSS 模板样式 - 全局样式和局部样式1、全局样式2、局部样式五、页面配置1、页面配置文件的作用2、页面配置和全局配置的关系3、页面配置中常用的配置项。

python基础小知识:引用和赋值的区别
通过引用,就可以在程序范围内任何地方传递大型对象而不必在途中进行开销巨大的赋值操作。不过需要注意的是,这种赋值仅能做到顶层赋值,如果出现嵌套的情况下仍不能进行深层赋值。赋值与引用不同,复制后会产生一个新的对象,原对象修改后不会影响到新的对象。如果在原位置修改这个可变对象时,可能会影响程序其他位置对这个对象的引用

mysql开启可以使用IP有权限访问
为实际的IP地址和你想要设置的密码。请小心操作,并确保你了解每个命令的作用。如果你对此有任何疑问,最好咨询经验丰富的数据库管理员。来设置或修改用户的密码。相反,你需要分两步来完成这个过程:首先创建或修改用户,并设置密码;然后授予相应的权限。用户应该能够从指定的内网IP地址访问MySQL服务器。用户已存在并且你只是想更改其密码或允许从另一个地址访问,使用。在MySQL 8.0及更高版本中,语句的语法有所变化。替换为你的内网IP地址,

基于深度学习的细胞感染性识别与判定
通过引入深度学习技术,我们能够更精准地识别细胞是否受到感染,为医生提供更及时的信息,有助于制定更有效的治疗方案。基于深度学习的方法通过学习大量样本,能够自动提取特征并进行准确的感染性判定,为医学研究提供了更高效和可靠的手段。通过引入先进的深度学习技术,我们能够实现更快速、准确的感染性判定,为医学研究和临床实践提供更为可靠的工具。其准确性和效率将为医学研究带来新的突破,为疾病的早期诊断和治疗提供更可靠的支持。通过大规模的训练,模型能够学到细胞感染的特征,并在未知数据上做出准确的预测。

TCP三次握手和四次挥手
看了大量的文章都不知道ack包,fin包,syn包是干嘛的?我搜了一些概念以及总结道一起是不是更容易理解一些方便后续面试使用

WebSocket 入门实战
这个简单示例演示了如何使用 Spring Boot 和 Spring WebSocket 创建一个基本的 WebSocket 服务。通过这个例子,可以了解 WebSocket 在实时通信中的应用,如果大家在平时工作当中有遇到需要实时推送的场景,比如大屏实时展示数据变化,就可以用这种发放时。

Docker网络配置&网络模式
网络相关概念,子网掩码、网关、规则的介绍及网络模式bridge、host详解,Dockers自定义网络配置

TCP怎么保证传输过程的可靠性?
校验和发送方在发送数据之前计算校验和,接收方收到数据后同样计算,如果不一致,那么传输有误确认应答,序列号TCP进行传输时数据都进行了编号,每次接收方返回ACK都有确认序列号。超时重试这里是引用连接管理流量控制阻塞控制..._tcp传输过程可靠性

华为路由器OSPF动态链路路由协议配置
【代码】华为路由器OSPF动态链路路由协议配置。

揭秘代理IP:原理、类型及其在大数据抓取中的作用
代理IP的原理:代理服务器作为客户端与目标网站之间的中介,当请求数据时,不是直接由用户的原始IP地址发送到目标网站,而是先发送到代理服务器。- 提升抓取效率:利用多个代理IP实现并发抓取,可以分散请求负载,提高数据采集速度,尤其是在需要大量数据或高频率访问时尤为关键。- 地域定位:某些代理IP能够提供特定地区的IP地址,这使得爬虫能够抓取特定区域的内容,比如针对不同国家或地区的本地化信息。- 绕过反爬机制:通过不断更换代理IP,爬虫可以避免因频繁访问而触发目标网站的反爬策略,从而继续高效地抓取数据。

你了解计算机网络的发展历史吗?
计算机网络是指将一群具有独立功能的计算机通过被互联起来的,在通信软件的支持下,实现的系统。计算机网络是计算机技术与通信技术紧密结合的产物,两者的迅速发展渗透形成了计算机网络技术。简而言之呢,计算机网络就是实现两台计算机相互沟通的介质。

教新手解决api-ms-win-crt-runtime-l1-1-0.dll丢失问题,简单有效修复dll文件问题
在使用电脑的过程中突然提示api-ms-win-crt-runtime-l1-1-0.dll错误的信息,那么出现在这样的问题有什么办法可以很好的解决api-ms-win-crt-runtime-l1-1-0.dll错误呢?今天就教大家解决api-ms-win-crt-runtime-l1-1-0.dll错误的几种办法,比较适合不懂电脑的萌新宝宝!如果你电脑电脑出现了这样的问题那么可以来试试吧!

websocket介绍并模拟股票数据推流
Websockt是一种网络通信协议,允许客户端和服务器双向通信。最大的特点就是允许服务器主动推送数据给客户端,比如股票数据在客户端实时更新,就能利用websocket。

怎么选择数据安全交换系统,能够防止内部员工泄露数据?
数据泄露可能给企业带来诸多风险:财产损失、身份盗窃、骚扰和诈骗、经济利益受损、客户信任度下降、法律风险和责任等,《2021年度数据泄漏态势分析报告》中显示,在数据泄露的主体中,内部人员导致的数据泄漏事件占比接近60%。飞驰云联文件安全交换系统,可以满足企业多场景下的文件交换需求,帮助企业终结多工具、 多系统并行使用的局面,减少因文件交换行为分散带来的数据管理不集中、难以管控的问题, 帮助企业内部构建统一、安全的企业数据流转通道。对于不能下载保存的数据,使用截屏、录屏的方式窃取并外泄数据;

什么?Postman也能测WebSocket接口了?
作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

Linux系统之ifconfig命令的基本使用
ifconfig是Linux中常用的网络配置工具之一,用于配置和显示网络接口的具体状况。

网页爬虫对于网络安全有哪些影响?
在当今信息爆炸的时代,网络已经成为人们获取信息、交流思想和开展业务的重要平台。然而,随着网络的普及和技术的不断发展,网络安全问题也日益凸显,其中网页爬虫对网络安全的影响不容忽视。本文将就网页爬虫对网络安全的影响进行深入分析,并提出相应的应对措施。网页爬虫,也称为网络爬虫或网络机器人,是一种自动化的程序,能够在互联网上抓取、收集和存储网页信息。总之,网页爬虫对网络安全的影响不容忽视,需要各方共同努力,采取有效的措施和策略,共同维护网络空间的安全和稳定。