【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

 

时间: 2024-10-12 01:06:42

【Netty】netty学习之nio了解的相关文章

线程-spring+netty项目中使用NIO的技术

问题描述 spring+netty项目中使用NIO的技术 在做一个springmvc+netty的项目,要求当请求进到方法正常返回一个成功的同时另一条线程处理后台的业务,后台业务在处理的同时其实这个会话已经正常返回了. @RequestMapping(value = "/static/o_index.do",method={RequestMethod.POST,RequestMethod.GET}) public void indexSubmit(HttpServletRequest

从Netty到EPollSelectorImpl学习Java NIO

终于可以在写了几篇鸡汤文后,来篇技术文章了,:),题图是Trustin Lee,Mina/Netty都是他搞的,对Java程序员尤其是写通讯类的都产生了巨大影响,向他致敬! 在上周查一个内存OOM的问题之前,我一直觉得自己对Java NIO应该还是比较懂的,君不见N年前我曾经写过一篇<NFS-RPC框架优化过程(从37K到168K)>(尴尬的发现,上次导blog记录的时候竟然丢了一些文章,于是这文章link就不是自己的blog了),从那优化经历来说理论上对Netty的原理应该已经是比较清楚了才

Netty学习4—NIO服务端报错:远程主机强迫关闭了一个现有的连接

1 发现问题 NIO编程中服务端会出现报错 Exception in thread "main" java.io.IOException: 远程主机强迫关闭了一个现有的连接. at sun.nio.ch.SocketDispatcher.read0(Native Method) at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:25) at sun.nio.ch.IOUtil.readIntoNativeBuffer(I

Netty学习3—NIO

1 代码 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.SocketCha

【Netty】netty学习之nio网络编程的模型

[一]NIO服务器编程结构   [二]Netty3.x服务端线程模型

《Netty 权威指南》—— NIO创建的TimeClient源码分析

声明:本文是<Netty 权威指南>的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文. 我们首先还是看下如何对TimeClient进行改造: public class TimeClient { /** * @param args */ public static void main(String[] args) { int port = 8080; if (args != null && args.length > 0) { try { port =

《Netty 权威指南》—— NIO类库简介

声明:本文是<Netty 权威指南>的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文. 在介绍NIO编程之前,我们首先需要澄清一个概念,NIO到底是什么的简称?有人称之为New IO,因为它相对于之前的IO类库是新增的,所以被称为New IO,这是它的官方叫法.但是,由于之前老的IO类库是阻塞IO,New IO类库的目标就是要让JAVA支持非阻塞IO,所以,更多的人喜欢称之为非阻塞IO(Non-block IO),由于非阻塞IO更能够体现NIO的特点,所以本书使用的NIO都

《Netty 权威指南》—— NIO创建的TimeServer源码分析

声明:本文是<Netty 权威指南>的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文. 我们将在TimeServer例程中给出完整的NIO创建的时间服务器源码: public class TimeServer { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { int port = 8080; if (args !=

《Netty 权威指南》—— NIO客户端序列图

声明:本文是<Netty 权威指南>的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文. 步骤一:打开SocketChannel,绑定客户端本地地址(可选,默认系统会随机分配一个可用的本地地址),示例代码如下: 1 SocketChannel clientChannel = SocketChannel.open(); 步骤二:设置SocketChannel为非阻塞模式,同时设置客户端连接的TCP参数,示例代码如下: 1 clientChannel.configureBlock