IO模型

什么是IO模型

IO分为:网络IO和文件系统IO

image.png

计算机组成

image.png

内核调度不同线程的情况

image.png

1. 网络IO模型

1.1BIO

1.1.1BIO的实现和内核调用

a.BIO程序Java代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketBIO {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(9090);
        System.out.println("step1: new ServerSocket(9090)");
        while(true){
            //等待建立Socket连接
            final Socket client = server.accept();//阻塞1
            System.out.println("step2:client\t" + client.getPort());

            new Thread(new Runnable() {
                public void run() {
                    InputStream in = null;
                    try{
                        in = client.getInputStream();
                        BufferedReader reader  = new BufferedReader(new InputStreamReader(in));
                        while(true){
                            //从Socket输出流中读取数据
                            String dataline = reader.readLine();//阻塞2
                            if(null != dataline){
                                System.out.println(dataline);
                            }else {
                                client.close();
                                break;
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

b.编译

命令:/usr/java/j2sdk1.4.2_15/bin/javac SocketBIO.java
注意:使用Java1.4进行编译,1.4中的ServerSocket还是用的BIO。

image.png

c.监控程序运行

strace可以用来监控网络IO交互过程中,不同线程的系统调用。
命令:strace -ff -o out /usr/java/j2sdk1.4.2_15/bin/java SocketBIO

image.png

使用jps查看当前主机下运行的Java程序

image.png

d.查看SocketBIO调用过程中,产生的所有系统调用文件

image.png

注意:out.1573是main线程系统调用记录文件,线程号与jps命令查询的SocketBIO的线程号相同。
查看out.1573内容最后

image.png

e.监控out.1573新追加的内容

tail监控指定文件的追加内容,并输出到窗口。
命令:tail -f out.1573

image.png

f.用nc建立连接和变化

image.png
  • 运行程序的变化:
    image.png
  • main线程out.1573变化

    image.png

  • 线程对应系统命令调用文件数量的变化
    原因:client与server新建立一个连接,新的连接是一个单独的线程,需要文件记录。

    image.png

  • 查看新建立连接线程系统调用记录文件的内容

    image.png

g.端口连接情况

Local Address:当前线程的端口
Foreign Address:连接到的端口
netstat:查看当前计算机端口的连接情况
命令: netstat -natp

  • client端口连接
    client使用nc与server建立连接,client端口使用情况如下。


    image.png
  • server端口连接
    Java进程1573监控9090端口,如果有client连接9090端口,交给Java程序处理。


    image.png

h.nc发送数据和变化

image.png
  • 应用程序变化

    image.png

  • out.1636数据变化

    image.png

1.1.2 BIO的缺点和引发的问题

缺点

每一个连接都交给一个线程处理,线程里面接收数据存在阻塞,主线线程中server等待客户端的连接也存在阻塞。

引发的问题

  • 1.很多的线程,资源,创建线程clone,池
  • 2.真正的问题:阻塞
  • 3.cpu执行被中断后,这些线程有可能获取到执行权,单线程中存在阻塞,效率低,频繁切换线程浪费CPU资源。

1.1.3 BIO的整体流程图

image.png

1.2 NIO

1.2.1 BIO和NIO的不同点

image.png

1.2.2 NIO的整体流程图

image.png

1.2.3 NIO代码实现

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;

public class SocketNIO {

    public static void main(String[] args) throws Exception{

        LinkedList<SocketChannel> clients = new LinkedList<>();

        ServerSocketChannel ss = ServerSocketChannel.open();//服务器开启监听,接受客户端
        ss.bind(new InetSocketAddress(9090));
        ss.configureBlocking(false);//中点  OS  NONBLOCKING   //只让接受客户端
        //fcntl(6, F_SETFL, O_RDWR|O_NONBLOCK)    = 0

        while(true){     //接受客户端的连接

            Thread.sleep(1000);
            SocketChannel client = ss.accept();//不会阻塞? -1 NULL
            /**
             * accept 调用内核了:1.没有客户端连接进来,返回值?在BIO的时候一直卡着,但是在NIO,不卡着,返回-1
             * 如果来客户端的连接,accept返回的是这个客户端的fd  5. client  object
             * NONBLOCKING 就是代码能往下走了,只不过有不同的情况
             */

            if(client == null){

            }else{
                client.configureBlocking(false);//重点 socket(服务端的listen socket 连接请求三次握手后,
                int port = client.socket().getPort();
                System.out.println("client..port:" + port);
                clients.add(client);
            }

            ByteBuffer buffer = ByteBuffer.allocate(4096);      //可以在堆里,堆外

            /**
             * 遍历已经连接进来的客户端能不能读写数据
             */
            for(SocketChannel c : clients){     //串行化,   多线程
                int num = c.read(buffer);       // > 0 , -1,  0 不会堵塞
                if(num > 0){
                    buffer.flip();
                    byte[] aaa = new byte[buffer.limit()];
                    buffer.get(aaa);

                    String b = new String(aaa);
                    System.out.println(c.socket().getPort() + " : " + b);
                    buffer.clear();
                }
            }

        }
    }
}
  • 监控Socket连接情况
    命令: strace -ff -o out java SocketNIO

    image.png

  • 查看主函数所在配置文件


    image.png

    image.png
  • nc连接


    image.png
  • socket端口连接情况


    image.png
  • 主线程文件变化


    image.png
  • 客户端


    image.png

1.2.4 NIO的实现

注意:NIO并不是Java代码实现,而是内核实现。Java中调用了内核实现。
命令: man 2 socket
查看内核中关于socket的实现和相关操作

image.png

1.3 select,poll

1.3.1 select,poll图解

  • BIO


    image.png
  • NIO


    image.png
  • select,poll


    image.png

1.3.2 select,poll的流程

  • 1.在server的main线程中,缓存了所有的客户端连接,在NIO中是逐个遍历客户端,如果客户端有发送数据,则server处理客户端发送的数据。
  • 2.在select,poll中不需逐个遍历客户端。将客户端作为参数传递给“电话机”,“电话机”会将客户端发送数据连接返回给server,server遍历这些发送数据的客户端,减少系统调用的时间浪费。
  • 3.多路复用器。

1.3.3 select,poll的实现

命令:man 2 select
man 2 poll
查看函数的实现。


image.png

image.png

select,poll都不是Java来实现的,都是由c语言来实现的。

1.3.4 select,poll解决的问题

server不需要每次都遍历所有的客户端连接。

1.4 epoll

1.4.1 epoll图解

image.png

1.4.2 epoll原理

epoll只是在poll的基础上,添加了一个记事本,记事本可以记录当前所有连接,不需要每次都将所有的客户端连接作为参数传递过去。

1.4.3 epoll实现

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容