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

这篇具有很好参考价值的文章主要介绍了深入理解网络阻塞 I/O:BIO。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

目录

前言

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) 模型,在默认的不加任何附加值的情况下,所有的套接字都是阻塞的,以数据报套接字作为例子,如下:

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

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

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

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

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

图解分析

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

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

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

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

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

源码实践

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

out.28980 文件的内容如下:

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

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

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

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

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

Server Accept:分配的 fd 为 5

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

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

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

BIO 为什么速度慢?

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

当有新的客户端连接时,在 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 参数选项输出到指定的文件中

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

  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,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!文章来源地址https://www.toymoban.com/news/detail-752188.html

到了这里,关于深入理解网络阻塞 I/O:BIO的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 学习网络编程No.2【深入理解TCP/IP】

    北京时间:2023/8/9/13:04,昨天在摆烂中把网络基础相关知识的博客更新,依然还是上不了C站热榜,我估计是因为我账号热度不够没有上榜资格,也可能是因为前段时间没有积极更新,导致周榜被甩出100名开外,反正应该是因为更新文章不够积极导致现在怎样都上不了热榜,不

    2024年02月13日
    浏览(45)
  • 网络编程——深入理解TCP/IP协议——OSI模型和TCP/IP模型:构建网络通信的基石

    TCP/IP协议,即 传输控制协议/互联网协议 ,是一组用于在计算机网络中实现通信的协议。它由两个主要的协议组成:TCP(传输控制协议)和IP(互联网协议)。TCP负责确保数据的可靠传输,而IP则负责路由数据包以在网络中传递。TCP/IP协议簇还包含其他辅助协议,如UDP(用户数

    2024年02月14日
    浏览(57)
  • Java网络编程-深入理解BIO、NIO

    BIO BIO 为 Blocked-IO(阻塞 IO),在 JDK1.4 之前建立网络连接时,只能使用 BIO 使用 BIO 时,服务端会对客户端的每个请求都建立一个线程进行处理,客户端向服务端发送请求后,先咨询服务端是否有线程响应,如果没有就会等待或者被拒绝 BIO 基本使用代码: 服务端: 客户端:

    2024年02月04日
    浏览(40)
  • 【netty系列-01】深入理解网络通信基本原理和tcp/ip协议

    Netty系列整体栏目 内容 链接地址 【一】深入理解网络通信基本原理和tcp/ip协议 https://zhenghuisheng.blog.csdn.net/article/details/136359640 【二】深入理解Socket本质和BIO https://zhenghuisheng.blog.csdn.net/article/details/136549478 在最初的网络中,是借鉴于这个OSI七层网络模型,而在实际开发应用中

    2024年03月17日
    浏览(62)
  • 深入理解网络非阻塞 I/O:NIO

    🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者 📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代 🌲文章所在专栏:网络 I/O 🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识 💬 向我询问任何您想

    2024年02月05日
    浏览(30)
  • 【Netty专题】【网络编程】从OSI、TCP/IP网络模型开始到BIO、NIO(Netty前置知识)

    我是有点怕网络编程的,总有点【谈网色变】的感觉。为了让自己不再【谈网色变】,所以我想过系统学习一下,然后再做个笔记这样,加深一下理解。但是真要系统学习,其实还是要花费不少时间的,所以这里也只是简单的,尽可能地覆盖一下,梳理一些我认为比较迫切需

    2024年02月06日
    浏览(62)
  • TCP/IP体系结构深入理解

    TCP/IP协议集把整个网络分成四层,包括网络接口层、网际层、传输层和应用层。 1.网络接口层 网络接口(Network Interface)和各种通信子网接口,屏蔽不同的物理网络细节; (1)ARP协议: 地址解析协议(Address Resolution Protocol):实现IP地址向物理地址的映射; (2)RARP协议:

    2024年02月05日
    浏览(42)
  • 深入理解TCP/IP协议栈及其应用

            TCP/IP协议栈是当今互联网世界中广泛应用的网络通信协议,它将数据分为若干个分组,通过网络传输到目的地,确保数据的可靠传输。对于计算机科学专业的学生以及从事网络通信相关行业的从业者而言,深入理解TCP/IP协议栈及其应用是必不可少的技能之一。  

    2024年02月14日
    浏览(46)
  • 网络通信深入解析:探索TCP/IP模型

            你知道在我们的网页浏览器的地址当中输入url,未必是如何呈现的吗?         web浏览器根据地址栏中指定的url,从web服务器获取文件资源(resource)等信息,从而显示出web页面。web使用HTTP(超文本传输协议)的协议作为规范,完成从客户端从服务器端等一系列的运

    2024年02月07日
    浏览(61)
  • 「网络编程」网络层协议_ IP协议学习_及深入理解

    「前言」文章内容是网络层的IP协议讲解。 「归属专栏」网络编程 「主页链接」个人主页 「笔者」枫叶先生(fy) IP指网际互连协议, Internet Protocol 的缩写,是TCP/IP体系中的网络层协议。 IP协议位于网络层 网络层解决的问题 传输层 : 负责两台主机之间的数据传输。代表协议:

    2024年02月08日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包