zoukankan      html  css  js  c++  java
  • 【Netty】netty学习之nio了解

    【一】五种IO模型:

    (1)阻塞IO
    (2)非阻塞IO(任务提交,工作线程处理,委托线程等待工作线程处理结果的同时,也可以做其他的事情)
    (3)IO复用模型.(委托线程接收多个任务,将任务提交给工作线程。委托线程等待多个工作线程结果,等待到其中一个,处理其中一个具体的工作)
    (4)信号驱动模型
    (5)异步IO模型

    【二】网络编程

    (1)网络编程的基本模型:Client/Server模型,也就是两个进程之间进行相互通信。其中服务端提供位置信息(绑定的ip地址和监听的端口号),客户端通过链接操作向服务端监听的地址发起连接请求。通过三次握手建立链接,如果建立链接成功,双方就可以通过网络套接字(Socket)进行通信。


    【三】阻塞IO和非阻塞IO的区别
    (1)IO的操作:对硬盘的读写、对socket的读写以及外设的读写。
    (2)IO读请求操作包括两个 阶段:
        --->查看数据是否就绪;
        --->进行数据拷贝(内核将数据拷贝到用户线程)。
    (3)阻塞IO:当用户线程发起一个IO请求操作(本文以读请求操作为例),内核会去查看要读取的数据是 否就绪,对于阻塞IO来说,如果数据没有就绪,则会一直在那等待,直到数据就绪
    (4)非阻塞IO:当用户线程发起一个IO请求操作(本文以读请求操作为例),内核会去查看要读取的数据是 否就绪,对于非阻塞IO来说,如果数据没有就绪,则会返回一个标志信息告知用户线 程当前要读的数据没有就绪。当数据就绪之后,便将数据拷贝到用户线程
    (5)那么阻塞(blocking IO)和非阻塞(non-blocking IO)的区别就在于第一个阶段,如果数据没有就绪,在查看数据是否就绪的过程中是一直等待,还是直接返回一个标志信息。Java中传统的IO都是阻塞IO,比如通过socket来读数据,调用read()方 法之后,如果数据没有就绪,当前线程就会一直阻塞在read方法调用那里,直到有数据才返回;而如果是非阻塞IO的话,当数据没有就绪,read()方法 应该返回一个标志信息,告知当前线程数据没有就绪,而不是一直在那里等待。

    【四】:BIO和NIO

    BIO
    ===>阻塞IO通信,通常导致通信线程被长时间阻塞。
    NIO
    ===>IO多路复用技术
    ===>非阻塞IO.

    【五】:NIO的几个关键的类
    (1)缓冲区Buffer
    ===>Buffer是一个对象,它包含一些要写入或要读出的数据。
    ===>在NIO的库中,所有数据都是用缓冲区处理的。在读取数据时候,它是直接读到缓冲区中进行的。在写数据时候,写入缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
    ===>缓冲区实质上是一个数组,通常它是一个字节数组(ByteBuffer),也可以是其他种类的数组。但一个缓冲区不仅仅是一个数组,缓冲区提供了对数据结构化访问以及维护读写位置等信息。
    ===>ByteBuffer(字节缓冲区),CharBuffer(子符缓冲区),ShortBuffer(短整型缓冲区),IntBuffer(整型缓冲区),LongBuffer(长整型缓冲区),FloatBuffer(浮点型缓冲区),DoubleBuffer(双精度浮点型缓冲区)


    (2)通道Channel
    ===>Channel是一个通道,可以通过它读取和写入数据
    ===>它就像自来水管一样,网络数据通过Channel读取和写入。
    ===>通道与流不同之处在于通道是双向的。流只是在一个方向上移动(一个流必须是InputStream或者OutputStream)
    ===>而且通道可以用于读,写或同时用于读写。
    ===>Channel分为两大类,分别是用于网络读写的SelectableChannel和用于文件操作的FileChannel
    ===>NIO网络编程的ServerSocketChannel和SocketChannel都是SelectabelChannel的子类。


    (3)多路复用器Selector
    ===>它是javaNio编程的基础,熟练掌握Selector对于掌握NIO编程至关重要。
    ===>多路复用器提供选择已经就绪的任务的能力。
    ===>简单的来讲,Selector会不断轮询注册在其上的Channel。如果某个Channel上面有新的TCP连接接入,读和写事件。这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。
    ===>一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用epoll()代替传统的select实现。所以它并没有最大连接句柄1024/2048的限制。这也意味者只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。


    【六】NIO实现服务端通信序列图

    NIO简单实现服务端代码

    package com.sxf.test.netty;
    
    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.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    import java.util.Set;
    
    import org.apache.commons.lang3.StringUtils;
    
    
    
    public class NioServer  implements Runnable{
        
        //打开serverSocketChannel,用于监听客户端链接,它是所有客户端链接的父管道
        private ServerSocketChannel serverSocketChannel;
        
        //创建多路复用器
        private Selector selector;
        
        private volatile boolean stop=false;
        
        //初始化服务器
        public NioServer(Integer port){
            try {
                //创建多路复用器
                 selector=Selector.open();
                //打开serverSocketChannel,用于监听客户端链接,它是所有客户端链接的父管道
                serverSocketChannel=ServerSocketChannel.open();
                //非阻塞模式
                serverSocketChannel.configureBlocking(false);
                //绑定监听端口
                serverSocketChannel.socket().bind(new InetSocketAddress(port),1024);
                //将serverSocketChannel注册到复用器上,并轮询出读的就绪事件。
                serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
                System.out.println("NioServer.NioServer()=====>NIOServer start...");
            } catch (Exception e) {
                System.out.println("NioServer.NioServer()"+e);
            }
        }
        
        public void stop(){
            this.stop=true;
        }
        
        
        /**
         * 服务器执行过程
         */
        @Override
        public void run() {
            while(!stop){
                try {
                    //
                    selector.select();
                    //多路复用器,选出已经就绪的事件
                    Set<SelectionKey> selectedKeys=selector.selectedKeys();
                    //遍历就绪事件进行处理
                    Iterator<SelectionKey> it=selectedKeys.iterator();
                    while(it.hasNext()){
                        //其中一个事件
                        SelectionKey selectionKey=it.next();
                        it.remove();
                        handleInput(selectionKey);
                    }
                    
                } catch (Exception e) {
                    // TODO: handle exception
                    System.out.println("NioServer.run()"+e);
                }
            }
            //多路复用器关闭后,所有注册在上面的Channel和Pipe都会被自动去注册并关闭,所以不需要重复释放资源
            if(selector!=null){
                try {
                    selector.close();
                } catch (Exception e) {
                    
                }
            }
        }
        
        
        
        private void handleInput(SelectionKey selectionKey) throws IOException{
            //判断是否有效
            if(selectionKey.isValid()){
                
                //=============处理新接入的网络链接================
                if(selectionKey.isAcceptable()){
                    //从事件key上获取请求通道
                    ServerSocketChannel requestChannel=(ServerSocketChannel) selectionKey.channel();
                    SocketChannel channel=requestChannel.accept();
                    //设置非阻塞模式
                    channel.configureBlocking(false);
                    channel.register(selector,selectionKey.OP_READ);
                    }
                
                //=============处理可以读取请求内容的链接================
                if(selectionKey.isReadable()){
                    //从事件中得到链接通道
                    SocketChannel channel=(SocketChannel) selectionKey.channel();
                    //声明缓冲区,准备读取数据。
                    ByteBuffer readByteBuffer=ByteBuffer.allocate(1024);
                    //读取请求管道的数据
                    int readBytes=channel.read(readByteBuffer);
                    
                    if(readBytes>0){
                        //读取到内容
                        readByteBuffer.flip();
                        byte[] requestByte=new byte[readByteBuffer.remaining()];
                        readByteBuffer.get(requestByte);
                        String requestBody=new String(requestByte, "utf-8");
                        System.out.println("NioServer.handleInput()the nio server receive body(接收到请求内容为)===>"+requestBody);
                        //模拟处理请求内容
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        String response="中华兴旺";
                        //将处理结果进行响应
                        doWrite(channel, response);
                    }else if(readBytes<0){
                        //客户端关闭
                        selectionKey.cancel();
                        channel.close();
                    }else{
                        //读到0字节忽略
                    }
                }
            }
        }
        
        
        /**
         * 响应请求
         * @param socketChannel
         * @param response
         * @throws IOException
         */
        public void doWrite(SocketChannel socketChannel,String response) throws IOException{
            if(StringUtils.isNotBlank(response)){
                byte[] res=response.getBytes();
                ByteBuffer writeByteBuffer=ByteBuffer.allocate(res.length);
                writeByteBuffer.put(res);
                writeByteBuffer.flip();
                socketChannel.write(writeByteBuffer);
            }
        }
    
        public static void main(String[] args) throws IOException, InterruptedException {
                NioServer nioServer=new NioServer(8000);
                new Thread(nioServer,"NioServer===>").start();
                Thread.sleep(1000*10);
                NioClient nioClient=new NioClient("127.0.0.1", 8000);
                new Thread(nioClient,"NioClient===>").start();
                
                while(true){
                    Thread.sleep(1000*60);
                }
        }
    }
    View Code

    【七】Nio客户端链接

    NIO简单实现客户端代码

    package com.sxf.test.netty;
    
    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 implements Runnable {
        /**
         * 请求的地址
         */
        private String host;
        /**
         * 请求的端口号
         */
        private int port;
        /**
         * 请求的多路复用器
         */
        private Selector selector;
        /**
         * 请求的通道
         */
        private SocketChannel socketChannel;
        /**
         * 客户端是否停止
         */
        private volatile boolean stop=false;
        
        
        public NioClient(String host,Integer port) throws IOException {
            try {
                this.host=host;
                this.port=port;
                this.selector=Selector.open();
                this.socketChannel=SocketChannel.open();
                socketChannel.configureBlocking(false);
            } catch (Exception e) {
            }    
        }
        
        
        @Override
        public void run() {
            //链接服务端
            try {
                doConnect();
            } catch (Exception e) {
                System.out.println("NioClient.run()"+e);
            }
            
            //循环发送请求
            while(!stop){
                try {
                    //超时时间
                    selector.select();
                    //获取就绪事件的key
                    Set<SelectionKey> selectionKeys=selector.selectedKeys();
                    //开始遍历事件
                    Iterator<SelectionKey> it=selectionKeys.iterator();
                    while(it.hasNext()){
                        SelectionKey selectionKey=it.next();
                        it.remove();
                        try {
                            handlerInput(selectionKey);
                        } catch (Exception e) {
                            //关闭
                            selectionKey.cancel();
                            if(selectionKey.channel()!=null){
                                selectionKey.channel().close();
                            }
                        }
                    }
                } catch (Exception e) {
                    System.out.println("NioClient.run()"+e);
                }
            }
            
            //多路复用器关闭后,所有注册在上面的channel和Pipe等资源都会被自动去注册并关闭
                     //所以不需要重复释放资源
             //        if(selector!=null){
             //            try {
             //                selector.close();
             //            } catch (Exception e) {
             //                e.printStackTrace();
             //            }
             //        }
            
        }
        
        //链接操作
        private void doConnect() throws IOException{
            //判断是否能链接到服务器
            if(socketChannel.connect(new InetSocketAddress(host,port))){
                socketChannel.register(selector, SelectionKey.OP_READ);
            }else{
                socketChannel.register(selector,SelectionKey.OP_CONNECT);
            }
        }
        
        //发送请求操作
        private void write(SocketChannel socketChannel) throws IOException{
            byte[] requestByte="shangxiaofei".getBytes();
            //将请求内容写入缓冲区
            ByteBuffer requestByteBuffer=ByteBuffer.allocate(requestByte.length);
            requestByteBuffer.put(requestByte);
            requestByteBuffer.flip();
            //向请求通道写数据
            socketChannel.write(requestByteBuffer);
            if(!requestByteBuffer.hasRemaining()){
                System.out.println("NioClient.write()向服务端发送请求");
            }
        }
        
        
        private void handlerInput(SelectionKey selectionKey) throws IOException{
            //判断是否有效
            if(selectionKey.isValid()){
                SocketChannel channel=(SocketChannel) selectionKey.channel();
                //已成功和服务端建立链接
                if(selectionKey.isConnectable()){
                    if(channel.finishConnect()){
                        //成功和服务端建立链接,开始写数据
                        channel.register(selector, selectionKey.OP_READ);
                        write(channel);
                    }else{
                        //链接失败,退出
                        System.exit(1);
                    }
                }
                if(selectionKey.isReadable()){
                    //读取事件就绪
                    ByteBuffer readByteBuffer=ByteBuffer.allocate(1024);
                    int a=channel.read(readByteBuffer);
                    if(a>0){
                        //读取到数据
                        readByteBuffer.flip();
                        byte[] response=new byte[readByteBuffer.remaining()];
                        readByteBuffer.get(response);
                        String responseBody=new String(response,"utf-8");
                        System.out.println("NioClient.handlerInput(响应内容)===>"+responseBody);
                        //退出
                        this.stop=true;
                    }else if(a<0){
                        //服务端链路关闭
                        selectionKey.cancel();
                        channel.close();
                    }else{
                        //读到0字节忽略
                    }
                }
                
                
            }
            
        }
        
    
    }
    View Code
  • 相关阅读:
    Building Java Projects with Gradle
    Vert.x简介
    Spring及Spring Boot 国内快速开发框架
    dip vs di vs ioc
    Tools (StExBar vs Cmder)which can switch to command line window on context menu in windows OS
    SSO的定义、原理、组件及应用
    ModSecurity is an open source, cross-platform web application firewall (WAF) module.
    TDD中测试替身学习总结
    Spring事务银行转账示例
    台式机(华硕主板)前面板音频接口(耳机和麦克风)均无声的解决办法
  • 原文地址:https://www.cnblogs.com/shangxiaofei/p/7833799.html
Copyright © 2011-2022 走看看