bio linux 创建_IO进化史:BIO时代

先从一段代码讲起,这是最经典的BIO服务器实现:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CDz
 * Create: 2020-08-16 15:08
 **/
public class SocketBIO {

    public static void main(String[] args) throws IOException {
        byte[] buffer = new byte[1024];
        ServerSocket serverSocket = new ServerSocket(9090);
        System.out.println("创建server 9090");
        while (true) {
            Socket socket = serverSocket.accept();//堵塞
            System.out.println("连接" + socket.getPort());
            new Thread(() -> {
                while (true) {
                    try {
                        socket.getInputStream().read(buffer);//堵塞
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    String string = new String(buffer);
                    System.out.println(string);
                }
            }).start();
        }
    }
}

可以看到两点注释的地方:

  • serverSocket.accept();//堵塞
  • socket.getInputStream().read(buffer);//堵塞

先解释一下accept操作是做什么的:

简单说就是连接,等待客户端进行连接,当有客户端进行连接的时候,就会往下执行,当没有客户端连接时,就会一直堵塞在这里。

那么我们为了其可以支撑多个客户端连接(如果只有一个连接该多好...也就没有了什么高并发、高可用,多线程balabala的)。

但是事实上,我们不可能让一个server只支持一个连接,一个连接进来之后,除非断开就再也不能有新的连接可以进入。

为了支持多个连接,于是发展出多线程处理——一个连接对应一个线程。

形象的图形是这样的:

1b1721202a59cb4d3e26c32667ba0b9d.png

如何证明?

我们使用strace 命令来追踪,看看最终的系统调用是什么。什么?不知道系统调用是什么意思?

什么是系统调用?

  • 代码是如何执行的?

我们写的Java代码,最终会怎么样?会被编译成.class文件。这个class文件被称为字节码。最终再JVM中被翻译然后通过CPU调用。

在计算机中程序的执行又分为两种,一种是系统调用,一种是程序自己的调用。

  • 为什么分为这两种?

系统调用——kernel内核来掌管

  • 为什么要内核来掌管?

因为实在有太多的设备需要进行处理,网卡、鼠标、显示器....等等,这些不可能让写程序的每每有调用的时候都自己去写硬件调用驱动。

于是kernel就是掌管这些硬件调用的。

真正疑问点(待解决):
我们写的程序是如何让kernel内核工作的?
CPU读到特定的指令直接切换?
还是程序可以直接调用kernel提供的API?

我们写的程序想要读取文件...一些的系统资源,那么就需要通过kernel来完成。

接下来我们使用strace来追踪系统调用的过程。

将上述代码直接粘贴放入文件命名SocketBIO.java然后在Linux系统下,进行javac SocketBIO.java得到class文件。

命令:strace -ff -o out java SocketBIO

这个命令就是追踪执行过程中的系统调用,启动之后可以看到会有很多的out开头文件,这时找到文件最大的那个,进行查看

0cd1050c175bdfcf1bb6c6ca08368c47.png

vi out.16899进入文件后,我们搜索9090(端口号)找到9090一行。

dc3ed22ac47d53ce0e36729ec119b451.png

我们看到一个bind这是映入眼帘的第一个系统调用,但是肯定没有那么简单,这里是直接bind绑定了,但是什么绑定的呢?看到传入的参数第一个5,5是什么意思?kernel系统调用中所有的返回对象都是用数字来表示。

所以我们向上看,找到5是什么地方返回的。

525315c9204516b6e01861a0ccb5c47c.png

在这里,建立socket的时候返回的5,所以是bind的一个socket。

接下来往下看:

bdd9a8a1676febc14579d080192a45b9.png

梳理一下一共几个流程:

  1. 创建socket
  2. bind端口
  3. listen 1创建的socket
  4. poll等待(这里使用的poll的原因是因为我们使用Java8编译,编译器自动做了优化,在1.4的时候这里其实就是accept)

创建一个客户端:nc localhost 9090

06d03c024059a725cbfd5e612fa77385.png

建立连接之后,立马就出现了accept函数调用。

往下看:

b1a3693ebfc01acc70bedf2b8a2b760e.png

就此我们搞明白了BIO的这个过程是什么样子了,最关键点就是系统调用函数accept是堵塞的。

那么这样肯定不行啊,我们同时也看到,每次创建一个新的thread都会clone,而clone是非常消耗资源的。并且线程也不是可以无限创建的,于是就有了新的模型NIO。

这也是时代发展的产物,开最开始的时候,并没有那么多人上网,连接肯定不算特别多,使用这个完全够用,只是发展的过程中不断的进行优化(其实Java的IO能力真正来源是Linux IO)——对应到我们程序架构也是一样,这么重要的Linux系统IO连接都是如此迭代过来的,更何况我们自己做的产品呢。

接下来就是优化的过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值