深入理解网络阻塞 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;
                }

                @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 网络信息

在这里插入图片描述

命令简要解析

当然,要学习 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,订阅一波不再迷路

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

源码安装mysql

使用源码安装mysql,这里选择的版本是mysql5.7.35 ,系统是Centos7.6 官网下载地址:https://downloads.mysql.com/archives/community/ 下载源码压缩包 [rootlocalhost ~]# cd /opt[rootlocalhost opt]# wget https://downloads.mysql.com/archives/get/…

一觉醒来!Keras 3.0史诗级更新,大一统深度学习三大后端框架【Tensorflow/PyTorch/Jax】

不知道大家入门上手机器学习项目是首先入坑的哪个深度学习框架,对于我来说,最先看到的听到的就是Tensorflow了,但是实际上手做项目开发的时候却发现了一个很重要的问题,不容易上手,基于原生的tf框架来直接开发模总是有…

ssh-keygen(centos)

A—免密登陆—>B (1)A 机器,通过命令”ssh-keygen -t rsa“, 生成id_rsa,id_rsa.pub authorized_keys:存放远程免密登录的公钥,主要通过这个文件记录多台机器的公钥 id_rsa : 生成的私钥文件 id_rsa.pub : 生成的公钥文件 know_hosts : 已知的主机公钥…

javaagent字节码增强浅尝

概述 javaagent 技术广泛应用于对代码的增强,比如统计方法执行时间、GC 信息打印、分布式链路跟踪等;实现方式包括 javassist 和 bytebuddy,bytebuddy 是对 javassist 的改进;类似于 spring 中的 AOP; Instrumentati…

SpringBoot application.yml配置文件写法

1,基本介绍 (1)YAML 是 JSON 的超集,简洁而强大,是一种专门用来书写配置文件的语言,可以替代 application.properties。 (2)在创建一个 SpringBoot 项目时,引入的 spri…

231129 刷题日报

本周值班第3天,今天终于收到二面电话,一度以为挂了。。加油卷! 今天尊重下艾宾浩斯遗忘曲线,重复下前几天看的01背包,子集背包,完全背包。 416. 分割等和子集 518. 零钱兑换 II 38min做了一道新题&#…

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

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

【网络奇遇之旅】:那年我与计算机网络的初相遇

🎥 屿小夏 : 个人主页 🔥个人专栏 : 计算机网络 🌄 莫道桑榆晚,为霞尚满天! 文章目录 一. 前言二. 计算机网络的定义三. 计算机网络的功能3.1 资源共享3.2 通信功能3.3 其他功能 四. 计算机网络…

MyBatis教程之简介(一)

1、MyBatis历史 MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。 iBatis一词来源于“inter…

android framework分屏“官方”黑屏bug问题发现,你会分析吗?-千里马实战作业挑战

背景 hi,粉丝朋友们: 大家都知道马哥课程以实战为特色,这里的实战就是最贴近公司里面开发的实战项目。这些实战主要来自哪呢? 1、以前在公司的工作积累,自己在公司做过什么,这部分比较好毕竟都是搞过的 2…

常见的类 nn.Conv2d,nn.BatchNorm2D,nn.AdaptiveAvgPool2d

nn.Conv2d理论部分代码部分PaddlePaddle 版torch 版分析 nn.BatchNorm2D理论部分代码部分PaddlePaddle 版Torch 版分析PaddlePaddle 版Torch 版 nn.AdaptiveAvgPool2d理论部分代码部分PaddlePaddle 版分析Torch 版 可以到适配的飞桨公开项目更好的理解:练习 PyTorch…

浅谈安科瑞ASJ继电器在菲律宾矿厂的应用

摘要:对电气线路进行接地故障保护,方式接地故障电流引起的设备和电气火灾事故越来越成为日常所需。针对用户侧主要的用能节点,设计安装剩余电流继电器,实时监控各用能回路的剩余电流状态。通过实时监控用能以及相关电力参数、提高…

分布式搜索引擎elasticsearch

目录 一、了解es1.1、elasticsearch作用1.2 ELK技术栈 二、倒排索引2.1 正向索引2.2倒排索引 三、es的概念3.1 文档和字段3.2 索引和映射3.3 mysql与elasticsearch 一、了解es 1.1、elasticsearch作用 elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功…

linux环境下编译安装OpenCV For Java(CentOS 7)

最近在业余时间学习了一些有关图像处理的代码,但是只能本地处理,满足不了将来开放远程服务的需求。 因此,查找并参考了一些资料,成功在centos7环境安装上了opencv 460。 下面上具体安装步骤,希望能帮到有需要的同学。 …

Java中xml映射文件是干什么的

Java中的XML映射文件主要用于将Java对象与XML文档之间进行转换。它通常用于处理数据交换和存储,例如将Java对象转换为XML格式以便在网络上传输或保存到文件中,或者将XML文档解析为Java对象以进行处理。这种转换可以通过Java的JAXB(Java Archi…

FIORI /N/UI2/FLP 始终在IE浏览器中打开 无法在缺省浏览器中打开

在使用/N/UI2/FLP 打开fiori 启动面板的时候,总是会在IE浏览器中打开,无法在缺省浏览器打开 并且URL中包含myssocntl 无法正常打开 启动面板 这种情况可以取消激活ICF节点/sap/public/myssocntl

【开源】基于Vue.js的医院门诊预约挂号系统的设计和实现

项目编号: S 033 ,文末获取源码。 \color{red}{项目编号:S033,文末获取源码。} 项目编号:S033,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 功能性需求2.1.1 数据中心模块2.1.2…

酷开系统 | 酷开科技聚焦价值人群 助力营销增长

2023年,是消费复苏回暖的一年,市场中充溢着大量品牌重启增长的机遇与实例。品牌商期望能够把握住市场趋势,通过营销获得确定性的业绩提升,并在未来收获长期稳定的增长。作为数字媒介的代表之一,OTT大屏营销的属性和价值…

Java数据结构之《合并线性表》问题

一、前言: 这是怀化学院的:Java数据结构中的一道难度中等偏下的一道编程题(此方法为博主自己研究,问题基本解决,若有bug欢迎下方评论提出意见,我会第一时间改进代码,谢谢!) 后面其他编程题只要我…

【腾讯云 HAI域探秘】借助高性能应用HAI——我也能使用【stable diffusion】制作高级视频封面了

目录 高性能应用服务HAI_GPU云服务器的申请与服务创建 官网地址:高性能应用服务HAI_GPU云服务器_腾讯云 通过高性能应用服务HAI——创建【stable diffusion】 WebUI效果: 服务器后台效果: stable-diffusion服务测试 启动接口服务 配置…