IO Unix IO BIO AIO NIO

走远了吗. 提交于 2020-03-10 12:57:08

IO:

io流分为字符字节两种,其实比较好认,一般字节的都是Stream结尾,字符的是Reader或Writer结尾,字符和字节转换用InputStreamReader

字符的用于读取纯文本格式,一次读一个字符,比如utf-8三个字节

字节用来读取二进制文件等,那种人看不懂的,包括图片 视频等

再者就是io流使用了典型的装饰者模式,等等我去偷张图,侵删。

可以清晰的看出FilterInputStream就是装饰者,再不改变原有类的情况下,下面三个Data/Buffered/PushbackInputStream附加实现了不同的功能,比如BufferedInputStream实现了缓冲的功能,如果想深入了解,个人建议去看 https://www.jianshu.com/p/4a530a3c70af

具体使用来个例子吧清晰一点:

复制文件

FileInputStream in = new FileInputStream(src); 

FileOutputStream out = new FileOutputStream(dist);

byte[] buffer = new byte[1024]; 

// read() 最多读取 buffer.length 个字节  返回的是实际读取的个数  返回 -1 的时候表示读到 eof,即文件尾 
while (in.read(buffer) != -1) 

{ 
    out.write(buffer); 

} 

in.close();

out.close();

如果使用Reader的话,就是读取的用readLine就好了,就不弄样例了

接下来看看

Unix IO的一些模型

先说一下一个输入操作包括,

1.等待数据准备好

2.从内核读取数据,将数据复制到应用程序缓冲内,使用的方法是recvfrom

阻塞式IO:其实很好理解,就是应用程序一直等待直到数据返回,期间不做任何事情,但只是当前程序阻塞,并不是操作系统瘫痪,所以效率还是比较高的

非阻塞式IO:应用程序发送请求,1只要没完成就返回失败,然后进行轮询,轮询就是不停的去请求,这种的其实效率不高,而且应用程序耗费了很多的调用去干轮询这件事

IO复用:使用select或者poll等待数据,等某个套接字返回的时候,就使用recvfrom复制数据,这样的好处就是一个进行就可以处理多个IO事件

信号驱动IO:这个就是发送一个信号告诉内核,数据好了通知我,等数据准备i好了,内核就会发信号给应用程序,然后再recvfrom

异步IO:这个跟信号驱动很像,区别就是内核发信号的时候,信号驱动IO是开始recvfrom,而异步IO是recvfrom已完成。

所以也好理解,同步和异步IO的区别就在于第二步的recvfrom,同步会在recvfrom阻塞,而异步IO不会。

 

BIO(同步IO)

这个方式其实就类似于伪异步方式,客户端多个请求一起过来时,服务端开启异步线程处理客户端的请求,我写了个小程序用来理解:

客户端代码:

public class SocketClient {
    public static void main(String[] args) {
        for(int count = 10; count<20;count++) {
            new Thread(new Client(count)).start();
        }

    }
    static  class Client implements Runnable{
        int count ;
        public  Client(int count){
            this.count = count;
        }
         @Override
        public void run() {
             InputStream inputStream =null;
             OutputStream outputStream =null;
             try {
                 Socket socket = new Socket("localhost", 8083);

                 inputStream =  socket.getInputStream();
                   outputStream = socket.getOutputStream();

                //发送信息
                 outputStream.write((("go---"+count+"---").getBytes()));

                 int len;
                 byte[] bs = new byte[1024];
                 while ((len = inputStream.read(bs)) != -1) {
                     String s = new java.lang.String(bs);
                     System.out.println(s);
                 }
             } catch (IOException e) {
                 e.printStackTrace();
             }finally {
                 try {
                     inputStream.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
                 try {
                     outputStream.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }
    }

服务端代码:

/**
 * 多线程实现异步IO
 */
public class SocketService {

    public static void main(String[] args) {
        try {
            int count = 0;
            ServerSocket serverSocket = new ServerSocket(8083);
            Socket accept = null;
            while (true) {
                accept = serverSocket.accept();
                //开启异步线程处理
                new Thread(new Service(accept)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static class Service implements Runnable {
        Socket accept;
        public Service(Socket accept){
            this.accept = accept;
        }
        @Override
        public void run() {
            try {
                InputStream in = accept.getInputStream();

                int len;
                byte[] bytes = new byte[1024];
                  in.read(bytes);
                String s = new String(bytes);
                System.out.println(s);
                OutputStream out = accept.getOutputStream();
                out.write(("comming---"+s+"---").getBytes());
                in.close();
                out.close();
                accept.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

 

NIO 多路复用:

实现就类似于上面所说的IO复用,使用轮询代理,具体使用要用到Selector,使用select等待请求,具体看程序:

Channel就是通道,也就是多路中的‘路’

还有一点要注意,普通的IO流使用byte[] 而NIO使用的则是缓冲BufferByte 方法allocate就类似byte数组长度,flip方法用来切换读写,以免导致脏读,clear用来清空,以免造成重复读

客户端代码比较简单:

public class NioSelectClient {

    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost", 8083);
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".getBytes());
            InputStream inputStream = socket.getInputStream();
            byte[] bytes = new byte[1024];


            inputStream.read(bytes);

            String s = new String(bytes);
            System.out.println(s);

            outputStream.close();
            inputStream.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

服务端:

* 使用选择器实现nio    将服务端的非阻塞channel注册到选择器上,选择器监听通道(channel)内的事件,然后进行处理
* 实现了一个线程监听多个事件   选择器selector监听的方式也是轮询
public class NioSelectorService {

    public static void main(String[] args) {


        try {
            //创建一个选择器
            Selector selector = Selector.open();

            //创建管道网络服务端
            ServerSocketChannel sChannel = ServerSocketChannel.open();
            sChannel.configureBlocking(false);//非阻塞
              //首次注册使用OP_ACCEPT
             //OP_CONECT已连接   OP_ACCEPT已获取数据   OP_READ表示可读取 OP_WRITE可操作   
            sChannel.register(selector, SelectionKey.OP_ACCEPT);

            ServerSocket socket = sChannel.socket();
            socket.bind(new InetSocketAddress(8083));
            while (true) {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();

                Iterator<SelectionKey> iterator = selectionKeys.iterator();


                while (iterator.hasNext()) {
                    SelectionKey next = iterator.next();


                    if (next.isAcceptable()) {
                        iterator.remove();

                        System.out.println("1-accept");
                        ServerSocketChannel channel = (ServerSocketChannel) next.channel();

                        SocketChannel accept = channel.accept();
                        accept.configureBlocking(false);
                      
                       
                        accept.register(selector, SelectionKey.OP_READ);

                    } else if (next.isReadable()) {
                        iterator.remove();
                        System.out.println("2-read");
                        SocketChannel channel = (SocketChannel) next.channel();

                        StringBuffer data = new StringBuffer();
                        ByteBuffer allocate = ByteBuffer.allocate(1024);
                        int len;

                        while ((len = channel.read(allocate)) != 0) {
                            allocate.flip();

                            int position = allocate.position();//目前读的位置
                            int capacity = allocate.capacity();//总量

                            byte[] bytes = new byte[capacity];

                            allocate.get(bytes, position, len);

                            String s = new String(bytes);
                            data.append(s);

                            allocate.clear();
                        }

                        System.out.println(data);

                        channel.write(ByteBuffer.wrap("3-返回:".getBytes()));
                        channel.close();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

除了select还可以使用poll,epoll

 select window和linux都支持     适用于要求实时性高的场景

poll  linux  适用于小量请求监控

epoll linux  linux没有实现异步IO,所以只能使用epoll模拟    适用于大量请求监控

 

AIO(异步IO)

再强调一下,linux不支持异步IO,只能通过epoll模拟

AIO采用的就是典型的异步IO ‘监听-通知’:下面这段代码可以改进,可以将读数据也弄成监听

客户端:

public class Client {

    public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {

        Socket socket = new Socket("localhost", 8083);

        OutputStream outputStream = socket.getOutputStream();

        outputStream.write("client2 go ".getBytes());

        outputStream.close();

    }
}
public class AsynchronousServiceSocket {

  static  final Object obj = new Object();

    public static void main(String[] args)  {

        ExecutorService executorService = Executors.newFixedThreadPool(6);

        try {
            //创建一个异步IO通道,绑定一个线程池
            AsynchronousServerSocketChannel open = AsynchronousServerSocketChannel.open(AsynchronousChannelGroup.withThreadPool(executorService));

            AsynchronousServerSocketChannel localhost = open.bind(new InetSocketAddress("localhost", 8083));

            //监听-通知机制 但是是一次性的
            localhost.accept(2, new ServerSocketChannelDeal(open));

            synchronized (obj){
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }


        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    static class ServerSocketChannelDeal implements CompletionHandler<AsynchronousSocketChannel, Integer> {
        private AsynchronousServerSocketChannel serverSocketChannel;

        public ServerSocketChannelDeal(AsynchronousServerSocketChannel serverSocketChannel) {
            this.serverSocketChannel = serverSocketChannel;
        }

        @Override
        public void completed(AsynchronousSocketChannel result, Integer attachment) {
            //因为是一次性的所以要每次都注册一次
            serverSocketChannel.accept(attachment, this);

            int len = 0;
            ByteBuffer bbs = ByteBuffer.allocate(50);
            StringBuffer sb = new StringBuffer();


            while (true) {
                Future<Integer> read = result.read(bbs);
                try {
                    len=  read.get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                if ( len== -1) {
                    break;
                }

                byte[] bs = new byte[50];

                int position = bbs.position();

                bbs.flip();
                 bbs.get(bs, 0, len);

                bbs.clear();

                sb.append(new String(bs));
            }

            System.out.println(sb);

            result.write(ByteBuffer.wrap("server got it".getBytes()));


        }

        @Override
        public void failed(Throwable exc, Integer attachment) {

        }
    }
}

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!