java的IO机制

java的IO机制
9/100
发布文章
qq_33556348
未选择文件
new

BIO\NIO\AIO

BIO

在这里插入图片描述
每一个客户都创建一个单独的线程进行IO交换
在这里插入图片描述

BIO: java线程等待操作系统返回数据
好处: 代码简单,直接
坏处: 效率不行,存在瓶颈
在这里插入图片描述
有多个客户端线程时,主线程进行阻塞等待其中的一个,效率很低

    public void serve(int port) throws IOException {
        //将ServerSocket绑定到指定的端口里
        final ServerSocket socket = new ServerSocket(port);
        while (true) {
            //阻塞直到收到新的客户端连接
            final Socket clientSocket = socket.accept();
            System.out.println("Accepted connection from " + clientSocket);
            //创建一个子线程去处理客户端的请求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try (BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))) {
                        PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true);
                        //从客户端读取数据并原封不动回写回去
                        while (true) {
                            writer.println(reader.readLine());
                            writer.flush();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

NIO

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
文件描述符目录:
0 ,1,2:输入,输出,报错
3,4:监听对应的socket接口,ipv4,ipv4
在这里插入图片描述

accept连接之后,返回对应的文件描述符
在这里插入图片描述
阻塞的方式,对面线程建立连接之后,本方获得文件描述符,对面不发消息则本方阻塞。
在这里插入图片描述

解决方式1:
每一个连接都抛出一个线程,把文件描述符扔出去,等待接收数据
在这里插入图片描述
解决方式2:
在这里插入图片描述
使用内核提供的非阻塞模式,JAVA也提供了相应的API
在这里插入图片描述
没有进入阻塞状态
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
不抛出线程,使用内核特性进行轮回询问

在这里插入图片描述

再有问题:

  • C10K文件描述符很多,每循环一次都要调一次系统调用revc,成本很高,O(n),n为客户端数目,但只有一个是有用的
注意,C10K 和 C1000K 的首字母 C 是 Client 的缩写。C10K 就是单机同时处理 1 万个请求(并发连接 1 万)的问题,而 C1000K 也就是单机支持处理 100 万个请求(并发连接 100 万)的问题。

多条路复用一次调用,直接告诉java哪个rev来了

在这里插入图片描述
使用内核的提供的Select,由内核进行查询,然后将结果返回

在这里插入图片描述
监控多个并发文件描述符,直到一个达到可读写的状态

在这里插入图片描述

多路复用器是用来判定状态的,recv还得自己读数据
这个可以理解为找了个人替咱们轮询,但咱这个线程还是要轮询的(同步),而不同于AIO那种直接由别人进行通知的方式

再再有问题:
select的固有问题,改成epoll()

适用于短连接,比如聊天服务器,问一问就好了,避免太长时间的等待
在这里插入图片描述
在等待数据的时候进行自旋检查,不断地主动询问内核是否已经准备好
与BIO的区别是使用一个单独的Selector线程对几个返回数据的客户端进行轮询,获取数据,主机不休息,适用于客户端信息发送较快的情况。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
允许单线程处理多个Channel,例如,聊天服务器

在这里插入图片描述
底层使用了操作系统的多路复用:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOPlainEchoServer {
    public void serve(int port) throws IOException {
        System.out.println("Listening for connections on port " + port);
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        ServerSocket ss = serverChannel.socket();
        InetSocketAddress address = new InetSocketAddress(port);
        //将ServerSocket绑定到指定的端口里
        ss.bind(address);
        serverChannel.configureBlocking(false);
        Selector selector = Selector.open();
        //将channel注册到Selector里,并说明让Selector关注的点,这里是关注建立连接这个事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            try {
                //阻塞等待就绪的Channel,即没有与客户端建立连接前就一直轮询
                selector.select();
            } catch (IOException ex) {
                ex.printStackTrace();
                //代码省略的部分是结合业务,正确处理异常的逻辑
                break;
            }
            //获取到Selector里所有就绪的SelectedKey实例,每将一个channel注册到一个selector就会产生一个SelectedKey
            Set<SelectionKey> readyKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = (SelectionKey) iterator.next();
                //将就绪的SelectedKey从Selector中移除,因为马上就要去处理它,防止重复执行
                iterator.remove();
                try {
                    //若SelectedKey处于Acceptable状态
                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        //接受客户端的连接
                        SocketChannel client = server.accept();
                        System.out.println("Accepted connection from " + client);
                        client.configureBlocking(false);
                        //像selector注册socketchannel,主要关注读写,并传入一个ByteBuffer实例供读写缓存
                        client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, ByteBuffer.allocate(100));
                    }
                    //若SelectedKey处于可读状态
                    if (key.isReadable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer output = (ByteBuffer) key.attachment();
                        //从channel里读取数据存入到ByteBuffer里面
                        client.read(output);
                    }
                    //若SelectedKey处于可写状态
                    if (key.isWritable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer output = (ByteBuffer) key.attachment();
                        output.flip();
                        //将ByteBuffer里的数据写入到channel里
                        client.write(output);
                        output.compact();
                    }
                } catch (IOException ex) {
                    key.cancel();
                    try {
                        key.channel().close();
                    } catch (IOException cex) {
                    }
                }
            }
        }
    }
}

AIO

  • Future 方式:即提交一个 I/O 操作请求(accept/read/write),返回一个 Future。然后您可以对 Future 进行检查(调用get(timeout)),确定它是否完成,或者阻塞 IO 操作直到操作正常完成或者超时异常。使用 Future 方式很简单,需要注意的是,因为Future.get()是同步的,所以如果不仔细考虑使用场合,使用 Future 方式可能很容易进入完全同步的编程模式,从而使得异步操作成为一个摆设。如果这样,那么原来旧版本的 Socket API 便可以完全胜任,大可不必使用异步 I/O.
  • Callback 方式:即提交一个 I/O 操作请求,并且指定一个 CompletionHandler。当异步 I/O 操作完成时,便发送一个通知,此时这个 CompletionHandler 对象的 completed 或者 failed 方法将会被调用。

在这里插入图片描述
写完程序后,主动进行通知:
在这里插入图片描述

public void serve(int port) throws IOException {
        System.out.println("Listening for connections on port " + port);
        final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open();
        InetSocketAddress address = new InetSocketAddress(port);
        // 将ServerSocket绑定到指定的端口里
        serverChannel.bind(address);
        final CountDownLatch latch = new CountDownLatch(1);
        // 开始接收新的客户端请求. 一旦一个客户端请求被接收, CompletionHandler 就会被调用.
        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(final AsynchronousSocketChannel channel, Object attachment) {
                // 一旦完成处理,再次接收新的客户端请求
                serverChannel.accept(null, this);
                ByteBuffer buffer = ByteBuffer.allocate(100);
                // 在channel里植入一个读操作EchoCompletionHandler,一旦buffer有数据写入,EchoCompletionHandler 便会被唤醒
                channel.read(buffer, buffer, new EchoCompletionHandler(channel));
            }

            @Override
            public void failed(Throwable throwable, Object attachment) {
                try {
                    // 若遇到异常,关闭channel
                    serverChannel.close();
                } catch (IOException e) {
                    // ingnore on close
                } finally {
                    latch.countDown();
                }
            }
        });
        try {
            latch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

在这里插入图片描述

三者对比

  • BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

  • NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

  • AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值