NIO零拷贝
java的 transformTo transformFrom
java是平台无关的,但是JVM是平台相关的。
磁盘上文件拿出来发给用户
最原始的实现(c实现)
内核空间系统调用--上下文切换--直接内存访问DMA 数据拷贝到内核空间缓冲区(页缓存)--上下文切换--又拷贝到了用户空间的缓冲区
内核空间系统调用--上下文切换--用户空间 数据拷贝到内核空间缓冲区(页缓存)--又拷贝到了网卡的socket缓冲区--上下文切换--返回用户空间
用户空间没有对数据修改
- 4次上下文切换
- 2次系统调用
- 4次数据拷贝

零拷贝(c实现)
完全依赖操作系统的,不会有数据在用户内核之间拷贝
sendfile() 内核空间系统调用--上下文切换--直接内存访问DMA 数据拷贝到内核空间缓冲区(页缓存)--数据拷贝到目标socket的缓冲区--缓冲区到DMA网卡发送--上下文切换--返回用户空间

真正的零拷贝(c实现)
文件描述符可以描述数据的一些大小偏移,socket缓存里面只是存这些,
DMA 拷贝内核缓冲区(页缓存)之后--Linux 2.4 之后,文件描述符gather操作,数据不会kernel buffer到socketbuffer,****只有文件描述符的信息(kernel buffer 的内存地址在什么地方;kernel buffer 多长)会到socket buffer里面。原来的数据本身
真正的数据直接去网卡缓冲区



零拷贝案例
服务端读取完就丢弃掉
客户端打印需要的时间
传统方式
import java.io.DataInputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class OldIOServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8899);
while (true) {
// 连接 建立起来才会不阻塞
Socket socket = serverSocket.accept();
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
try {
byte[] byteArray = new byte[4096];
while (true) {
// 实际读到的字节数
int readCount = dataInputStream.read(byteArray, 0, byteArray.length);
// 读完就返回-1
if (-1 == readCount) {
break;
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;
public class OldIOClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost", 8899);
String fileName = "~/Desktop/spark-2.2.0-bin-hadoop2.7.tgz";
InputStream inputStream = new FileInputStream(fileName);
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
byte[] buffer = new byte[4096];
long readCount;
long total = 0;
long startTime = System.currentTimeMillis();
while ((readCount = inputStream.read(buffer)) >= 0) {
total += readCount;
dataOutputStream.write(buffer);
}
// 200多MB
// 400多ms
System.out.println("发送总字节数: " + total + ", 耗时: " + (System.currentTimeMillis() - startTime));
dataOutputStream.close();
socket.close();
inputStream.close();
}
}
零拷贝
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class NewIOServer {
public static void main(String[] args) throws Exception{
InetSocketAddress address = new InetSocketAddress(8899);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
// 连接关闭之后会有超时状态,超时时候就可以重用了
// 端口号处于time wait的时候是不可以绑定连接的,true就可以绑定,即便是time out状态
// 处于time wait就可以重用
serverSocket.setReuseAddress(true);
serverSocket.bind(address);
// 字节数数组的大小是4096
ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
while (true) {
// false 非阻塞,没有Selector,所以就要用阻塞的
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(true);
int readCount = 0;
while (-1 != readCount) {
try {
// position到最后没法read了
readCount = socketChannel.read(byteBuffer);
} catch (Exception ex) {
ex.printStackTrace();
}
//
// 磁带倒带,Mark丢弃,position变为0.
// rewind与flip的用法差异
// https://blog.csdn.net/yiifaa/article/details/77652914
byteBuffer.rewind();
}
}
}
}
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
public class NewIOClient {
public static void main(String[] args) throws Exception {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8899));
socketChannel.configureBlocking(true);
String fileName = "~/Desktop/spark-2.2.0-bin-hadoop2.7.tgz";
FileChannel fileChannel = new FileInputStream(fileName).getChannel();
long startTime = System.currentTimeMillis();
// 使用 FileChannel transferTo 就是写到 socketChannel
// 传递多少就是 0, fileChannel.size(),
// transferTo 比简单的循环效率更高,很多OS会直接从文件系统缓存直接写到目标Channel。不会从源Channel拷贝到目标
long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
// 149ms
System.out.println("发送总字节数:" + transferCount + ",耗时: " + (System.currentTimeMillis() - startTime));
fileChannel.close();
}
}
磁盘上文件修改一下发给用户
内存映射
文件内容映射,用户空间直接操作内核空间的文件
java里面的MappedByteBuffer就是这个在支持
posted on 2025-10-13 17:42 chuchengzhi 阅读(12) 评论(0) 收藏 举报
浙公网安备 33010602011771号