BIO,NIO,AIO初体验

       最近看了几集各种IO的视频,对于各种IO稍微有了一些感觉。

概念篇

        对于了解各种IO,首先需要了解阻塞和非阻塞的概念,以及同步和异步的概念。

        所谓阻塞和非阻塞,就是

在访问数据的时候,对于数据是否准备就绪的一种处理方式。
当数据没有准备的时候,
阻塞:需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待。
非阻塞:会直接返回,不需要等待;数据若准备好了,也直接返回。

        一个会卡在那,一个则不会

        所谓同步和非同步,就是

基于应用程序和操作系统处理IO事件锁的方式。
同步:应用程序需要直接参与IO读写操作;
处理IO事件时,阻塞在某个方法上等待我们的IO事件完成(阻塞或者轮训IO事件)
异步:交给操作系统操作;非阻塞,不需要等待;操作系统完成IO之后,通知应用程序,即可。

        一个需要自己动手,丰衣足食;一个则让别人干,自己干别的去;

        而其中,对于同步还可以分成两种,以为

1)阻塞到IO事件
阻塞到read 或者 write,完全不能做自己的事;将读写方法加入到线程里面,然后阻塞线程来实现。对线程性能开销比较大;
2)IO事件轮询 多路复用技术(select模式)
读写事件交给一个单独的线程来处理,这个线程完成IO事件的注册功能,以及不断轮询我们的读写缓冲区,看数据是否准备好。然后通知相应的读写线程。读写线程可以做其他事情,但是select这个线程会被阻塞。
Client     select            管家 BOSS
客人来的时候,找管家,说我来了,找BOSS,管家得到这个注册信息之后,告诉Boss,我这里有客人,Boss说你去给A这个东西,给B这个东西。此时,客人可以做点自己的事情,比如看看视频,看看书。当管家知道boss给他任务,他去找对应的客人,告诉他boss给他的东西。(根据注册信息)

        这俩的主要区别就是在于一个是现象(卡在那等数据和不卡在那等通知),一个是本质(因为啥都要自己干,所以只能自己等,卡在那;因为有一个中间人,他会找人给我干,干完给我通知,所以不用卡在那;因为找到最厉害的人(操作系统)干,干完给个回复就行,所以完全不用担心,不用卡在那)。

        明白上面两个概念之后,BIO(block IO),NIO(non-block IO)和AIO(asynchronous,IO),依次递增;BIO是同步阻塞的,NIO是,同步非阻塞的;AIO则是,异步非阻塞的;从名字上也能看出来。

        首先要明白,异步肯定非阻塞;其次对于同步,分为阻塞和非阻塞;也就是到底是完全卡死呢,还是还有点活路呢?

        刚刚说BIO和NIO以及AIO是依次递增,是出于对Java IO事件演进的角度来说的:

JAVA IO事件
BIO:JDK 1.4之前,用的都是BIO,阻塞IO阻塞到读写方法-->阻塞到线程来提供性能
NIO:JDK1.4,学习了linux的多路复用技术,select模式,实现IO事件的轮询方法; 同步-非阻塞的模式,主流的网络通信模式。
Mina,netty,Mina2.0 netty5.0 ---网络通信的框架,简化我们自己写NIO的复杂度,并且代码可读性高。
AIO:JDK1.7(NIO2)才是真正的异步,学习Linux的epoll模式。

        概念介绍完了,介绍一下原理吧!

NIO的网络通信原理篇

          首先,对于网络通信而言,NIO和AIO,他们并没有改变网络通信的基本步骤,只是在原来的基础(serversocket,socket)上做了改进。

          原本socket和serversocket建立连接,需要进行三次握手协议;但是三次握手的方式,建立稳定的连接,性能开销较大,解决方式(思路):减少连接的次数。

          对读写通信管道进行一个抽象。


       上图的理解,对于读和写采用抽象的管道的概念:Channel(管道),是一个TCP的抽象概念,一个TCP连接可以对应多个Channel,而不是以前的方式,只有一个通信信道,减少了TCP连接的次数;读写分离。

       大概的意思就是,socket和serversocket通过三次握手,建立一次TCP连接,TCP连接不断开,他们通过他们建立的多个管道进行访问,并且管道还有读写的区分,可以达到读写分离。所以,他们的实现主要靠管道。

NIO和AIO的网络通信架构

       NIO的通信架构


       通过selector(选择器),相当于管家,管理所有的IO事件,例如客户端的connection,服务端的accept,客户端和服务端的读写,这些IO事件;

       Selector(选择器)如何进行管理?
       当IO事件注册到选择器的时候,选择器会给他们分配一个Key值(可简单理解成一个时间标签);当IO事件完成之后,通过Key来找到相应的管道,然后通过管道发送数据和接收数据操作.

       数据缓冲区:
通过bytebuffer,提供很多读写的方法,put(),get();

       服务端:serversocketchannel
       客户端:socketchannel
       选择器:selector selector=Select.open(); 打开选择器
              Key:selectionkey,可以通过它来判断IO事件是否已经就绪
              Key.isAcceptable,是否可以接受客户端的连接
              Key.isconnectionable,是否可以连接服务器,
              Key.isreadable(),缓冲区是否可以读;
              Key.iswriteable(),缓冲区是否可写;
       如何获得事件的keys?
              SelectionKey key = Selector.selectedkeys();
       如何注册?

              以下分别是在将IO(读,写,客户端的连接,服务端的接受)事件注册到选择器上。 
              Channel.regist(selector,selectionkey.OP_Write);
              Channel.regist(selector,selectionkey.OP_Read);
              Channel.regist(selector,selectionkey.OP_Connect);
              Channel.regist(selector,selectionkey.OP_Accept);

        AIO通信架构

       API主要有:
              服务端:AsynchronousServerSocketChannel
              客户端:AsynchronousSocketChannel

       用户处理器:CompletionHandler接口,这个接口实现应用程序向操作系统发起IO请求,当完成后处理具体逻辑,否则做自己该做的事. completed和failed方法

       客户端发起连接请求,会由用户处理器(CompletionHanlder)来接收注册。而服务器会监听连接,处理器直接通知服务器的内核,内核完成IO操作,会通知用户处理器,而处理器根据注册事件来通知客户端。

实践篇 

       对于上面的NIO和AIO架构有了简单的了解之后。可以写点简单的实例;

       对于NIO,首先是服务端;

package nio;

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 NIOServer {

    private int flag = 1;
    //块大小
    private int blockSize = 4096;

    //发送数据的缓冲区
    private ByteBuffer sendBuffer = ByteBuffer.allocate(blockSize);

    //接收数据缓冲区
    private ByteBuffer receiveBuffer = ByteBuffer.allocate(blockSize);

    //选择器
    private Selector selector;

    //构造方法初始化
    public NIOServer(int port) throws IOException {
        //打开管道,并设置管道为非阻塞的
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);

        //管道连接到服务端的socket,绑定ip和端口
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(new InetSocketAddress(port));

        //打开选择器,并将管道注册到选择器上
        selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //服务器启动完成;
        System.out.println("Server start->"+port);

    }

    //监听
    public void listen() throws IOException {
        while(true) {
            //轮询选择器,获得事件列表
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while(iterator.hasNext()) {
                SelectionKey key = iterator.next();
                //事件取到,则从迭代器中移除
                iterator.remove();
                //业务逻辑
                handleKey(key);
            }
        }
    }

    //业务逻辑处理
    public void handleKey(SelectionKey selectionKey) throws IOException {
        ServerSocketChannel server = null;
        SocketChannel client = null;

        String receviedText;
        String sendText;

        int count = 0;
        //若io事件可以接收客户端的连接
        if(selectionKey.isAcceptable()) {
            server = (ServerSocketChannel)selectionKey.channel();
            client = server.accept();
            client.configureBlocking(false);

            System.out.println("服务端可以接收到客户端的信息:");
            //读事件
            client.register(selector,SelectionKey.OP_READ);
        }else if(selectionKey.isReadable()) {
            client = (SocketChannel)selectionKey.channel();
            //读取到缓冲区
            count = client.read(receiveBuffer);
            if(count > 0) {
                receviedText = new String(receiveBuffer.array(),0,count);
                System.out.println("服务端接收到客户端的信息:"+receviedText);

                //然后客户端注册一个写的事件
                client.register(selector,SelectionKey.OP_WRITE);
            }
        }else if(selectionKey.isWritable()) {
            //清除写的缓冲区
            sendBuffer.clear();
            client = (SocketChannel)selectionKey.channel();
            sendText = "msg send to client"+flag++;
            sendBuffer.put(sendText.getBytes());
            //发送缓冲区的数据
            sendBuffer.flip();
            client.write(sendBuffer);
            System.out.println("服务端发送数据给客户端:"+sendText);
        }

    }


    public static void main(String[] args) throws IOException {
        int port = 7080;
        NIOServer server = new NIOServer(port);
        server.listen();
    }
}
       服务端在7080端口上,并且开始了监听连接。

       而客户端,则连接到到7080端口上,开始了读写的请求

package nio;

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


public class NIOClient {
    private static int flag = 1;
    //块大小
    private static int blockSize = 4096;

    //发送数据的缓冲区
    private static ByteBuffer sendBuffer = ByteBuffer.allocate(blockSize);

    //接收数据缓冲区
    private static ByteBuffer receiveBuffer = ByteBuffer.allocate(blockSize);

    //服务器地址
    private final static InetSocketAddress serverAddress = new InetSocketAddress("127.0.0.1", 7080);

    public static void main(String[] args) throws IOException {
        //客户端的管道打开,设置为非阻塞
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);

        //选择器,客户端注册连接服务器事件
        Selector selector = Selector.open();
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        socketChannel.connect(serverAddress);

        Set<SelectionKey> selectionKeys;
        Iterator<SelectionKey> iterator;
        SelectionKey selectionKey;
        SocketChannel client;
        //接收发送数据
        String receviedText;
        String sendText;
        int count;

        //监听
        while (true) {
            //不断获得选择器
            selector.select();
            selectionKeys = selector.selectedKeys();
            iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                selectionKey = iterator.next();

                //若IO事件是可以连接的
                if (selectionKey.isConnectable()) {
                    System.out.println("客户端发起连接 client connect");

                    client = (SocketChannel) selectionKey.channel();
                    if (client.isConnectionPending()) {
                        client.finishConnect();
                        System.out.println("客户端完成连接操作");
                        //发送消息
                        sendBuffer.clear();
                        sendBuffer.put("hello server".getBytes());
                        //写到缓冲区
                        sendBuffer.flip();
                        //将缓冲区的数据发送出去
                        client.write(sendBuffer);
                    }
                    //注册读的事件
                    client.register(selector, SelectionKey.OP_READ);
                }

                if (selectionKey.isReadable()) {
                    //可读,服务端发送过来的数据
                    client = (SocketChannel) selectionKey.channel();
                    //先读到缓冲区,然后显示
                    receiveBuffer.clear();
                    count = client.read(receiveBuffer);
                    if (count > 0) {
                        receviedText = new String(receiveBuffer.array(), 0, count);
                        System.out.println("客户端接收到服务端的信息:" + receviedText);
                    }

                    //注册写事件
                    client.register(selector, SelectionKey.OP_WRITE);
                }

                if (selectionKey.isWritable()) {
                    client = (SocketChannel) selectionKey.channel();
                    sendBuffer.clear();
                    sendText = "msg send to server-->" + flag++;
                    sendBuffer.put(sendText.getBytes());
                    sendBuffer.flip();

                    client.write(sendBuffer);
                    System.out.println("客户端发送信息给服务端:" + sendText);
                    client.register(selector, SelectionKey.OP_READ);
                }
            }

            selectionKeys.clear();
        }
    }
}
       效果就是这样:分别启动服务器和客户端,然后发现两边在不停的通信;



        而AIO的服务端

package nio.aio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;

/**
 * AIO的服务端
 * 主要监听,监听客户端,并且接收客户端的数据
 */
public class AIOServer {

    public AIOServer(int port) throws IOException {
        AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(port));
        listener.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
            @Override
            public void completed(AsynchronousSocketChannel ch, Void vi) {
                listener.accept(null,this);//接收下一个连接
                handler(ch);
            }

            @Override
            public void failed(Throwable exc, Void vi) {
                System.out.println("异步IO失败");

            }
        });
    }

    //真正的逻辑
    private void handler(AsynchronousSocketChannel ch) {
        ByteBuffer buffer = ByteBuffer.allocate(32);
        //从管道中读取
        try {
            ch.read(buffer).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        buffer.flip();
        System.out.println("服务端接收客户端的数据:"+buffer.get());

    }

    public static void main(String[] args) throws IOException, InterruptedException {
        //启动服务器
        int port = 7080;
        AIOServer server = new AIOServer(port);
        System.out.println("监听端口"+port);
        Thread.sleep(100000);
    }
}
       AIO的客户端

package nio.aio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;


public class AIOClient {

    private AsynchronousSocketChannel client = null;

    public AIOClient(String host,int port) throws IOException, ExecutionException, InterruptedException {
        client = AsynchronousSocketChannel.open();
        Future<?> future = client.connect(new InetSocketAddress(host,port));
        System.out.println(future.get());

    }

    private void write(Byte b) {
        ByteBuffer buffer = ByteBuffer.allocate(32);
        buffer.put(b);
        buffer.flip();
        client.write(buffer);
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException, IOException {
        AIOClient client = new AIOClient("localhost",7080);
        client.write((byte) 1);
        Thread.sleep(10);

        client = new AIOClient("localhost",7080);
        client.write((byte) 29);
        Thread.sleep(10);

        client = new AIOClient("localhost",7080);
        client.write((byte) 30);
    }
}
     最终效果



      以上就是我对各种IO的初步体验了,主要还是各种不明白,不过不妨碍我继续学下去,加油!






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值