网络编程之NIO(二)

一.NIO定义

    Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。学习NIO首先要知道其有两个核心,一个是ByteBuffer字节缓冲区,一个是Selector通道选择器

二.ByteBuffer字节缓冲区

1.ByteBuffer设计原理

在这里插入图片描述
2.demo测试

import java.nio.ByteBuffer;
/**
 * Created by ${ligh} on 2019/2/22 下午4:54
 *
 *  图解参考 https://www.processon.com/
 */
public class TestBufferDemo {
    public static void main(String[] args) {

        //1.创建ByteBuffer
        byte[] bytes = new byte[5];
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        showMetircs("初始化状态",buffer);  //打印结果: 0 5 5
        //写入字节
        buffer.put((byte) 'a');
        buffer.put((byte) 'b');
        showMetircs("写入两个字节之后",buffer);  //打印结果: 2 5 5
        //遍历读取数据
       /* while (buffer.hasRemaining()){
            System.out.print(buffer.get()+" "); //读取到的是 0 0 0
        }
        System.out.println();*/

        //为了读取到添加进去的a,b两字节,调用flip()方法
        buffer.flip();
        //遍历读取数据
        while (buffer.hasRemaining()){
            System.out.print(buffer.get()+" "); //读取到的是 97 98 此时position和limit的值都是2,不能再存储数据了,因为没有缓冲区了
        }
        System.out.println();
        //查看此时buffer中属性的值
        showMetircs("读完a,b两字节之后",buffer);   //打印结果 2 2 5 表示pos和limit的之间没有存储空间了
        //调用clear()方法,然后添加字节c
        buffer.clear();
        buffer.put((byte)'c');
        showMetircs("写完c字节之后",buffer);  //打印结果 1 5 5
        //遍历添加完c字节之后的缓冲区buffer
        while (buffer.hasRemaining()) {
            System.out.print(buffer.get() + " ");  //读取到的数据是 98 0 0 0
        }
        //如果想读到新添加的字节c也就是98只需要调用flip即可
    }
    public static void showMetircs(String state,ByteBuffer buffer){
        System.out.println(state+ " pos: "+buffer.position()+" ,limit: "+buffer.limit()+" ,capacity: "+buffer.capacity());
    }
}

三.Selector通道选择器

1.定义:

    Selector会自动把注册队列中的附件封装成一个个的SelectionKey放在事件队列中,然后一直遍历事件队列获得SelectionKey对应的通道类型或者附件,然后执行相应的IO操作类型是accept还是read或者write,最后执行完成之后把事件队列中的SelectionKey移除,注册队列不进行操作,继续监控.作用就是辅助检查Channel状态,将就绪的IO提供给线程。

2.设计原理
在这里插入图片描述
三.NIO编程套路

1.服务端:

创建ServerSocketChannel
绑定监听端口
设置通道非阻塞
创建通道选择器
注册事件类型
迭代遍历事件列表

2.客户端:

创建客户端实例 Socket s = new Socket();
连接服务器 s.connect(new InetSocketAddress(host,port));
发送请求 OutputStream out = s.getOutputStream();
获取响应 InputStream result = s.getInputStream();
关闭资源 s.close();

四.实战demo演练

1.服务端:

import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
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.Date;
import java.util.Iterator;
/**
 *  服务端
 *
 */
public class NioServerBootstrap {
    public static void main(String[] args) throws Exception{
        //1.创建ServerSocketChannel
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //2.绑定监听端口
        ssc.bind(new InetSocketAddress(9999));
        //3.设置通道非阻塞
        ssc.configureBlocking(false);
        //4.创建通道选择器
        Selector selector = Selector.open();
        //5.注册事件类型
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        //6.遍历事件列表
        while (true){
            System.out.println("------我在9999-------");
            int n = selector.select();
            //判断是否阻塞 0表示阻塞
            if(n>0){ //n大于0表示有事件需要处理
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
                    //遍历获取每一个key
                    SelectionKey key = iterator.next();
                    //处理对应的IO事件
                    if(key.isAcceptable()){
                        System.out.println("------isAcceptable------");
                        //获取通道
                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                        //调用channel的转发
                        SocketChannel socketChannel = channel.accept();//立即返回一个非null的SocketChannel

                        //设置通道非阻塞
                        socketChannel.configureBlocking(false);
                        //注册到通道选择器注册队列
                        socketChannel.register(selector,SelectionKey.OP_READ);
                    }else if(key.isReadable()){
                        System.out.println("------isReadable------");
                        //读就绪的情况下进入
                        SocketChannel channel = (SocketChannel) key.channel();
                        //读事件
                        //用户意图
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        ByteArrayOutputStream req = new ByteArrayOutputStream();

                        while (true){
                            buffer.clear();
                            int num = channel.read(buffer);
                            if(num == -1) break;
                            buffer.flip();
                            req.write(buffer.array(),0,num);
                        }
                        channel.register(selector,SelectionKey.OP_WRITE,req);

                    }else if(key.isWritable()){
                        System.out.println("------isWritable-----");
                        //写事件
                        //获取附件
                        ByteArrayOutputStream req = (ByteArrayOutputStream) key.attachment();
                        ByteBuffer buffer = ByteBuffer.wrap((new Date().toLocaleString()).getBytes());
                        SocketChannel channel = (SocketChannel) key.channel();
                        channel.write(buffer);
                        //告诉通道写结束
                        channel.shutdownOutput();
                        //关闭通道
                        channel.close();
                    }
                    //移除已经处理的事件
                    iterator.remove();
                }
            }else {
                continue;
            }
        }
    }
}

2.客户端:

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
 * Created by ${ligh} on 2019/2/22 上午10:08
 *
 * 在NIO中服务端不一样,但是客户端几乎都是差不多的
 *
 * 客户端
 */
public class Clientbootstrap {
    public static void main(String[] args) throws Exception{
        //1. 创建客户端实例
        Socket s = new Socket();
        //2. 连接服务器
        s.connect(new InetSocketAddress("127.0.0.1",9999));
        //3. 发送请求
        OutputStream out = s.getOutputStream();
        PrintWriter pw = new PrintWriter(out);
        pw.println("你好,我是客户端...");
        pw.flush();

        //告诉服务器 写结束
        s.shutdownOutput();
        //4. 获取响应
        InputStream result = s.getInputStream();
        InputStreamReader isr= new InputStreamReader(result);
        BufferedReader reader= new BufferedReader(isr);

        //读取响应
        String line = null;
        StringBuffer sb = new StringBuffer();
        while ((line = reader.readLine())!=null){
            sb.append(line);
        }
        System.out.println("客户端收到: "+sb.toString());
        //5. 关闭资源
        s.close();
    }
}

五.拓展
通过NIO实现文件的拷贝,具体demo如下:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
 * Created by ${ligh} on 2019/2/22 下午5:18
 *
 */
public class TestFileCopy {
    public static void main(String[] args) throws Exception{

       // testWrite();
       // testRead();
        testCopy();
    }
    /**
     *  写文件
     *
     *  特点:
     *    应用 --> ByteBuffer --> 通道 --> 磁盘   应用将数据传到ByteBuffer中,然后通过通道将数据写到磁盘中
     *
     * @throws Exception
     */
    public static void testWrite() throws Exception{

        //所写入文件中的位置
        FileOutputStream fos = new FileOutputStream("aa.txt");

        //创建缓冲区
        ByteBuffer buffer = ByteBuffer.wrap(new byte[1024]);

        //创建通道
        FileChannel channel = fos.getChannel();

        //向缓冲区中存储数据
        buffer.put("hello world!".getBytes());
        //读到刚存进缓冲区中的数据
        buffer.flip();

        //写入到通道中
        channel.write(buffer);

        //关闭资源
        channel.close();
        fos.close();
    }

    /**
     *  读文件
     *
     *  特点:
     *      磁盘 --->  通道  ---> ByteBuffer中 ---> 应用   磁盘数据通过通道写入到ByteBuffer中,然后应用从ByteBuffer中拿读取数据
     * @throws Exception
     */
    public static void testRead() throws Exception{
        //要读取的文件
        FileInputStream fis = new FileInputStream("aa.txt");

        //创建通道
        FileChannel inchannel = fis.getChannel();

        //创建缓冲区
        ByteBuffer buffer = ByteBuffer.wrap(new byte[1024]);

        //通道读取数据到buffer中,相当于存储到其中
        int i = inchannel.read(buffer);

        buffer.flip();

        //获取buffer中的数据
        System.out.println(new String(buffer.array(),0,i));

        //关闭资源
        inchannel.close();
        fis.close();
    }

    /**
     * 文件的拷贝
     *
     *  实际就是边读边写
     *
     * @throws Exception
     */
    public static void testCopy()throws Exception{

        FileInputStream fis = new FileInputStream("aa.txt");
        FileOutputStream fos = new FileOutputStream("bb.txt");

        //创建通道
        FileChannel inchannel = fis.getChannel();
        FileChannel outchannel = fos.getChannel();

        //创建ByteBuffer
        ByteBuffer buffer = ByteBuffer.wrap(new byte[1024]);

        //开始读取文件,直到读完为止
        while (true){
            //每次读之前首先要调用clear方法
            buffer.clear();
            int i = inchannel.read(buffer);
            //如果i等于-1就表示已经读完,否则就继续读
            if(i == -1) break;

            buffer.flip();
            //写出到文件中
            outchannel.write(buffer);
        }
        //关闭资源
        inchannel.close();
        outchannel.close();
        fis.close();
        fos.close();
    }
}

总结
    原来的BIO是一种多线程阻塞的IO,非常消耗资源,由此产生了NIO,这个单线程阻塞,多线程畅通的IO,单线程阻塞情况是通道选择器中的事件队列为空的情况下,所以不会消耗很多资源,因此是一种理想化的IO处理方式,但是,为了开发简单快捷,还是不建议使用,因此由此产生了NIO的框架Netty/Mina,一种便捷式的开发框架,具体介绍请看下一篇。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值