零拷贝
- 零拷贝,从操作系统角度看,没有CPU拷贝,在内核缓冲区中,数据是没有重复的,更少的上下文切换
- java中常用的零拷贝
mmap 内存映射、sendFile
- DMA,direct memory access,直接内存拷贝,不使用cpu
- 传统IO传输,四次拷贝,四次状态切换

mmap优化
- 通过内存映射将文件映射到内核缓冲区,用户空间共享内核空间的数据,在网络传输时,减少内核空间到用户空间的拷贝次数
- 三次拷贝,四次状态切换

sendFile优化
- 数据不经过用户态,直接从内存缓冲区进入Socket Buffer,与用户态完全无关,减少状态切换
- 三次拷贝,两次状态切换

sendFile进一步优化
- 大部分数据从内存缓冲区直接拷贝到
协议栈
- 小部分数据需要拷贝到Socket Buffer,如length,offset,信息量少,消耗低
- 两次拷贝,两次状态切换

mmap和sendFile
- mmap适合小数据量读写,sendFile适合大文件传输
- sendFile利用DMA减少CPU拷贝,mmap不能,必须从内核拷贝到Socket缓冲区
NIO中的零拷贝
传统
Socket s = new Socket("localhost", 7001)
String fileName = "/Users/mzx/Desktop/java/WechatIMG1022.jpg"
FileInputStream fis = new FileInputStream(fileName)
DataOutputStream dos = new DataOutputStream(s.getOutputStream())
byte[] bytes = new byte[4096]
long readCount
long total = 0
long st = System.currentTimeMillis()
while ((readCount = fis.read(bytes)) >= 0) {
total += readCount
dos.write(bytes)
System.out.println(total)
}
System.out.println(total + " - " + (System.currentTimeMillis() - st))
dos.close()
s.close()
fis.close()
ServerSocket ss = new ServerSocket(7001)
while (true) {
Socket s = ss.accept()
DataInputStream dis = new DataInputStream(s.getInputStream())
byte[] bytes = new byte[4096]
int len = 0
while ((len = dis.read(bytes, 0, len)) != -1) {
}
}
NIO
SocketChannel sc = SocketChannel.open()
sc.connect(new InetSocketAddress("localhost", 7002))
String fileName = "/Users/mzx/Desktop/java/WechatIMG1022.jpg"
FileChannel fc = new FileInputStream(fileName).getChannel()
long st = System.currentTimeMillis()
//win下一次只能发送8m,linux无限制,mac无限制
long len = fc.transferTo(0, fc.size(), sc)
fc.close()
sc.close()
System.out.println(len + " - " + (System.currentTimeMillis() - st))
ServerSocketChannel ssc = ServerSocketChannel.open()
ssc.bind(new InetSocketAddress(7002))
ByteBuffer bb = ByteBuffer.allocate(4096)
while (true) {
SocketChannel sc = ssc.accept()
int len = 0
while ((len = sc.read(bb)) != -1) {
bb.rewind()
}
}