第一章Netty,NIO零拷贝详细实现代码

📅 发布时间:2026/7/3 16:32:36 👁️ 浏览次数:
第一章Netty,NIO零拷贝详细实现代码
基于前文对 ‌NIO 零拷贝原理‌sendfile 与 mmap及‌高并发场景‌的讨论以下是两种核心零拷贝技术的详细 Java 实现代码。先看一段传统IO模型的文件拷贝代码packagecom.example.demo;importjava.io.File;importjava.io.IOException;importjava.io.RandomAccessFile;importjava.net.Socket;publicclassFileNioTest{publicstaticvoidmain(String[]args)throwsIOException{FilefnewFile(netty.txt);RandomAccessFilefilenewRandomAccessFile(f,r);byte[]bufnewbyte[(int)f.length()];intreadfile.read(buf);SocketsocketnewSocket();socket.getOutputStream().write(buf);}}传统IO工作流程一、 基于 transferTo 的文件网络传输推荐这是最纯粹的零拷贝适用于‌静态资源服务器‌或‌大文件上传/下载‌。数据直接在内核态从 Page Cache 传输到 Socket Buffer不经过用户态。1. 服务端代码发送文件importjava.io.FileInputStream;importjava.net.InetSocketAddress;importjava.nio.channels.FileChannel;importjava.nio.channels.ServerSocketChannel;importjava.nio.channels.SocketChannel;publicclassZeroCopyServer{publicstaticvoidmain(String[]args)throwsException{// 1. 创建 ServerSocketChannelServerSocketChannelserverSocketChannelServerSocketChannel.open();serverSocketChannel.bind(newInetSocketAddress(8080));System.out.println(Server started on port 8080...);while(true){// 2. 接受连接SocketChannelsocketChannelserverSocketChannel.accept();// 3. 获取文件通道FileChannelfileChannelnewFileInputStream(large_file.dat).getChannel();// 4. 【核心】零拷贝传输// transferTo 底层调用 sendfile数据不经过用户空间longtransferredfileChannel.transferTo(0,fileChannel.size(),socketChannel);System.out.println(Total bytes transferred: transferred);// 5. 关闭资源fileChannel.close();socketChannel.close();}}}2. 客户端代码接收文件importjava.io.FileOutputStream;importjava.net.InetSocketAddress;importjava.nio.channels.FileChannel;importjava.nio.channels.SocketChannel;publicclassZeroCopyClient{publicstaticvoidmain(String[]args)throwsException{// 1. 连接服务器SocketChannelsocketChannelSocketChannel.open();socketChannel.connect(newInetSocketAddress(localhost,8080));// 2. 获取文件输出通道FileChannelfileChannelnewFileOutputStream(received_file.dat).getChannel();// 3. 读取数据并写入文件// 注意客户端接收通常仍涉及一次从内核到用户空间的拷贝除非使用 DirectBuffer write// 但服务端发送已实现零拷贝整体性能显著提升longbytesReadfileChannel.transferFrom(socketChannel,0,Long.MAX_VALUE);System.out.println(Total bytes received: bytesRead);// 4. 关闭资源fileChannel.close();socketChannel.close();}}二、 基于 MappedByteBuffer 的大文件随机读写适用于‌数据库索引‌、‌日志分析‌等需要频繁随机访问大文件的场景。通过内存映射避免传统 read/write 的系统调用开销。1. 写入大文件示例importjava.io.RandomAccessFile;importjava.nio.MappedByteBuffer;importjava.nio.channels.FileChannel;publicclassMappedFileWriter{publicstaticvoidmain(String[]args)throwsException{StringfilePathmapped_large_file.dat;longfileSize1024*1024*100;// 100MB// 1. 创建 RandomAccessFile 和 FileChannelRandomAccessFilerafnewRandomAccessFile(filePath,rw);FileChannelchannelraf.getChannel();// 2. 【核心】内存映射// MapMode.READ_WRITE: 读写模式// position: 0, size: fileSizeMappedByteBuffermappedBufferchannel.map(FileChannel.MapMode.READ_WRITE,0,fileSize);// 3. 直接操作内存如同操作数组for(inti0;i1024;i){mappedBuffer.putInt(i*4,i);// 在偏移量 i*4 处写入整数 i}// 4. 强制刷新到磁盘可选确保数据持久化mappedBuffer.force();System.out.println(Data written to mapped file.);// 5. 关闭资源channel.close();raf.close();}}2. 读取大文件示例importjava.io.RandomAccessFile;importjava.nio.MappedByteBuffer;importjava.nio.channels.FileChannel;publicclassMappedFileReader{publicstaticvoidmain(String[]args)throwsException{StringfilePathmapped_large_file.dat;// 1. 创建 RandomAccessFile 和 FileChannelRandomAccessFilerafnewRandomAccessFile(filePath,r);FileChannelchannelraf.getChannel();// 2. 内存映射只读模式MappedByteBuffermappedBufferchannel.map(FileChannel.MapMode.READ_ONLY,0,channel.size());// 3. 随机读取数据intvaluemappedBuffer.getInt(0);// 读取偏移量 0 处的整数System.out.println(Value at offset 0: value);intvalueAt1024mappedBuffer.getInt(1024*4);System.out.println(Value at offset 4096: valueAt1024);// 4. 关闭资源channel.close();raf.close();}}三、 关键注意事项‌资源关闭‌FileChannel 和 SocketChannel 必须显式关闭否则会导致文件描述符泄漏。‌内存映射释放‌MappedByteBuffer 占用的堆外内存不会立即被 GC 回收。若需手动释放可借助 sun.misc.Cleaner非官方 API需谨慎使用或通过反射调用 clean() 方法。‌小文件性能‌对于极小文件 1KB零拷贝的系统调用开销可能高于传统 I/O此时传统 read/write 可能更优。‌平台差异‌transferTo 在 Linux 下性能最佳完整零拷贝在 Windows 下可能退化为部分拷贝实现。