Netty的介绍

官方的文档:尚硅谷netty文档 · 语雀

1、Netty 是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可 靠性的网络 IO 程序。

2、Netty主要针对在TCP协议下,面向Clients端的高并发应用,或者Peer-to-Peer场景下 的大量数据持续传输的应用。

3、Netty本质是一个NIO框架,适用于服务器通讯相关的多种应用场景

友情提示:

Netty是基于NIO实现的,NIO是基于java原生IO编程实现的,java原生IO是基于TCP/IP实现的

理解:

1、异步:类似于ajax一样

2、基于事件驱动:一个线程有一个selector,当有客户端有事件动作的时候(动作包含,发送消息,连接服务端,回复消息,接收消息等动作),selector就会监测到,让线程执行。

友情提示:非阻塞的,监测channel,如果Channel没有动作,就会监测下一个Channel

I/O模型

I/O 模型简单的理解:就是用什么样的通道进行数据的发送和接收,很大程度上决定了程 序通信的性能

Java共支持3种网络编程模型/IO模式:BIO、NIO、AIO

Java BIO : 同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端 有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成 不必要的线程开销 ,Java BIO 就是传统的java io 编程,其相关的类和接口在 java.io.

【简单示意图】

 Java NIO : 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发 送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理 。

友情提示:对于多路复用,只是针对网络传输,对于本地的文件复制是不存在的,这里的selector就相当于微服务的注册中心,多个客户端要向selector进行注册。并且selector监视着各个客户端的事件的一举一动。

【简单示意图】

 Java AIO(NIO.2) : 异步非阻塞,AIO 引入异步通道的概念,采用了 Proactor 模式,简 化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般用于连接数较多且时间比较长的应用。(简单介绍,不是重点,记住netty5.x是基于他实现的,但是效果不是很好,就将其Netty5.x作废了。因为Linux系统本身的原因)

BIO、NIO、AIO适用场景分析

Java BIO 工作机制

Java BIO 应用实例

实例说明:

1) 使用BIO模型编写一个服务器端,监听6666端口,当有客户端连接时,就启动一个线程与之通讯。

2) 要求使用线程池机制改善,可以连接多个客户端.

3) 服务器端可以接收客户端发送的数据(telnet 方式即可)。

下面的是代码实现:

服务端:

package com.atguigu.bio;import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class BIOServer {public static void main(String[] args) throws Exception {//线程池机制//思路//1. 创建一个线程池//2. 如果有客户端连接,就创建一个线程,与之通讯(单独写一个方法)ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();//创建ServerSocketServerSocket serverSocket = new ServerSocket(6666);System.out.println("服务器启动了");while (true) {System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());//监听,等待客户端连接System.out.println("等待连接....");
//            这个地方会阻塞,在没有用户连接就会阻塞这里final Socket socket = serverSocket.accept();System.out.println("连接到一个客户端");//就创建一个线程,与之通讯(单独写一个方法)newCachedThreadPool.execute(new Runnable() {public void run() { //我们重写//可以和客户端通讯handler(socket);}});}}//编写一个handler方法,和客户端通讯public static void handler(Socket socket) {try {System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());byte[] bytes = new byte[1024];//通过socket 获取输入流InputStream inputStream = socket.getInputStream();//循环的读取客户端发送的数据while (true) {System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());System.out.println("read....");
//                这个地方会阻塞,在用户没有输入的时候就会阻塞这里int read =  inputStream.read(bytes);if(read != -1) {System.out.println(new String(bytes, 0, read)); //输出客户端发送的数据} else {break;}}}catch (Exception e) {e.printStackTrace();}finally {System.out.println("关闭和client的连接");try {socket.close();}catch (Exception e) {e.printStackTrace();}}}
}

 客户端:

package yu.learn;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;public class Client {public static void main(String[] args) {// 通过构造函数创建Socket,并且连接指定地址和端口的服务端try {Socket socket = new Socket("127.0.0.1", 6666);System.out.println("请输入信息");new ReadMsg(socket).start();PrintWriter pw = null;// 写数据到服务端while (true) {pw = new PrintWriter(socket.getOutputStream());pw.println(new Scanner(System.in).next());pw.flush();}} catch (IOException e) {e.printStackTrace();}}public static class ReadMsg extends Thread {Socket socket;public ReadMsg(Socket socket) {this.socket = socket;}@Overridepublic void run() {try (BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {String line = null;// 通过输入流读取服务端传输的数据while ((line = br.readLine()) != null) {System.out.printf("%s\n", line);}} catch (IOException e) {e.printStackTrace();}}}
}

Java BIO 问题分析

Java NIO 基本介绍(重点)

1、NIO 有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)

2、NIO是 面向缓冲区 ,或者面向 块 编程的。

友情提示:为什么相比BIO不会阻塞,就是因为缓冲区,就像人在拿一堆易拉罐,前面的人将易拉罐一点点的丢给你感觉比较麻烦,你就说让前面的人直接把易拉罐丢到袋子里,我一次性的拿走,在他装袋的时候,你可以去听听音乐,喝喝水。

NIO基本介绍

NIO 和 BIO 的比较

友情提示:BIO是通过字节流和字符流,他是单向的。NIO是通过Channel和Buffer,Channel是双向的。导致NIO是双向的。

NIO三大核心组件的关系

友情提示:其实客户端和服务端在和Channel进行连接的时候,都需要Buffer缓冲区,下图显示的不完整,需要注意,客户端和服务端直接交互的是buffer,然后通过buffer和Channel进行交互

缓冲区(Buffer)核心组件一

基本介绍

友情提示:Buffer内部存在一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel就是渠道,可以想象,网络传输的二进制流,就是人,Channel就是到目的的一种方式如水路,空中飞行,大路(在网络中就是相当于协议如WS协议,http协议),这个buffer就是交通工具,轮船,飞机,汽车。

Buffer 类及其子

buffer 则用来缓冲读写数据,常见的 buffer 有

  • ByteBuffer

    • MappedByteBuffer
    • DirectByteBuffer
    • HeapByteBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • CharBuffer

ByteBuffer 结构

ByteBuffer 有以下四个重要属性

  • capacity():相当于数组给的初始容量,始终不变的
  • position():相当于一个指针,不断的移动,初始值为0
  • limit():指针移动的极限位置,也就是写入的数据的最终的位置。
  • mark():表示标记,给某一个位置加上标记,可以让其指定在指定的位置,不断的从标记位读取数据,初始值为-1

注意:

  • mark()和 reset()

    mark() 是在读取时,做一个标记,即使 position 改变,只要调用 reset 就能回到 mark 的位置

rewind 和 flip 都会清除 mark 位置,可以调用 rewind 方法将 position 重新置为 0

友情提示:在进行Clear执行之后,会将其position的位置重新写到起始的位置,但是compact执行之后会将其没有读取到的数据压缩到最起始的位置。

起始状态

在写模式情况下

利用flip进行切换(写变成读,limit变换到最后写入的位置)

 读取到4个字节后

clear动作,会编程最开始的写状态(由读变成写状态)

Compact方法,会把未读完的移动最起始位置。(由读变成写状态)

友情提示:移动的最后一位还是最原始的数据,但是在写的时候,会将其原始的数据覆盖掉。

Buffer相关的方法

ByteBuffer 常见方法

友情提示:

当输出这两个方法的对象发现

System.out.println(ByteBuffer.allocate(16).getClass());针对堆
System.out.println(ByteBuffer.allocateDirect(16).getClass());针对直接内存

友情提示:allocate存在垃圾回收,回收就会出现内存的复制导致会多复制一次,除此之外,allocateDirect,容易出现OOM

class java.nio.HeapByteBuffer    - java 堆内存,读写效率较低,受到 GC 的影响
class java.nio.DirectByteBuffer  - 直接内存,读写效率高(少一次拷贝),不会受 GC 影响,分配的效率低

代码:

package com.atguigu.nio;import java.nio.IntBuffer;public class BasicBuffer {public static void main(String[] args) {//举例说明Buffer 的使用 (简单说明)//创建一个Buffer, 大小为 5, 即可以存放5个intIntBuffer intBuffer = IntBuffer.allocate(5);//向buffer 存放数据
//        intBuffer.put(10);
//        intBuffer.put(11);
//        intBuffer.put(12);
//        intBuffer.put(13);
//        intBuffer.put(14);for(int i = 0; i < intBuffer.capacity(); i++) {intBuffer.put( i * 2);}//如何从buffer读取数据//将buffer转换,读写切换(!!!)/*public final Buffer flip() {limit = position; //读数据不能超过5position = 0;mark = -1;return this;}*/intBuffer.flip();intBuffer.position(1);//1,2System.out.println(intBuffer.get());intBuffer.limit(3);while (intBuffer.hasRemaining()) {System.out.println(intBuffer.get());}}
}

字符串与 ByteBuffer 互转

重点:

      字符串转换成ByteBufferByteBuffer byteBuffer2= StandardCharsets.UTF_8.encode("我啦");

下面的可以自动切换成读模式

      Buffer转换成字符串,需要注意的是转换成字符串需要将其Buffer进行flip不然没有显示byteBuffer.flip();String str=  StandardCharsets.UTF_8.decode(byteBuffer).toString();

可以将字节数组包装成功wrap,也是可以自动切换成读模式

 // 3. wrapByteBuffer buffer3 = ByteBuffer.wrap("hello".getBytes());debugAll(buffer3);
package cn.itcast.nio.c2;import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;import static cn.itcast.nio.c2.ByteBufferUtil.debugAll;public class TestByteBufferString {public static void main(String[] args) {// 1. 字符串转为 ByteBufferByteBuffer buffer1 = ByteBuffer.allocate(16);buffer1.put("hello".getBytes());debugAll(buffer1);// 2. CharsetByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");debugAll(buffer2);// 3. wrapByteBuffer buffer3 = ByteBuffer.wrap("hello".getBytes());debugAll(buffer3);// 4. 转为字符串String str1 = StandardCharsets.UTF_8.decode(buffer2).toString();System.out.println(str1);//        buffer1是put进去的需要切换成读模式buffer1.flip();String str2 = StandardCharsets.UTF_8.decode(buffer1).toString();System.out.println(str2);}
}

通道(Channel)

友情提示:

1、通道可以实现异步读写数据,而且可读可写

2、在使用channel的read和write方法都是针对Channel,read方法是从channel中读取数据到buffer,write则是从buffer中写入到channel。

3、Channel就可以理解成是类似于流。

 常见的 Channel 

  • FileChannel :专门读取文件的
  • DatagramChannel :专门用于UDP的
  • SocketChannel:对应网络传输的客户端,用于TCP的
  • ServerSocketChannel::对应网络传输的服务端,用于TCP的

下面的是对应的不同的Channel

非阻塞 vs 阻塞

友情提示:configureBlocking针对给方法介绍false为非阻塞,true表示阻塞。同时没有添加多路复用Selector.的情况。

阻塞(configureBlocking(true))

  • 阻塞模式下,相关方法都会导致线程暂停

    • ServerSocketChannel.accept 会在没有连接建立时让线程暂停
    • SocketChannel.read 会在没有数据可读时让线程暂停
    • 阻塞的表现其实就是线程暂停了,暂停期间不会占用 cpu,但线程相当于闲置
  • 单线程下,阻塞方法之间相互影响,几乎不能正常工作,需要多线程支持
  • 但多线程下,有新的问题,体现在以下方面
    • 32 位 jvm 一个线程 320k,64 位 jvm 一个线程 1024k,如果连接数过多,必然导致 OOM,并且线程太多,反而会因为频繁上下文切换导致性能降低
    • 可以采用线程池技术来减少线程数和线程上下文切换,但治标不治本,如果有很多连接建立,但长时间 inactive,会阻塞线程池中所有线程,因此不适合长连接,只适合短连接

友情提示:

1、accept() 和read()方法都是阻塞的,对于NIO模型,使用configureBlocking(false);让其变成非阻塞。

2、如果使用单线程,阻塞方法之间会相互影响,效率极低,几乎不能工作。

3、如果使用多线程,可以使用线程池,减少上线文的线程切换,但是如果是长连接,导致线程不能释放资源,线程池也就是没什么作用。所以只适合短连接

非阻塞(configureBlocking(false))

  • 非阻塞模式下,相关方法都会不会让线程暂停

    • 在 ServerSocketChannel.accept 在没有连接建立时,会返回 null,继续运行
    • SocketChannel.read 在没有数据可读时,会返回 0,但线程不必阻塞,可以去执行其它 SocketChannel 的 read 或是去执行 ServerSocketChannel.accept
    • 写数据时,线程只是等待数据写入 Channel 即可,无需等 Channel 通过网络把数据发送出去
  • 但非阻塞模式下,即使没有连接建立,和可读数据,线程仍然在不断运行,白白浪费了 cpu
  • 数据复制过程中,线程实际还是阻塞的(AIO 改进的地方)

多路复用

单线程可以配合 Selector 完成对多个 Channel 可读写事件的监控,这称之为多路复用

  • 多路复用仅针对网络 IO、普通文件 IO 没法利用多路复用
  • 如果不用 Selector 的非阻塞模式,线程大部分时间都在做无用功,而 Selector 能够保证
    • 有可连接事件时才去连接
    • 有可读事件才去读取
    • 有可写事件才去写入
    • 限于网络传输能力,Channel 未必时时可写,一旦 Channel 可写,会触发 Selector 的可写事件

FileChannel类

FileChannel 只能工作在阻塞模式下

获取

不能直接打开 FileChannel,必须通过 FileInputStream、FileOutputStream 或者 RandomAccessFile 来获取 FileChannel,它们都有 getChannel 方法

  • 通过 FileInputStream 获取的 channel 只能读
  • 通过 FileOutputStream 获取的 channel 只能写
  • 通过 RandomAccessFile 是否能读写根据构造 RandomAccessFile 时的读写模式决定

读取

会从 channel 读取数据填充 ByteBuffer,返回值表示读到了多少字节,-1 表示到达了文件的末尾

java int readBytes = channel.read(buffer);

关闭

channel 必须关闭,不过调用了 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close 方法会间接地调用 channel 的 close 方法

位置

获取当前位置,获取到Channel读取的位置。

java long pos = channel.position();

设置当前位置

java long newPos = ...; channel.position(newPos);

设置当前位置时,如果设置为文件的末尾

  • 这时读取会返回 -1
  • 这时写入,会追加内容,但要注意如果 position 超过了文件末尾,再写入时在新内容和原末尾之间会有空洞(00)

大小

使用 size 方法获取文件的大小

强制写入

操作系统出于性能的考虑,会将数据缓存,不是立刻写入磁盘。可以调用 force(true) 方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘

channel常用的方法,FileChannel 没有非阻塞模式,因此不能配合 selector 一起使用,网络编程才会出现Selector

这里留意一下transferTo,用到了零拷贝的知识

实例1:

实例要求:

1) 使用前面学习后的ByteBuffer(缓冲) 和 FileChannel(通道), 将 "hello,尚硅谷" 写入 到file01.txt 中

2) 文件不存在就创建

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class FileChannelTest {public static void main(String[] args) throws IOException {//String str="我是谁";FileOutputStream fileOutputStream = null;try{fileOutputStream=new FileOutputStream("d:\\file01.txt");FileChannel fileChannel= fileOutputStream.getChannel();ByteBuffer byteBuffer = ByteBuffer.allocate(1024);byteBuffer.put(str.getBytes());byteBuffer.flip();fileChannel.write(byteBuffer);}catch (IOException e){e.printStackTrace();}finally{fileOutputStream.close();}}
}

实例2:

实例要求:

1) 使用前面学习后的ByteBuffer(缓冲) 和 FileChannel(通道), 将 file01.txt 中的数据读 入到程序,并显示在控制台屏幕

2) 假定文件已经存在

package com.atguigu.nio;import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class NIOFileChannel02 {public static void main(String[] args) throws Exception {//创建文件的输入流File file = new File("d:\\file01.txt");FileInputStream fileInputStream = new FileInputStream(file);//通过fileInputStream 获取对应的FileChannel -> 实际类型  FileChannelImplFileChannel fileChannel = fileInputStream.getChannel();//创建缓冲区ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());//将 通道的数据读入到BufferfileChannel.read(byteBuffer);//将byteBuffer 的 字节数据 转成StringSystem.out.println(new String(byteBuffer.array()));fileInputStream.close();}
}

另外写法:

package cn.itcast.nio.c2;import lombok.extern.slf4j.Slf4j;import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;@Slf4j
public class TestByteBuffer {public static void main(String[] args) {// FileChannel// 1. 输入输出流, 2. RandomAccessFiletry (FileChannel channel = new FileInputStream("data.txt").getChannel()) {// 准备缓冲区ByteBuffer buffer = ByteBuffer.allocate(10);while(true) {// 从 channel 读取数据,向 buffer 写入int len = channel.read(buffer);log.debug("读取到的字节数 {}", len);if(len == -1) { // 没有内容了break;}// 打印 buffer 的内容buffer.flip(); // 切换至读模式while(buffer.hasRemaining()) { // 是否还有剩余未读数据byte b = buffer.get();log.debug("实际字节 {}", (char) b);}buffer.clear(); // 切换为写模式}} catch (IOException e) {e.printStackTrace();}}}

实例3:

下面的实现的思想,读取和传输都是通过一个buffer

代码

package com.atguigu.nio;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class NIOFileChannel03 {public static void main(String[] args) throws Exception {FileInputStream fileInputStream = new FileInputStream("1.txt");FileChannel fileChannel01 = fileInputStream.getChannel();FileOutputStream fileOutputStream = new FileOutputStream("2.txt");FileChannel fileChannel02 = fileOutputStream.getChannel();ByteBuffer byteBuffer = ByteBuffer.allocate(512);while (true) { //循环读取//这里有一个重要的操作,一定不要忘了/*public final Buffer clear() {position = 0;limit = capacity;mark = -1;return this;}*/byteBuffer.clear(); //清空bufferint read = fileChannel01.read(byteBuffer);System.out.println("read =" + read);if(read == -1) { //表示读完break;}//将buffer 中的数据写入到 fileChannel02 -- 2.txtbyteBuffer.flip();fileChannel02.write(byteBuffer);}//关闭相关的流fileInputStream.close();fileOutputStream.close();}
}

实例4

友情提示:注意transferFrom 的使用

缺少者(输出流,write)调用transferFrom方法

transferFrom参数分别:拥有者(输入流,read),position读取的位置,拥有者资源的长度

destCh.transferFrom(sourceCh,0,sourceCh.size());

利用拷贝文件transferFrom 方法

实例要求:

1) 使用 FileChannel(通道) 和 方法 transferFrom ,完成文件的拷贝

2) 拷贝一张图片

package com.atguigu.nio;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;public class NIOFileChannel04 {public static void main(String[] args)  throws Exception {//创建相关流FileInputStream fileInputStream = new FileInputStream("d:\\a.jpg");FileOutputStream fileOutputStream = new FileOutputStream("d:\\a2.jpg");//获取各个流对应的filechannelFileChannel sourceCh = fileInputStream.getChannel();FileChannel destCh = fileOutputStream.getChannel();//使用transferForm完成拷贝destCh.transferFrom(sourceCh,0,sourceCh.size());//关闭相关通道和流sourceCh.close();destCh.close();fileInputStream.close();fileOutputStream.close();}
}

实例5

友情提示:transferTo的使用效率高,底层会利用操作系统的零拷贝进行优化, 这个方法一次只能传递2g内存 数据

package cn.itcast.nio.c3;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;public class TestFileChannelTransferTo {public static void main(String[] args) {try (FileChannel from = new FileInputStream("data.txt").getChannel();FileChannel to = new FileOutputStream("to.txt").getChannel();) {// 效率高,底层会利用操作系统的零拷贝进行优化, 这个方法一次只能传递2g内存 数据long size = from.size();// left 变量代表还剩余多少字节for (long left = size; left > 0; ) {System.out.println("position:" + (size - left) + " left:" + left);
//                下面的方式可以传递多次,left -= from.transferTo((size - left), left, to);}} catch (IOException e) {e.printStackTrace();}}
}

关于Buffer 和 Channel的注意事项和细节

1、存什么类型,获取的时候就是什么类型,不然会报BufferUnderflowException异常

写入什么类型,取什么类型

package com.atguigu.nio;import java.nio.ByteBuffer;public class NIOByteBufferPutGet {public static void main(String[] args) {//创建一个BufferByteBuffer buffer = ByteBuffer.allocate(64);//类型化方式放入数据buffer.putInt(100);buffer.putLong(9);buffer.putChar('尚');buffer.putShort((short) 4);//取出buffer.flip();System.out.println();System.out.println(buffer.getInt());System.out.println(buffer.getLong());System.out.println(buffer.getChar());System.out.println(buffer.getShort());}
}

MappedByteBuffer

友情提示:代码执行完成,在idea中查看数据是没有改变的。到电脑的文件的管理系统,去查看,里面的数据是改变。

NIO 还提供了 MappedByteBuffer, 可以让文件直接在内存(堆外的内存)中进 行修改, 而如何同步到文件由NIO来完成,少了一次拷贝,注意这里是5 的含义,最多只能修改5个字节

package com.atguigu.nio;import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;/*
说明
1. MappedByteBuffer 可让文件直接在内存(堆外内存)修改, 操作系统不需要拷贝一次*/
public class MappedByteBufferTest {public static void main(String[] args) throws Exception {RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");//获取对应的通道FileChannel channel = randomAccessFile.getChannel();/*** 参数1: FileChannel.MapMode.READ_WRITE 使用的读写模式* 参数2: 0 : 可以直接修改的起始位置* 参数3:  5: 是映射到内存的大小(不是索引位置) ,即将 1.txt 的多少个字节映射到内存* 可以直接修改的范围就是 0-5,需要注意不包含5* 实际类型 DirectByteBuffer*/MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 15);mappedByteBuffer.put(0, (byte) 'H');mappedByteBuffer.put(3, (byte) '9');mappedByteBuffer.put(14, (byte) 'Y');//IndexOutOfBoundsExceptionrandomAccessFile.close();System.out.println("修改成功~~");}
}

Buffer 转成只读Buffer

友情提示:readOnlyBuffer.get(),在获取的内容,如果存的是String,他获取的是ASC码,如果存储的是数字,获取的就是实际的数字。

package com.atguigu.nio;import java.nio.ByteBuffer;public class ReadOnlyBuffer {public static void main(String[] args) {//创建一个bufferByteBuffer buffer = ByteBuffer.allocate(64);for(int i = 0; i < 64; i++) {buffer.put((byte)i);}//读取buffer.flip();//得到一个只读的BufferByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();System.out.println(readOnlyBuffer.getClass());//读取while (readOnlyBuffer.hasRemaining()) {System.out.println(readOnlyBuffer.get());}readOnlyBuffer.put((byte)100); //ReadOnlyBufferException}
}

Scattering 和 Gathering

NIO 还支持 通过多个 Buffer (即 Buffer 数组) 完成读写操作,主要就是对应的Channle的read和write方法内部参数可以使Buffer数组对象

Gathering的代码

public class TestGatheringWrites {public static void main(String[] args) {ByteBuffer b1 = StandardCharsets.UTF_8.encode("hello");ByteBuffer b2 = StandardCharsets.UTF_8.encode("world");ByteBuffer b3 = StandardCharsets.UTF_8.encode("你好");try (FileChannel channel = new RandomAccessFile("words2.txt", "rw").getChannel()) {channel.write(new ByteBuffer[]{b1, b2, b3});} catch (IOException e) {}}
}

Scattering的代码

public class TestScatteringReads {public static void main(String[] args) {try (FileChannel channel = new RandomAccessFile("words.txt", "r").getChannel()) {ByteBuffer b1 = ByteBuffer.allocate(3);ByteBuffer b2 = ByteBuffer.allocate(3);ByteBuffer b3 = ByteBuffer.allocate(5);channel.read(new ByteBuffer[]{b1, b2, b3});b1.flip();b2.flip();b3.flip();debugAll(b1);debugAll(b2);debugAll(b3);} catch (IOException e) {}}
}

网络编程代码:

友情提示:

Scattering:将数据写入到buffer时,可以采用buffer数组,依次写入 [分散]

Gathering: 从buffer读取数据时,可以采用buffer数组,依次读

package com.atguigu.nio;import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;/*** Scattering:将数据写入到buffer时,可以采用buffer数组,依次写入  [分散]* Gathering: 从buffer读取数据时,可以采用buffer数组,依次读*/
public class ScatteringAndGatheringTest {public static void main(String[] args) throws Exception {//使用 ServerSocketChannel 和 SocketChannel 网络ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);//绑定端口到socket ,并启动serverSocketChannel.socket().bind(inetSocketAddress);//创建buffer数组ByteBuffer[] byteBuffers = new ByteBuffer[2];byteBuffers[0] = ByteBuffer.allocate(5);byteBuffers[1] = ByteBuffer.allocate(3);//等客户端连接(telnet)SocketChannel socketChannel = serverSocketChannel.accept();int messageLength = 8;   //假定从客户端接收8个字节//循环的读取while (true) {int byteRead = 0;while (byteRead < messageLength ) {
//                从客户端读数据long l = socketChannel.read(byteBuffers);byteRead += l; //累计读取的字节数System.out.println("byteRead=" + byteRead);//使用流打印, 看看当前的这个buffer的position 和 limitArrays.asList(byteBuffers).stream().map(buffer -> "postion=" + buffer.position() + ", limit=" + buffer.limit()).forEach(System.out::println);}//将所有的buffer进行flipArrays.asList(byteBuffers).forEach(buffer -> buffer.flip());//将数据读出显示到客户端long byteWirte = 0;while (byteWirte < messageLength) {long l = socketChannel.write(byteBuffers); //byteWirte += l;}//将所有的buffer 进行clearArrays.asList(byteBuffers).forEach(buffer-> {buffer.clear();});System.out.println("byteRead:=" + byteRead + " byteWrite=" + byteWirte + ", messagelength" + messageLength);}}
}

Selector(选择器)又叫多路复用

友情提示:一个Selector对应多个Channel.一个线程对应一个Selector,Selector用于监听Channel事件,有事件发生,才会去处理。没有也不会造成阻塞,这个线程该干啥干啥去。redis单线程为什么效率高,也是存在IO的多路复用问题。

 示意图:

socket:表示客户端,中间没写Channle(对应的就是读写),多个Channel对应一个Selector。同样他也对应一个线程。

特点:

1、Netty的NioEventLoop就是聚合了Selector选择器。

2、如果当前通道是空闲的,那么线程就会切换到其他的通道执行任务。

Selector类相关方法

SelectedKeys()和Select()和keys()的介绍:

SelectedKeys()是获取,对应的某一个channel下只要注册到Selector上的有事件发生的就会返回,

Select()监控所有注册的通道,有IO操作可以进行,将其对应的Selectionkey加入到内部集合并将该Selectionkey返回,

selector.keys().size()获取的是,注册到selector上所有channel下 的所有的selectionkey的数量。

补充:

当Selector的select在进行监听的时候,如果没有事件发生就处于阻塞状态,如果有事件发生就不处于阻塞状态。

Selector的select()方法会监听发生事件的通道,一有事件发生,将其对应的 SelectionKey 加入到内部集合中并返回,内部集合就是对应下面的图。HashSet<SelectionKey>

SelectionKey内部又关联一个Channel

selector.select()//阻塞 也是一个阻塞方法

selector.select(1000);//阻塞1000毫秒,在1000毫秒后返回

selector.wakeup();//唤醒selector

selector.selectNow();//不阻塞,立马返还

select 何时不阻塞

下面的都是事件不发生阻塞的情况

  • 事件发生时

    • 客户端发起连接请求,会触发 accept 事件
    • 客户端发送数据过来,客户端正常、异常关闭时,都会触发 read 事件,另外如果发送的数据大于 buffer 缓冲区,会触发多次读取事件
    • channel 可写,会触发 write 事件
    • 在 linux 下 nio bug 发生时
  • 调用 selector.wakeup()
  • 调用 selector.close()
  • selector 所在线程 interrupt

事件发生后能否不处理

事件发生后,要么处理,要么取消(cancel),不能什么都不做,否则下次该事件仍会触发,这是因为 nio 底层使用的是水平触发

NIO 非阻塞 网络编程原理分析图

1、当客户端连接时,会通过服务端的ServerSocketChannel得到SocketChannel,通过ServerSocketChannel的accept()方法就能获得客户端的SocetChannel.

提示:在客户端注册到Selector之前,先将服务端的ServsocketChannel注册到Selector上也是调用的register()方法。

2、Selector进行监听, 通过调用Selector的select()方法,进行事件的监听,方法返回有事件发生的通道个数。

3、将socketChannel注册到Selector上, register(Selector sel, int ops), 一个 selector上可以注册多个SocketChannel

4、注册后返回一个 SelectionKey, 内部也会将其SelectionKey放入到HashMap集合中,并和该 Selector 关联(集合)

5、进而得到各个客户端的SelectionKey(有事件发生的通道)

6、在通过 SelectionKey 反向获取 SocketChannel , SelectionKey 调用方法 channel(),获得对应的Channel.当然也可以通过SelectionKey获取到Selector,只需要调用Selector()

7. 可以通过 得到的Channel,完成业务处理

SelectionKey

表示 Selector 和网络通道的注册关系总共四种:

SelectionKey相关方法

ServerSocketChannel(主要进行连接)

ServerSocketChannel 主要在服务器端监听新的客户端 Socket 连接

SocketChannel(主要进行数据的读写)

SocketChannel,网络 IO 通道,具体负责进行读写操作。NIO 把缓冲区的数据写入通 道,或者把通道里的数据读到缓冲区。

案例1

下面代码为何要 keyIterator.remove()

友情提示:说白了他不会主动的帮我们去删除,如果不删除,就一直放在集合中。如果没有删除还是获取没事删除的,这导致空指针异常,因为之前SelectionKey已经被我们用过了。

因为 select 在事件发生后,就会将相关的 key 放入 selectedKeys 集合,但不会在处理完后从 selectedKeys 集合中移除,需要我们自己编码删除。例如

  • 第一次触发了 accept 事件,没有移除
  • 第二次触发了 read 事件,但这时 selectedKeys 中还有上次的,在处理时因为没有真正的 serverSocket 连上了,就会导致空指针异常

cancel 的作用

cancel 会取消注册在 selector 上的 channel,并从 keys 集合中删除 key 后续不会再监听事件

友情提示:下面的案列,区分selector.keys()和selector.selectedKeys()

1、selector.keys()主要获取所有注册到Selector上的事件。包括服务端注册的

2、selector.selectedKeys(),表示的是有事件发生的,也就是正在发生的事件个数。

Set<SelectionKey> keys=selector.keys();
Set<SelectionKey> selectionKeys = selector.selectedKeys();

案例要求:

1) 编写一个 NIO 入门案例,实现服务器端和客户端之间的数据简单通讯(非阻塞)

2) 目的:理解NIO非阻塞网络编程机制

服务端代码:

package com.atguigu.nio;import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;public class NIOServer {public static void main(String[] args) throws Exception {// 创建ServerSocketChannel -> ServerSocketServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 得到一个Selecor对象Selector selector = Selector.open();// 绑定一个端口6666, 在服务器端监听serverSocketChannel.socket().bind(new InetSocketAddress(6666));// 设置为非阻塞serverSocketChannel.configureBlocking(false);// 把 serverSocketChannel 注册到  selector 关心 事件为 OP_ACCEPTserverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("注册后的selectionkey 数量=" + selector.keys().size()); // 1// 循环等待客户端连接while (true) {// 这里我们等待1秒,如果没有事件发生, 返回,select的调用,表示有事件发生的个数if (selector.select(1000) == 0) { // 没有事件发生System.out.println("服务器等待了1秒,无连接");continue;}// 如果返回的>0, 就获取到相关的 selectionKey集合// 1.如果返回的>0, 表示已经获取到关注的事件// 2. selector.selectedKeys() 返回关注事件的集合//   通过 selectionKeys 反向获取通道Set<SelectionKey> selectionKeys = selector.selectedKeys();System.out.println("selectionKeys 数量 = " + selectionKeys.size());// 遍历 Set<SelectionKey>, 使用迭代器遍历Iterator<SelectionKey> keyIterator = selectionKeys.iterator();while (keyIterator.hasNext()) {// 获取到SelectionKeySelectionKey key = keyIterator.next();// 根据key 对应的通道发生的事件做相应处理if (key.isAcceptable()) { // 如果是 OP_ACCEPT, 有新的客户端连接// 该该客户端生成一个 SocketChannelSocketChannel socketChannel = serverSocketChannel.accept();System.out.println("客户端连接成功 生成了一个 socketChannel " + socketChannel.hashCode());// 将  SocketChannel 设置为非阻塞socketChannel.configureBlocking(false);// 将socketChannel 注册到selector, 关注事件为 OP_READ, 同时给socketChannel// 关联一个Buffer,这个buf是在服务器端的socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));//                 selector.keys() 中的  keys()代表所有的// selector.selectedKeys()中的selectedKeys(),监听注册的通道中有事件发生的数量System.out.println("客户端连接后 ,注册的selectionkey 数量=" + selector.keys().size()); // 2,3,4..}if (key.isReadable()) { // 发生 OP_READ// 通过key 反向获取到对应channelSocketChannel channel = (SocketChannel) key.channel();// 获取到该channel关联的buffer
//          attachment得到与之关联的数据ByteBuffer buffer = (ByteBuffer) key.attachment();channel.read(buffer);System.out.println("form 客户端 " + new String(buffer.array()));}// 手动从集合中移动当前的selectionKey, 防止重复操作keyIterator.remove();}}}
}

客户端代码:

package com.atguigu.nio;import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;public class NIOClient {public static void main(String[] args) throws Exception{//得到一个网络通道SocketChannel socketChannel = SocketChannel.open();//设置非阻塞socketChannel.configureBlocking(false);//提供服务器端的ip 和 端口InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);//连接服务器,如果没有连接成功if (!socketChannel.connect(inetSocketAddress)) {while (!socketChannel.finishConnect()) {System.out.println("因为连接需要时间,客户端不会阻塞,可以做其它工作..");}}//...如果连接成功,就发送数据String str = "hello, 尚硅谷~";//Wraps a byte array into a buffer
//        这是客户端的buffer,wrap比较智能的将数据存储下缓存中,之前都是人工设定的,以前需要自己指定buffer的大小有了他就可以比较智能ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());//发送数据,将 buffer 数据写入 channelsocketChannel.write(buffer);System.in.read();}
}

案例2群聊

1)编写一个 NIO 群聊系统,实现服务器 端和客户端之间的数据简单通讯(非 阻塞)

2) 实现多人群聊

3) 服务器端:可以监测用户上线,离线, 并实现消息转发功能

4) 客户端:通过channel 可以无阻塞发送 消息给其它所有用户,同时可以接受 其它用户发送的消息(有服务器转发得 到)

 服务端代码


package com.atguigu.nio.groupchat;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;public class GroupChatServer {//定义属性private Selector selector;private ServerSocketChannel listenChannel;private static final int PORT = 6667;//构造器//初始化工作public GroupChatServer() {try {//得到选择器selector = Selector.open();//ServerSocketChannellistenChannel =  ServerSocketChannel.open();//绑定端口listenChannel.socket().bind(new InetSocketAddress(PORT));//设置非阻塞模式listenChannel.configureBlocking(false);//将该listenChannel 注册到selectorlistenChannel.register(selector, SelectionKey.OP_ACCEPT);}catch (IOException e) {e.printStackTrace();}}//监听public void listen() {System.out.println("监听线程: " + Thread.currentThread().getName());try {//循环处理while (true) {int count = selector.select();if(count > 0) {//有事件处理//遍历得到selectionKey 集合Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {//取出selectionkeySelectionKey key = iterator.next();//监听到acceptif(key.isAcceptable()) {SocketChannel sc = listenChannel.accept();sc.configureBlocking(false);//将该 sc 注册到seletorsc.register(selector, SelectionKey.OP_READ);//提示System.out.println(sc.getRemoteAddress() + " 上线 ");}if(key.isReadable()) { //通道发送read事件,即通道是可读的状态//处理读 (专门写方法..)readData(key);}//当前的key 删除,防止重复处理iterator.remove();}} else {System.out.println("等待....");}}}catch (Exception e) {e.printStackTrace();}finally {//发生异常处理....}}//读取客户端消息private void readData(SelectionKey key) {//取到关联的channleSocketChannel channel = null;try {//得到channelchannel = (SocketChannel) key.channel();//创建bufferByteBuffer buffer = ByteBuffer.allocate(1024);int count = channel.read(buffer);//根据count的值做处理if(count > 0) {//把缓存区的数据转成字符串String msg = new String(buffer.array());//输出该消息,从控制台输出System.out.println("form 客户端: " + msg);//向其它的客户端转发消息(去掉自己), 专门写一个方法来处理sendInfoToOtherClients(msg, channel);}}catch (IOException e) {try {System.out.println(channel.getRemoteAddress() + " 离线了..");//取消注册key.cancel();//关闭通道channel.close();}catch (IOException e2) {e2.printStackTrace();;}}}//转发消息给其它客户(通道)private void sendInfoToOtherClients(String msg, SocketChannel self ) throws  IOException{System.out.println("服务器转发消息中...");System.out.println("服务器转发数据给客户端线程: " + Thread.currentThread().getName());//遍历 所有注册到selector 上的 SocketChannel,并排除 selffor(SelectionKey key: selector.keys()) {//通过 key  取出对应的 SocketChannelChannel targetChannel = key.channel();//排除自己if(targetChannel instanceof  SocketChannel && targetChannel != self) {//转型SocketChannel dest = (SocketChannel)targetChannel;//将msg 存储到bufferByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());//将buffer 的数据写入 通道dest.write(buffer);}}}public static void main(String[] args) {//创建服务器对象GroupChatServer groupChatServer = new GroupChatServer();groupChatServer.listen();}
}//可以写一个Handler
class MyHandler {public void readData() {}public void sendInfoToOtherClients(){}
}

客户端代码

package com.atguigu.nio.groupchat;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.Scanner;
import java.util.Set;public class GroupChatClient {//定义相关的属性private final String HOST = "127.0.0.1"; // 服务器的ipprivate final int PORT = 6667; //服务器端口private Selector selector;private SocketChannel socketChannel;private String username;//构造器, 完成初始化工作public GroupChatClient() throws IOException {selector = Selector.open();//连接服务器socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));//设置非阻塞socketChannel.configureBlocking(false);//将channel 注册到selectorsocketChannel.register(selector, SelectionKey.OP_READ);//得到usernameusername = socketChannel.getLocalAddress().toString().substring(1);System.out.println(username + " is ok...");}//向服务器发送消息public void sendInfo(String info) {info = username + " 说:" + info;try {socketChannel.write(ByteBuffer.wrap(info.getBytes()));}catch (IOException e) {e.printStackTrace();}}//读取从服务器端回复的消息public void readInfo() {try {int readChannels = selector.select();if(readChannels > 0) {//有可以用的通道Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if(key.isReadable()) {//得到相关的通道SocketChannel sc = (SocketChannel) key.channel();//得到一个BufferByteBuffer buffer = ByteBuffer.allocate(1024);//读取sc.read(buffer);//把读到的缓冲区的数据转成字符串String msg = new String(buffer.array());System.out.println(msg.trim());}}iterator.remove(); //删除当前的selectionKey, 防止重复操作} else {//System.out.println("没有可以用的通道...");}}catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) throws Exception {//启动我们客户端GroupChatClient chatClient = new GroupChatClient();//启动一个线程, 每个3秒,读取从服务器发送数据new Thread() {public void run() {while (true) {chatClient.readInfo();try {Thread.currentThread().sleep(3000);}catch (InterruptedException e) {e.printStackTrace();}}}}.start();//发送数据给服务器端Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()) {String s = scanner.nextLine();chatClient.sendInfo(s);}}}

处理消息的边界(简单介绍)

也就是我们说的粘包粘包问题,后面还会详细说的。只是简单介绍

由于网络的不确定性,导致一个消息被拆分变成不完整,又或者导致一个消息完整但是尾部多出了不属于自己的消息。

解决方案:

  • 思路一:固定消息长度,数据包大小一样,服务器按预定长度读取,缺点是浪费带宽
  • 思路二:按分隔符拆分,缺点是效率低
  • 思路三:TLV 格式,即 Type 类型、Length 长度、Value 数据,类型和长度已知的情况下,就可以方便获取消息大小,分配合适的 buffer,缺点是 buffer 需要提前分配,如果内容过大,则影响 server 吞吐量
    • Http 1.1 是 TLV 格式
    • Http 2.0 是 LTV 格式

这个思路三有点想Java的class文件的内部构造

代码:

package cn.itcast.nio.c2;import java.nio.ByteBuffer;import static cn.itcast.nio.c2.ByteBufferUtil.debugAll;//粘包,拆包问题
public class TestByteBufferExam {public static void main(String[] args) {/*网络上有多条数据发送给服务端,数据之间使用 \n 进行分隔但由于某种原因这些数据在接收时,被进行了重新组合,例如原始数据有3条为Hello,world\nI'm zhangsan\nHow are you?\n变成了下面的两个 byteBuffer (黏包,半包)Hello,world\nI'm zhangsan\nHow are you?\n现在要求你编写程序,将错乱的数据恢复成原始的按 \n 分隔的数据*/ByteBuffer source = ByteBuffer.allocate(32);source.put("Hello,world\nI'm zhangsan\nHo".getBytes());split(source);source.put("w are you?\n".getBytes());split(source);}private static void split(ByteBuffer source) {
//        转换成写模式source.flip();
//        遍历Bytebuffer实际的长度for (int i = 0; i < source.limit(); i++) {// 找到一条完整消息,如果有换行符就是一个消息if (source.get(i) == '\n') {
//                获取长度存储到另一个bytebuffer中,获取的元素的位置+1减去资源的起始位置int length = i + 1 - source.position();// 把这条完整消息存入新的 ByteBufferByteBuffer target = ByteBuffer.allocate(length);// 从 source 读,向 target 写for (int j = 0; j < length; j++) {target.put(source.get());}debugAll(target);}}
//        位置前移source.compact();}
}

Utis:

package cn.itcast.nio.c2;import io.netty.util.internal.StringUtil;import java.nio.ByteBuffer;import static io.netty.util.internal.MathUtil.isOutOfBounds;
import static io.netty.util.internal.StringUtil.NEWLINE;public class ByteBufferUtil {private static final char[] BYTE2CHAR = new char[256];private static final char[] HEXDUMP_TABLE = new char[256 * 4];private static final String[] HEXPADDING = new String[16];private static final String[] HEXDUMP_ROWPREFIXES = new String[65536 >>> 4];private static final String[] BYTE2HEX = new String[256];private static final String[] BYTEPADDING = new String[16];static {final char[] DIGITS = "0123456789abcdef".toCharArray();for (int i = 0; i < 256; i++) {HEXDUMP_TABLE[i << 1] = DIGITS[i >>> 4 & 0x0F];HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F];}int i;// Generate the lookup table for hex dump paddingsfor (i = 0; i < HEXPADDING.length; i++) {int padding = HEXPADDING.length - i;StringBuilder buf = new StringBuilder(padding * 3);for (int j = 0; j < padding; j++) {buf.append("   ");}HEXPADDING[i] = buf.toString();}// Generate the lookup table for the start-offset header in each row (up to 64KiB).for (i = 0; i < HEXDUMP_ROWPREFIXES.length; i++) {StringBuilder buf = new StringBuilder(12);buf.append(NEWLINE);buf.append(Long.toHexString(i << 4 & 0xFFFFFFFFL | 0x100000000L));buf.setCharAt(buf.length() - 9, '|');buf.append('|');HEXDUMP_ROWPREFIXES[i] = buf.toString();}// Generate the lookup table for byte-to-hex-dump conversionfor (i = 0; i < BYTE2HEX.length; i++) {BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i);}// Generate the lookup table for byte dump paddingsfor (i = 0; i < BYTEPADDING.length; i++) {int padding = BYTEPADDING.length - i;StringBuilder buf = new StringBuilder(padding);for (int j = 0; j < padding; j++) {buf.append(' ');}BYTEPADDING[i] = buf.toString();}// Generate the lookup table for byte-to-char conversionfor (i = 0; i < BYTE2CHAR.length; i++) {if (i <= 0x1f || i >= 0x7f) {BYTE2CHAR[i] = '.';} else {BYTE2CHAR[i] = (char) i;}}}/*** 打印所有内容* @param buffer*/public static void debugAll(ByteBuffer buffer) {int oldlimit = buffer.limit();buffer.limit(buffer.capacity());StringBuilder origin = new StringBuilder(256);appendPrettyHexDump(origin, buffer, 0, buffer.capacity());System.out.println("+--------+-------------------- all ------------------------+----------------+");System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), oldlimit);System.out.println(origin);buffer.limit(oldlimit);}/*** 打印可读取内容* @param buffer*/public static void debugRead(ByteBuffer buffer) {StringBuilder builder = new StringBuilder(256);appendPrettyHexDump(builder, buffer, buffer.position(), buffer.limit() - buffer.position());System.out.println("+--------+-------------------- read -----------------------+----------------+");System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), buffer.limit());System.out.println(builder);}public static void main(String[] args) {ByteBuffer buffer = ByteBuffer.allocate(10);buffer.put(new byte[]{97, 98, 99, 100});debugAll(buffer);}private static void appendPrettyHexDump(StringBuilder dump, ByteBuffer buf, int offset, int length) {if (isOutOfBounds(offset, length, buf.capacity())) {throw new IndexOutOfBoundsException("expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length+ ") <= " + "buf.capacity(" + buf.capacity() + ')');}if (length == 0) {return;}dump.append("         +-------------------------------------------------+" +NEWLINE + "         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |" +NEWLINE + "+--------+-------------------------------------------------+----------------+");final int startIndex = offset;final int fullRows = length >>> 4;final int remainder = length & 0xF;// Dump the rows which have 16 bytes.for (int row = 0; row < fullRows; row++) {int rowStartIndex = (row << 4) + startIndex;// Per-row prefix.appendHexDumpRowPrefix(dump, row, rowStartIndex);// Hex dumpint rowEndIndex = rowStartIndex + 16;for (int j = rowStartIndex; j < rowEndIndex; j++) {dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);}dump.append(" |");// ASCII dumpfor (int j = rowStartIndex; j < rowEndIndex; j++) {dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);}dump.append('|');}// Dump the last row which has less than 16 bytes.if (remainder != 0) {int rowStartIndex = (fullRows << 4) + startIndex;appendHexDumpRowPrefix(dump, fullRows, rowStartIndex);// Hex dumpint rowEndIndex = rowStartIndex + remainder;for (int j = rowStartIndex; j < rowEndIndex; j++) {dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);}dump.append(HEXPADDING[remainder]);dump.append(" |");// Ascii dumpfor (int j = rowStartIndex; j < rowEndIndex; j++) {dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);}dump.append(BYTEPADDING[remainder]);dump.append('|');}dump.append(NEWLINE +"+--------+-------------------------------------------------+----------------+");}private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) {if (row < HEXDUMP_ROWPREFIXES.length) {dump.append(HEXDUMP_ROWPREFIXES[row]);} else {dump.append(NEWLINE);dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L));dump.setCharAt(dump.length() - 9, '|');dump.append('|');}}public static short getUnsignedByte(ByteBuffer buffer, int index) {return (short) (buffer.get(index) & 0xFF);}
}

Netty大战之NIO相关推荐

  1. 源码剖析 Netty 服务启动 NIO

    如果这个文章看不懂的话 , 建议反复阅读 Netty 与 Reactor 开篇立意.引用网友好的建议.看源码要针对性的看,最佳实践就是,带着明确的目的去看源码.抓主要问题,放弃小问题.主要的逻辑理解了 ...

  2. java netty教程_Java NIO框架Netty教程(一) – Hello Netty

    先啰嗦两句,如果你还不知道Netty是做什么的能做什么.那可以先简单的搜索了解一下.我只能说Netty是一个NIO的框架,可以用于开发分布式的Java程序.具体能做什么,各位可以尽量发挥想象.技术,是 ...

  3. Netty入门——基于NIO实现机器客服案例

    Netty简单案例 前言 环境准备 前置知识 网络传输的几种实现方式 BIO--同步阻塞IO NIO--同步非阻塞IO AIO--异步非阻塞IO 适用范围 Netty 简介 特点 核心组件 使用场景 ...

  4. netty系列之:NIO和netty详解

    文章目录 简介 NIO常用用法 NIO和EventLoopGroup NioEventLoopGroup SelectorProvider SelectStrategyFactory Rejected ...

  5. java nio框架netty教程_Java NIO框架Netty教程(一) – Hello Netty

    先啰嗦两句,如果你还不知道Netty是做什么的能做什么.那可以先简单的搜索了解一下.我只能说Netty是一个NIO的框架,可以用于开发分布式的Java程序.具体能做什么,各位可以尽量发挥想象.技术,是 ...

  6. 【Netty】Netty 简介 ( 原生 NIO 弊端 | Netty 框架 | Netty 版本 | 线程模型 | 线程 阻塞 IO 模型 | Reactor 模式引入 )

    文章目录 一. NIO 原生 API 弊端 二. Netty 简介 三. Netty 架构 四. Netty 版本 五. Netty 线程模型 六. 阻塞 IO 线程模型 七. 反应器 ( React ...

  7. netty 为什么用nio 不用 aio

    NIO模型 同步非阻塞 NIO有同步阻塞和同步非阻塞两种模式,一般讲的是同步非阻塞,服务器实现模式为一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才 ...

  8. epoll nio区别_高性能网络服务器编程:为什么linux下epoll是最好,Netty要比NIO.2好?...

    基本的IO编程过程(包括网络IO和文件IO)是,打开文件描述符(windows是handler,java是stream或channel),多路捕获(Multiplexe,即select和poll和ep ...

  9. Netty学习(一)-- Netty 底层 Java NIO

    视频地址,建议观看,老师由浅入深,循序渐进: https://www.bilibili.com/video/BV1py4y1E7oA 前面的学习:https://blog.csdn.net/weixi ...

最新文章

  1. 在Eclipse中安装ADT
  2. 分布式理论(一)CAP 理论
  3. CH4402 小Z的袜子(莫队)
  4. python pandas dataframe 转json_python-将嵌套的json转换为pandas dataframe
  5. 字体海报设计灵感|这海报的字体够别致,脑洞够大!
  6. 启动tomcat后无法访问
  7. java 生日 计算_java根据生日计算当前年龄,精确到月
  8. python核心编程
  9. python 移动文件 覆盖_Python操作文件(删除、复制、移动...)
  10. 贪吃蛇的c语言运行程序,用C语言编写贪吃蛇游戏的程序
  11. 纯JAVA写的socket局域网斗地主游戏
  12. raft算法 java_raft-java首页、文档和下载 - 分布式一致性算法 Raft 的 Java 实现 - OSCHINA - 中文开源技术交流社区...
  13. 微信隐藏功能系列:微信朋友圈三天可见怎么设置?
  14. 生活中的一些哲理名言
  15. 北京林业大学matlab公选课,北京林业大学视频类公共选修课学习指引-北京林业大学教务处.DOC...
  16. 经常手误怎么办?Delete键旁边是电源键
  17. depts: deep expansion learning for periodic time series forecasting
  18. web前端面试学习指南
  19. 机器学习实战之朴素贝叶斯与垃圾邮件分类
  20. nvidia jetson agx xavier运行 OpenCL

热门文章

  1. 指数分布与幂律分布定义及不同(泊松分布、伽马分布)
  2. 一文了解国家为什么大力推行OFD版式文件格式?
  3. PHPCMS v9 文件后缀提取错误代码上传漏洞
  4. Premiere Pro之抠像(八)
  5. 用白嫖的Adobe正版软件,减少应届毕业生的慢就业、不就业等现象
  6. 中文维基百科文本数据获取与预处理
  7. “泻药,人在知乎,刚吃月饼”,众大V吃定制月饼后腹泻,知乎道歉
  8. 【矩阵】- “之”字形打印
  9. 微信小程序保存视频到相册wx.saveVideoToPhotosAlbum()
  10. Flutter 对齐没对齐。总感觉自己对不齐