文章目录
1.前言
此篇博文主要把使用Netty需要了解的知识进行整理并根据自己的理解加以补充。由于内容较多所以将笔记分为多个部分,同时更新速度可能会比较慢,希望读者多包涵。
2.零拷贝
2.1零拷贝基本介绍
- 零拷贝是一种I/O操作优化技术,可以快速高效地将数据从文件系统移动到网络接口,不需要将其从内核空间复制到用户空间。
- Java 常用的零拷贝技术有mmap(内存映射)、sendFile
传统I/O读写实例
2.2.传统I/O读写实例
File file = new File("test.txt");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
byte[] arr = new byte[(int) file.length()];
raf.read(arr);
Socket socket = new ServerSocket(8080).accept();
socket.getOutputStream().write(arr);
2.3.传统I/O模型
DMA
:direct memory access 直接内存拷贝
2.4.零拷贝技术原理
- 基本介绍:零拷贝技术主要是用于处理操作系统在处理I/O操作,频繁的数据复制(主要是不产生CPU复制)
- 主要技术:
mmap、sendFIle,splice
等几种方式。
2.4.1mmap优化
- 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。
- mmap示意图
2.4.2.sendFile优化
- Linux2.1 版本提供了 sendFile 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 SocketBuffer,同时,由于和用户态完全无关,就减少了一次上下文切换
- Linux在2.4 版本中,做了一些修改,避免了从内核缓冲区拷贝到 Socketbuffer 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。
2.4.3.splice方式
-
splice
和sendFile
非常类似,用户的应用程序必须拥有两个文件描述符,一个表示输入设备另一个表示输出设备。 - 与sendFile不同的是,
splice允许任意一个文件之间互相连接
,这是splice一直使用的机制。 - 在Linux2.6.17版本中引入了splice,而在2.6.23的版本中,sendFile机制实现已经没有了。
- 与sendFile不同的是
splice不需要硬件支持
。
2.4.4.mmap 和 sendFile 的区别
mmap
适合小数据量读写
,sendFile
适合大文件传输
。mmap
需要4
次上下文切换,3
次数据拷贝;sendFile
需要3
次上下文切换,最少2
次数据拷贝。sendFile
可以利用DMA
方式,减少
CPU 拷贝,mmap
则不能(必须从内核拷贝到 Socket缓冲区
)。
2.4.5.零拷贝理解
- 我们说零拷贝,是
从操作系统的角度
来说的。因为内核缓冲区之间,没有数据是重复的(只有 kernel buffer 有一份数据)。 - 零拷贝不仅仅带来
更少的数据复制
,还能带来其他的性能优势,例如更少的上下文切换,更少的 CPU 缓存伪共享以及无 CPU 校验和计算。 - 所谓零拷贝都是为了
减少CPU copy 和上下文切换
。
2.5.各种零拷贝技术对比
CPU拷贝 | DMA拷贝 | 系统调用 | 上下文切换 | |
---|---|---|---|---|
传统I/0 | 2 | 2 | read/write | 4 |
mmap | 1 | 2 | mmap/write | 4 |
sendFile | 1 | 2 | sendFile | 2 |
sendFile with dma scatter/gather copy | 0 | 2 | sendFile | 2 |
splice | 0 | 2 | splice | 2 |
2.6.Java 零拷贝实现
2.6.1基本介绍
* 1\使用传统的 IO 方法传递一个大文件
* 2\使用 NIO 零拷贝方式传递(transferTo)一个大文件
* 3\看看两种传递方式耗时时间分别是多少
2.6.2.传统方案
/**
* 传统I/o拷贝文件
*/
public static void bioCopyFileClient() throws IOException {
Socket client = new Socket("127.0.0.1",12345);
OutputStream outputStream = client.getOutputStream();
FileInputStream fileOutputStream = new FileInputStream("C:\\Users\\admin\\Desktop\\fsdownload\\degLog.zip");
long startTime = System.currentTimeMillis();
byte[] bytes = new byte[1024];
int len ;
while( (len = fileOutputStream.read(bytes))!=-1){
outputStream.write(bytes,0,len);
}
System.out.println("发送的总的字节数 = " + fileOutputStream.getChannel().size() + " 耗时: " + (System.currentTimeMillis() - startTime));
client.close();
outputStream.close();
fileOutputStream.close();
}
/**
* 传统I/o拷贝文件
*/
public static void bioCopyFile() throws IOException {
ExecutorService executorService = new ThreadPoolExecutor(4,8,0L, TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>());
ServerSocket serverSocket = new ServerSocket(12345);
System.out.println("服务启动了");
while (true){
Socket socket = serverSocket.accept();
System.out.println("有客户端连接了");
executorService.execute(new Runnable() {
@Override
public void run() {
byte[] bytes = new byte[1024];
try {
while (true){
InputStream inputStream = socket.getInputStream();
int read = inputStream.read(bytes);
if(read!=-1){
System.out.println(new String(bytes,0,read));
}else {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
System.out.println("服务端关闭");
}
}
});
}
}
2.6.3.零拷贝方案
/**
* 零拷贝文件
*/
public static void zeroCopyFile() throws IOException {
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket socket = serverSocketChannel.socket();
serverSocketChannel.bind(inetSocketAddress);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while(true){
int read = 0;
SocketChannel socketChannel = serverSocketChannel.accept();
while (read!=-1) {
try {
read = socketChannel.read(byteBuffer);
}catch (Exception e){
break;
}
}
byteBuffer.rewind();
}
}
/**
* 零拷贝文件
*/
public static void zeroCopyFileClient() throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
FileChannel fileChannel = new FileInputStream("C:\\Users\\admin\\Desktop\\fsdownload\\degLog.zip").getChannel();
//准备发送
long startTime = System.currentTimeMillis();
long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
System.out.println("发送的总的字节数 = " + transferCount + " 耗时: " + (System.currentTimeMillis() - startTime));
}
传统方案:发送的总的字节数 = 183977 耗时: 110
零拷贝方案:发送的总的字节数 = 183977 耗时: 10
2.7.Java AIO 基本介绍
- 从Java 1.7开始,Java 提供AIO这是异步AIO,Java AIO也称为NIO2.0,提供异步I/O的方式。
- Java AIO采用“订阅-通知”模式。
- Java AIO也是由操作系统通知的,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用
- AIO也是位于java.nio包下
3.BIO、NIO、AIO 对比总结
项目 | BIO | NIO | AIO |
---|---|---|---|
I/O模型 | 同步阻塞 | 异步阻塞 | 异步非阻塞 |
吞吐量 | 低 | 高 | 高 |
可靠性 | 差 | 好 | 好 |
编程难度 | 简单 | 复杂 | 复杂 |