Java NIO初体验 简述加小项目体验

NIO 非阻塞 IO

1介绍

1.1 JAVA NIO

BIO:阻塞式IO,我们传统的流式IO就是阻塞式IO,意思式在读写的过程中可能发生阻塞
NIO:非阻塞IO,读写过程中不会产生阻塞。
AIO:在NIO基础上完成异步非阻塞。 NIO属于同步非阻塞。
同步:单线程上执行的操作,执行存在先后顺序。
异步:多个线程执行的操作,执行没有先后顺序。
IO发生阻塞最常见的场景出现与网络上的IO操作。好比TCP连接中Socket产生IO阻塞。
之前使用流式IO完成TCP通讯式,只能依靠多线程来完成同时多客户端交互。
那么使用了NIO后,就可以以单线程形式完成上述操作。
好处:资源开销小,运行性能好。
缺点:NIO设计不够优雅。以后有了NIO知识后,可以学习netty框架。

NIO实现(复制文件)代码演示:

public class NIODemo {
    public static void main(String[] args) throws IOException {
        /*
            从读写文件入手,初识NIO的API
            声明:虽然java对于读写本地文件也提供NIO的API,但是本身读写文件
            并不会产生阻塞问题!

            BIO是面向"流"的。它是单向操作:要么读(输入流),要么写(输出流)
            NIO是面向"通道"的。它是双向的:能读能写。
         */
        FileInputStream fis = new FileInputStream("NARUTO.JPG");
        FileOutputStream fos = new FileOutputStream("NARUTO_CP.JPG");
        //通过流获取对应的NIO通道,用通道对文件进行读写操作
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();
        /*
            NIO的读写是基于缓冲区的Buffer。
            类似与我们使用传统的流进行的"块"读写操作。
         */
        //创建一个10KB的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024*10);

        int len;//记录每次实际读取到的字节数
        while((len = inChannel.read(buffer))!=-1) {
            buffer.flip();
            outChannel.write(buffer);
            buffer.clear();
        }

        System.out.println("复制完毕");
        inChannel.close();
        outChannel.close(); 
    }
}

1.2 NIO缓存操作

读入数据装满空间 读入空间的一部分数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SQVWPDgB-1656237914892)(C:\Users\tarena\AppData\Roaming\Typora\typora-user-images\image-20220626163953324.png)]

小项目体验IO与NIO区别:

服务端代码:

/**
 * 基于NIO的聊天室服务端
 */
public class NIOServer {
    //存放所有客户端Channel,便于广播消息
    private List<SocketChannel> allChannel = new ArrayList<>();

    public void start(){
        try {
            /*
                NIO的API中对应的ServerSocket为:ServerSocketChannel
                作用:
                1:打开服务端口
                2:等待客户端的连接(非阻塞的)
                它相较于ServerSocket而言,accept操作并不会阻塞线程。
             */
            ServerSocketChannel serverSocketChannel
                                = ServerSocketChannel.open();
            //需要显示的设置为非阻塞状态,必须为非阻塞状态才能交给多路选择器监控事件
            serverSocketChannel.configureBlocking(false);
            //打开服务端口8088,客户端就可以通过8088端口进行连接了
            serverSocketChannel.bind(new InetSocketAddress(8088));
            /*
                NIO的核心API:多路选择器Selector    select:选择
                多路选择器就是一个"监控设备",可以同时监控多路"通道",一旦一个
                通道触发了某个需要处理的事件时
                比如:
                ServerSocketChannel的accept操作(一个客户端连接时,接受连接操作)
                SocketChannel的read操作(当一个客户端发送消息过来,需要读取的操作)
                (以上两点在原有的:ServerSocket的accept和Socket获取的输入流上进行
                读取操作都会产生阻塞。)
                此时多路选择器就会通知我们来进行该通道的处理动作。因此有了多路选择器
                原来会导致线程阻塞的操作全部由多路选择器接管,解除了线程阻塞的现象。
             */
            //创建一个多路选择器
            Selector selector = Selector.open();


            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            /*
                询问多路选择器,现在有没有事件待处理?
                若现在注册在多路选择器上所有通道都没有事件待处理时,该方法会阻塞
                若存在待处理的事件,此方法会立即返回一个数字,表示待处理事件的个数
             */
            while(true) {
                System.out.println("主线程等待多路选择提示是否有事件要处理...");
                selector.select();//该方法会形成阻塞
                System.out.println("多路选择器监听到有事件要进行处理!!");
                //通过多路选择器将所有待处理的事件全部获取回来进行逐一处理
                Set<SelectionKey> set = selector.selectedKeys();
                //遍历每一个事件并进行对应处理
                for (SelectionKey key : set) {
                    //使用分支判断该事件是什么事件,以便做对应的处理
                    //判断该事件是否为ServerSocketChannel中的accept事件(有客户端连接了)
                    if (key.isAcceptable()) {
                        //通过事件获取相关通道来进行对应的操作
                        //对于accept事件而言,只有ServerSocketChannel会触发。
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                    /*
                        通过ServerSocketChannel的accept得到一个SocketChannel
                        通过该SocketChannel的read和write方法就可以与连接的客户端交互了
                     */
                        SocketChannel socket = ssc.accept();//接电话
                    /*
                        当使用ServerSocketChannel调用accept方法时,可能返回的
                        SocketChannel为null(坑爹)
                     */
                        if (socket != null) {
                            //将当前SocketChannel存入共享集合
                            allChannel.add(socket);

                            //将该SocketChannel注册到多路选择上,让其监听发送消息过来的事件
                            socket.configureBlocking(false);//将SocketChannel设置为非阻塞的,这样才能交给多路选择器
                            socket.register(selector, SelectionKey.OP_READ);
                        }
                    //判断事件是否为有的通道上有消息可以读取(某个客户端发送消息过来了)
                    }else if(key.isReadable()){
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024*10);
                        int len = socketChannel.read(buffer);
                        if(len==-1){//客户端断开连接,服务端这里才会读取到-1
                            socketChannel.close();//断开连接
                            allChannel.remove(socketChannel);
                        }
                        buffer.flip();//position-limit之间就是刚刚读取到的数据了。
                        /*
                            flip后
                            position:0
                            limit:读取到的字节长度
                         */
                        byte[] data = new byte[buffer.limit()];
                        /*
                            Buffer的get方法定义:
                            get(byte[] data)
                            此方法会将Buffer内部数据中从position开始的连续data.length
                            个字节存入到data数组中
                         */
                        buffer.get(data);//调用后data中保存的就是position-limit之间的数据
                        /*
                            get完毕后:
                            position和limit位置又一样了。
                         */
                        String line = new String(data, StandardCharsets.UTF_8);
                        System.out.println("客户端说:"+line);

                        //遍历allChannel集合,将消息发送给所有客户端
                        for(SocketChannel sc : allChannel){
                            buffer.flip();//limit=position,position=0
                            sc.write(buffer);//limit与position值相同了
                        }

                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        NIOServer server = new NIOServer();
        server.start();
    }
}

客户端代码:

/**
 * 聊天室的客户端
 */
public class Client {
    /*
        java.net.Socket
        封装了TCP协议的通讯细节,我们基于它就可以与远端计算机建立TCP连接,并基于一对
        输入与输出流的读写完成与远端计算机的数据交互操作。
        可以把Socket想象成"电话"
     */
    private Socket socket;
    /*
        构造器中完成初始化操作
     */
    public Client(){
        try {
            /*
                Socket的构造器:
                Socket(String host,int port)
                参数1:要连接的远端计算机(服务器)的IP地址
                参数2:要连接的运行在远端计算机上的服务端应用程序开启的服务端口
                我们通过IP地址可以找到网络上的服务器计算机,通过端口可以连接到运行在
                该服务器计算上的服务端应用程序。

                并且这个构造器实例化Socket的过程就是与服务端建立连接的过程,如果连接
                失败会抛出异常.
                如果指定的服务端地址与端口不对,那么连接时会抛出异常:
                java.net.ConnectException:Connection refused : connect

             */
            System.out.println("正在连接服务端...");
            socket = new Socket("localhost",8088);
            System.out.println("已连接服务端!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void start(){
        try {
            ServerHandler serverHandler = new ServerHandler();
            Thread t = new Thread(serverHandler);
            t.setDaemon(true);
            t.start();
            /*
                Socket提供的方法:
                OutputStream getOutputStream()
                通过socket获取一个字节输出流,使用该输出流写出的字节数据会发送给
                建立连接的远端计算机。而对方也可以通过建立连接的Socket获取输入流
                读取到我们写出的字节。
             */
            //低级流,通过该流写出的字节就发送给了远端计算机
            OutputStream out = socket.getOutputStream();
            OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
            BufferedWriter bw = new BufferedWriter(osw);
            PrintWriter pw = new PrintWriter(bw,true);

            Scanner scanner = new Scanner(System.in);
            while(true){
                String line = scanner.nextLine();//获取控制台输入的一行字符串
                if("exit".equalsIgnoreCase(line)){
                    break;//停止循环
                }
                pw.println(line);//将输入的内容发送给服务端
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //与服务端断开连接
            try {
                /*
                    Socket提供了close方法,该方法内部会将通过它获取的输入流和输出流
                    全部关闭,同时还会跟远端计算机做最后的4次挥手动作(TCP协议断开的操作)
                 */
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }

    private class ServerHandler implements Runnable{
        public void run() {
            try{
                  /*
                    通过socket获取输入流,读取服务端发送过来的消息
                 */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);

                String message;
                while((message = br.readLine()) != null){
                    System.out.println(message);
                }
            }catch(IOException e){

            }
        }
    }
}
                BufferedReader br = new BufferedReader(isr);

                String message;
                while((message = br.readLine()) != null){
                    System.out.println(message);
                }
            }catch(IOException e){

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别来无恙blwy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值