描述:
在java程序中,常用的零拷贝有mmap(内存映射) 和 sendFile,那么他们在系统里,到底是怎么样的一个设计?
在nio中是如何实现零拷贝的呢?
传统io拷贝
直接内存拷贝:
从图中可以看到,内核缓冲区,拷贝到 用户缓冲,在拷贝到socket 缓冲,在dma 拷贝,这是非常耗费资源的。
mmap优化
mmap 通过内存映射,将文件映射到内核缓冲区中,同时用户空间可以共享内核空间的数据,这样在网络传输的时候,就可以减少内核空间到用户空间的拷贝。
sendFile 1.0
数据传输不经过用户态,直接从内核缓冲区 进入 到 socket buffer,这样就少一次 用户态的上下文切换。
sendFile 2.0
sendFile 再次升级,内核缓冲区,直接不经过 socket buffer 直接和协议栈进行交互,这样就再一次减少了拷贝,
注意: 这里内核缓冲区,会拷贝一些少量的数据到 socket buffer区,但是这些数据基本可以忽略不计。
代码实例:
传统io 网络传输实例:
/**
* @Author qrn
* @Title
* @Date 2021/5/14 10:06
* @time 10:06
* java io 拷贝 服务端:
*/
public class BioServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(7001);
while (true){
//等待连接请求
Socket socket = serverSocket.accept();
//获取 输入流,客户端发送的数据流:
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
try{
byte[] byteArray = new byte[4096];
while (true){
//读取数据 等于 -1的时候 就表示数据读取完,就跳出循环
int read = dataInputStream.read(byteArray, 0, byteArray.length);
if(-1 == read){
break;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
/**
* @Author qrn
* @Title
* @Date 2021/5/14 19:07
* @time 19:07
* java io 客户端 ,发送一个 zip包到服务端,传输查看花费的时间。
*/
public class BioClient {
public static void main(String[] args) throws Exception{
Socket socket = new Socket("127.0.0.1",7001);
String fileName = "protoc-3.6.1-win32.rar";
//输入流
FileInputStream fileInputStream = new FileInputStream(fileName);
//输出流,发送文件给服务端:
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
byte[] buffer = new byte[4096];
long readcout;
long total = 0;
long startTime = System.currentTimeMillis();
while ((readcout = fileInputStream.read(buffer)) >= 0){
total += readcout;
dataOutputStream.write(buffer);
}
System.out.println("发送总字节数: "+total +", 耗时: "+(System.currentTimeMillis() - startTime));
dataOutputStream.close();;
socket.close();
fileInputStream.close();
}
}
nio 零拷贝
/**
* @Author qrn
* @Title
* @Date 2021/5/14 19:27
* @time 19:27
* nio 服务端,零拷贝实例:
*/
public class NioServer {
public static void main(String[] args) throws Exception {
InetSocketAddress address = new InetSocketAddress(7001);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(address);
ByteBuffer allocate = ByteBuffer.allocate(4096);
while (true){
SocketChannel socketChannel = serverSocketChannel.accept();
int readcount = 0;
while (-1 != readcount){
try{
readcount = socketChannel.read(allocate);
}catch (Exception e) {
break;
}
allocate.rewind();// 倒带
}
}
}
}
/**
* @Author qrn
* @Title
* @Date 2021/5/14 19:38
* @time 19:38
* nio 零拷贝 客户端:
*/
public class NioCllient {
public static void main(String[] args) throws Exception{
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 7001));
String fileName = "protoc-3.6.1-win32.rar";
FileChannel fileChannel = new FileInputStream(fileName).getChannel();
long startTime = System.currentTimeMillis();
/**
* transferTo 在linux 下可以完成全部传输,在Windows 下一次只能传 8 m
*/
long transferCount = 0;
//如果是windows 就需要循环
if(System.getProperty("os.name").toLowerCase().contains("win")){
transferCount = testFileChannel(fileChannel,socketChannel,transferCount);
}else{
transferCount += fileChannel.transferTo(0, fileChannel.size(), socketChannel);
}
System.out.println("发送的总的字节数 =" + transferCount + " 耗时:" + (System.currentTimeMillis() - startTime));
//关闭
fileChannel.close();
}
public static Long testFileChannel(FileChannel fileChannel,SocketChannel socketChannel,long transferCount) throws Exception {
Integer num = 8*1024*1024;
Long fiel = fileChannel.size();
while (true){
if(fiel <= num){
transferCount += fileChannel.transferTo(0, fiel, socketChannel);
break;
}
transferCount += fileChannel.transferTo(0, num, socketChannel);
fiel = fiel - num;
}
return transferCount;
}
}
代码实例放在github上面了,需要的可以自己去上面看 : Nrtty-Notes
如果这篇文章,有帮助到大家的,请给作者一个一键三连,谢谢。