Java基础知识

java.nio中的Channel系列(2)-FileChann

2019-04-14  本文已影响0人  奔跑地蜗牛

简介

本文主要是用来记录Channel接口相关实现类的功能和特性

FileChannel

FileChannel主要是从文件中中读写数据的Channel,其实现的接口和继承的对象如下:

public abstract class FileChannel extends AbstractInterruptibleChannel
    implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel{
....
}

FileChannel是连接到一个文件的通道,对于它所连接的文件,会维护一个position用来指向文件内容的绝对位置,该绝对位置可以通过position()查询和position(long)进行修改,如果该position修改后,那么输出文件内容到指定ByteBuffer时,将从该position处开始;另外read(ByteBuffer,position)会从指定position开始读取文件内容到ByteBuffer,但并不会修改通道本身position的位置;

URL path = FileChannelTest.class.getClassLoader().getResource("text.txt");
RandomAccessFile accessfile = new RandomAccessFile(new java.io.File(path.getFile()), "r");
        FileChannel fileChannel=accessfile.getChannel();
        fileChannel.position(5);
        ByteBuffer byteBuffer=ByteBuffer.allocate(10);
        fileChannel.read(byteBuffer);
        byteBuffer.flip();
        System.out.println(new String(byteBuffer.array()));

FileChannel具有以下特性:

    RandomAccessFile accessfile = new RandomAccessFile(
                new java.io.File("C:\\Users\\Administrator\\git\\javabase\\JavaBase\\resources\\text.txt"), "rw");
        FileChannel fileChannel = accessfile.getChannel();
        MappedByteBuffer map = fileChannel.map(MapMode.READ_WRITE, 0, fileChannel.size());
        
        Charset charset=Charset.forName("utf-8");
        CharBuffer decode = charset.decode(map.asReadOnlyBuffer());
        System.out.println(decode.toString());//读取测试
        byte[] chars = "hao hi yo".getBytes();

        map.put(chars,0,chars.length);//写入测试,写入位置和position有关
        map.force();
        fileChannel.close();

Linux零拷贝

普通传输文件

在linux系统中,用户程序要访问某个文件,传输到网络,可以通过如下代码进行访问

while((n = read(diskfd, buf, BUF_SIZE)) > 0)
    write(sockfd, buf , n);

代码虽然简单,但是IO实际上的操作,会经过如下过程:

具体如下图:


文件网络传输过程.png

在这种场景下,一次文件传输一般需要两次cpu copy,两次DMA copy,同时也发生了多次用户态和内核态之间的上下文切换,这无疑加大了cpu的负担;

零拷贝技术

什么是零拷贝技术?在上面场景中,磁盘文件传输到网络端口,需要经过多次cpu copy,加大了cpu的负担,而零拷贝就是指为了避免CPU做大量的拷贝和减少不必要的拷贝而采用的一些技术,这些技术包括采用其他组件来进行简单的文件网络传输;

内核缓存区主要是缓存本地读写文件并与用户程序交换数据的缓存区,而socket缓存区则是用来发送到网络或者从网络读取的文件数据;

mmap

mmap函数可以将用户空间的一块内存地址和内核中的一块内存地址同时映射到真正的物理内存上,从而这块物理内存对于内核和用户空间都是可见的,需要注意的是映射的文件大小最好是内核缓存页大小(PAGE_SIZE)的整数倍,如果不是则会进行强制内存对齐,最后一页没被使用的空间会被填充零;
mmap系统调用代码如下:

buf = mmap(diskfd, len);
write(sockfd, buf, len);

其主要作用如下:

mmap隐藏问题

使用mmap了一个文件,那么当write这个文件过程中,如果存在另一个进程对该文件进行truncate操作(truncate操作可以改变文件大小),那么write系统调用会因为访问非法地址而被SIGBUS终止,这样SIGBUS会杀掉你的进程,同时留下一个coredump文件(coredump文件用来存储进程崩溃时的内存快照,可以用来定位问题);
针对于这类问题的处理方法,就是避免write在文件被truncate后继续访问,具体方法如下:

if(fcntl(diskfd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
    perror("kernel lease set signal");
    return -1;
}
/* l_type can be F_RDLCK F_WRLCK  加锁*/
/* l_type can be  F_UNLCK 解锁*/
if(fcntl(diskfd, F_SETLEASE, l_type)){
    perror("kernel lease set type");
    return -1;
}

sendFile

关于sendFile,可以先看下sendFile方法:

#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

其中in_fd必须是mmap文件,out_fd必须是套接字,这样就可以通过sendFile直接将内核缓存区的文件拷贝到网络缓存区,减少了用户态和内核态的上下文切换,和减少了文件拷贝次数,同时数据拷贝只发生在内核层,如下图所示:


sendFile.png

另外sendfile即使不做任何信号程序处理,如果调用sendFile时其他进程truncate文件,sendFile会被中断调用,返回中断前读取的字节,将errno设置为success,但是不会因为读取非法地址而中断进程;如果给文件使用租借锁,情况没有变化,但是会返回一个RT_SIGNAL_LEASE信号;

sendFile改进

在上面的场景中,文件数据从内核缓存区到socket缓存区同样会经历一次拷贝,那么有没有办法减少这次拷贝呢?借助硬件是可以实现的,我们可以使用sendFile将页缓存区的关于文件缓存的描述符如位置,大小添加到socket端口,这一步不会复制文件缓存,这样DMA控制引擎可以根据文件描述符直接将内核页缓存区的文件拿到协议引擎中,避免最后一次拷贝,如下图:


sendFile2.png
splice

sendFile可以将数据拷贝到一个套接字上面,这就限制了它的一些适用范围;linux可以通过splice的方法将文件数据在两个文件描述符中进行移动,其方法如下:

#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <fcntl.h>
ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);

splice系统调用会在两个文件描述符中进行文件移动,但是其中一个一方必须是管道设备;
flags参数有以下取值:

扩展:linux写时复制
linux为了减少数据文件在内核和用户缓存区进行复制,采用的一种机制,其主要原理是:多个进程访问同一个文件时,那么该文件被拷贝到内核缓存区,对所有进程都是可见的,但是不是所有线程都需要去修改该文件,所以针对这一现象,linux采用了只用当进程需要修改该文件时,才将该文件复制到用户空间,这就是写时复制;

FileChannel与零拷贝

FileChannel中的map()方法其实就是利用mmap()系统调用,而transferTo()、transferFrom()的实现也是根据情况采用了相应的零拷贝技术;

上一篇下一篇

猜你喜欢

热点阅读