零拷贝的“零”是指用户态和内核态间copy数据的次数为零。
传统的数据copy(文件到文件、client到server等)涉及到四次用户态内核态切换、四次copy。四次copy中,两次在用户态和内核态间copy需要CPU参与、两次在内核态与IO设备间copy为DMA方式不需要CPU参与。零拷贝避免了用户态和内核态间的copy、减少了两次用户态内核态间的切换。
零拷贝可以提高数据传输效率,但对于需要在用户传输过程中对数据进行加工的场景(如加密)就不适合使用零拷贝。
使用Zero Copy前后对比:
①零拷贝前
②零拷贝后
1. 通过网络把一个文件从client传到server
/**
* disk-nic零拷贝
*/
class ZerocopyServer {
ServerSocketChannel listener = null;
protected void mySetup() {
InetSocketAddress listenAddr = new InetSocketAddress(9026);
try {
listener = ServerSocketChannel.open();
ServerSocket ss = listener.socket();
ss.setReuseAddress(true);
ss.bind(listenAddr);
System.out.println("监听的端口:" + listenAddr.toString());
} catch (IOException e) {
System.out.println("端口绑定失败 : " + listenAddr.toString() + " 端口可能已经被使用,出错原因: " + e.getMessage());
e.printStackTrace();
}
}
public static void main(String[] args) {
ZerocopyServer dns = new ZerocopyServer();
dns.mySetup();
dns.readData();
}
private void readData() {
ByteBuffer dst = ByteBuffer.allocate(4096);
try {
while (true) {
SocketChannel conn = listener.accept();
System.out.println("创建的连接: " + conn);
conn.configureBlocking(true);
int nread = 0;
while (nread != -1) {
try {
nread = conn.read(dst);
} catch (IOException e) {
e.printStackTrace();
nread = -1;
}
dst.rewind();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ZerocopyClient {
public static void main(String[] args) throws IOException {
ZerocopyClient sfc = new ZerocopyClient();
sfc.testSendfile();
}
public void testSendfile() throws IOException {
String host = "localhost";
int port = 9026;
SocketAddress sad = new InetSocketAddress(host, port);
SocketChannel sc = SocketChannel.open();
sc.connect(sad);
sc.configureBlocking(true);
String fname = "src/main/java/zerocopy/test.data";
FileChannel fc = new FileInputStream(fname).getChannel();
long start = System.nanoTime();
long nsent = 0, curnset = 0;
curnset = fc.transferTo(0, fc.size(), sc);
System.out.println("发送的总字节数:" + curnset + " 耗时(ns):" + (System.nanoTime() - start));
try {
sc.close();
fc.close();
} catch (IOException e) {
System.out.println(e);
}
}
}
2. 文件到文件的零拷贝
/**
* disk-disk零拷贝
*/
class ZerocopyFile {
@SuppressWarnings("resource")
public static void transferToDemo(String from, String to) throws IOException {
FileChannel fromChannel = new RandomAccessFile(from, "rw").getChannel();
FileChannel toChannel = new RandomAccessFile(to, "rw").getChannel();
long position = 0;
long count = fromChannel.size();
fromChannel.transferTo(position, count, toChannel);
fromChannel.close();
toChannel.close();
}
@SuppressWarnings("resource")
public static void transferFromDemo(String from, String to) throws IOException {
FileChannel fromChannel = new FileInputStream(from).getChannel();
FileChannel toChannel = new FileOutputStream(to).getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(fromChannel, position, count);
fromChannel.close();
toChannel.close();
}
public static void main(String[] args) throws IOException {
String from = "src/main/java/zerocopy/1.data";
String to = "src/main/java/zerocopy/2.data";
// transferToDemo(from,to);
transferFromDemo(from, to);
}
}