Netty大战之NIO
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相关推荐
- 源码剖析 Netty 服务启动 NIO
如果这个文章看不懂的话 , 建议反复阅读 Netty 与 Reactor 开篇立意.引用网友好的建议.看源码要针对性的看,最佳实践就是,带着明确的目的去看源码.抓主要问题,放弃小问题.主要的逻辑理解了 ...
- java netty教程_Java NIO框架Netty教程(一) – Hello Netty
先啰嗦两句,如果你还不知道Netty是做什么的能做什么.那可以先简单的搜索了解一下.我只能说Netty是一个NIO的框架,可以用于开发分布式的Java程序.具体能做什么,各位可以尽量发挥想象.技术,是 ...
- Netty入门——基于NIO实现机器客服案例
Netty简单案例 前言 环境准备 前置知识 网络传输的几种实现方式 BIO--同步阻塞IO NIO--同步非阻塞IO AIO--异步非阻塞IO 适用范围 Netty 简介 特点 核心组件 使用场景 ...
- netty系列之:NIO和netty详解
文章目录 简介 NIO常用用法 NIO和EventLoopGroup NioEventLoopGroup SelectorProvider SelectStrategyFactory Rejected ...
- java nio框架netty教程_Java NIO框架Netty教程(一) – Hello Netty
先啰嗦两句,如果你还不知道Netty是做什么的能做什么.那可以先简单的搜索了解一下.我只能说Netty是一个NIO的框架,可以用于开发分布式的Java程序.具体能做什么,各位可以尽量发挥想象.技术,是 ...
- 【Netty】Netty 简介 ( 原生 NIO 弊端 | Netty 框架 | Netty 版本 | 线程模型 | 线程 阻塞 IO 模型 | Reactor 模式引入 )
文章目录 一. NIO 原生 API 弊端 二. Netty 简介 三. Netty 架构 四. Netty 版本 五. Netty 线程模型 六. 阻塞 IO 线程模型 七. 反应器 ( React ...
- netty 为什么用nio 不用 aio
NIO模型 同步非阻塞 NIO有同步阻塞和同步非阻塞两种模式,一般讲的是同步非阻塞,服务器实现模式为一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才 ...
- epoll nio区别_高性能网络服务器编程:为什么linux下epoll是最好,Netty要比NIO.2好?...
基本的IO编程过程(包括网络IO和文件IO)是,打开文件描述符(windows是handler,java是stream或channel),多路捕获(Multiplexe,即select和poll和ep ...
- Netty学习(一)-- Netty 底层 Java NIO
视频地址,建议观看,老师由浅入深,循序渐进: https://www.bilibili.com/video/BV1py4y1E7oA 前面的学习:https://blog.csdn.net/weixi ...
最新文章
- 在Eclipse中安装ADT
- 分布式理论(一)CAP 理论
- CH4402 小Z的袜子(莫队)
- python pandas dataframe 转json_python-将嵌套的json转换为pandas dataframe
- 字体海报设计灵感|这海报的字体够别致,脑洞够大!
- 启动tomcat后无法访问
- java 生日 计算_java根据生日计算当前年龄,精确到月
- python核心编程
- python 移动文件 覆盖_Python操作文件(删除、复制、移动...)
- 贪吃蛇的c语言运行程序,用C语言编写贪吃蛇游戏的程序
- 纯JAVA写的socket局域网斗地主游戏
- raft算法 java_raft-java首页、文档和下载 - 分布式一致性算法 Raft 的 Java 实现 - OSCHINA - 中文开源技术交流社区...
- 微信隐藏功能系列:微信朋友圈三天可见怎么设置?
- 生活中的一些哲理名言
- 北京林业大学matlab公选课,北京林业大学视频类公共选修课学习指引-北京林业大学教务处.DOC...
- 经常手误怎么办?Delete键旁边是电源键
- depts: deep expansion learning for periodic time series forecasting
- web前端面试学习指南
- 机器学习实战之朴素贝叶斯与垃圾邮件分类
- nvidia jetson agx xavier运行 OpenCL
热门文章
- 指数分布与幂律分布定义及不同(泊松分布、伽马分布)
- 一文了解国家为什么大力推行OFD版式文件格式?
- PHPCMS v9 文件后缀提取错误代码上传漏洞
- Premiere Pro之抠像(八)
- 用白嫖的Adobe正版软件,减少应届毕业生的慢就业、不就业等现象
- 中文维基百科文本数据获取与预处理
- “泻药,人在知乎,刚吃月饼”,众大V吃定制月饼后腹泻,知乎道歉
- 【矩阵】- “之”字形打印
- 微信小程序保存视频到相册wx.saveVideoToPhotosAlbum()
- Flutter 对齐没对齐。总感觉自己对不齐