【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); } } }
【七】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字节忽略 } } } } }