概念介绍
文件是最常见的数据源之一,程序经常需要在文件中读取数据,也要将数据保存在文件中,进行持久化。
文件是计算机中一种基本的数据存储形式。即使计算机关机,文件的数据还是存在的,但是内存的数据就会丢失。
相对路径
从当前路径开始: linux 中: …/root/Demo.java。 当前目录的上一级目录中的 root文件夹的Demo.java文件。
绝对路径
从根节点开始,例如:windows 中的 D:\java\nio\netty\Demo.java
linux 中 /root/temp/Demo.java
开发
业务流程图如下:
FileChannel 介绍
Java NIO 中的FileChannel是一个连接到文件的通道,可以通过这个文件通道读写文件。JDK1.7 之前NIO 的FileChannel是同步阻塞的。JDK1.7对NIO进行了升级,升级后的NIO提供了异步文件通道AsynchronousFileChannel。它支持异步非阻塞文件操作(AIO)。
在使用FileChannel之前必须先打开它,需要有InputStream,OutputSream或者RandomAccessFile来构造FileChannel实例。
RandomAccessFile tempFile=new RandomAccessFile("/home/temp/xxx.java");
FileChannel channel=tempFile.getChannle();
如果要从FileChannel中读取数据,要申请一个ByteBuffer,将数据从FileChannel中读取到字节缓冲中。read()方法返回的Int值表示有多少字节被读到了字节缓冲区中。如果返回-1,表示读到了文件末尾。
反之,如果需要通过FileChannel向文件中写入数据,需要将数据复制或者直接存放到ByteBuffer中,然后调用FileChannel.writer()方法进行写操作。
String content="echo,welcome to File world.";
ByteBuffer wrieteBuffer=ByteBuffer.allocate(128);//缓冲字节分配128 字节大小
//内容放入缓冲字节中
writeBuffer.put(content.getBytes());
writeBuffer.flip();
//将字节内容写入FileChannel 对应的文件中去。
channel.write(buf);
jar 依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId> <!-- Use 'netty5-all' for 5.0-->
<version>5.0.0.Alpha1</version>
<scope>compile</scope>
</dependency>
服务端启动类 FileServer
public class FileServer {
public void run(int port){
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workGroup=new NioEventLoopGroup();
try {
ServerBootstrap bootstrap=new ServerBootstrap();
bootstrap.group(bossGroup,workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,100)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(
//编码 编码器从下至上。 顺序是 FileServerHandler >>>>StringEncoder
//解码 是解码器从上到下。 每一个的返回值是下一个的入参。
// LineBasedFrameDecoder >> StringDecoder >> FileServerHandler
//到 FileServerHandler 拿到的就是String了。
//将文件内容编码为字符串
new StringEncoder(CharsetUtil.UTF_8),
//按照回车换行符对数据报报进行解码
new LineBasedFrameDecoder(1024),
//将数据报解码为字符串
new StringDecoder(CharsetUtil.UTF_8),
//业务处理类
new FileServerHandler());
}
});
ChannelFuture future=bootstrap.bind(port).sync();
System.out.println("Start file server at port : "+port);
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new FileServer().run(8080);
}
}
服务端业务处理类 FileServerHandler
public class FileServerHandler extends SimpleChannelInboundHandler<String> {
private static final String CR=System.getProperty("line.separator");
@Override
protected void messageReceived(ChannelHandlerContext context, String s) throws Exception {
//通过路径来构造文件
File file=new File(s);
//如果文件存在
if (file.exists()){
//如果不是文件,是文件夹
if(!file.isFile()){
context.writeAndFlush("Not a file : "+file+CR);
return;
}
context.write(file+" "+file.length()+CR);
//构造只读文件
RandomAccessFile randomAccessFile=new RandomAccessFile(s,"r");
//构造netty 的FileRegion对象
FileRegion region=new DefaultFileRegion(
//FileChannel 文件通道,用于对文件进行读写操作
randomAccessFile.getChannel(),
//0(Position)文件操作的指针位置,读取或写入的起始点。
0,
//操作的总字节数
randomAccessFile.length());
//实现对文件的发送。由于Netty 底层对文件写入进行了封装,我们不用关心发送的细节。
context.write(region);
//写入分割符告诉CMD 控制台,文件传输结束
context.writeAndFlush(CR);
randomAccessFile.close();
}else {
//文件不存在
context.writeAndFlush("File not found : "+file+CR);
}
}
public void exceptionCaught(ChannelHandlerContext context,Throwable cause){
cause.printStackTrace();
context.close();
}
}
测试
测试步骤
(1)启动服务端
(2)打开cmd 窗口。输入 telnet 127.0.0.1 8080 (注意ip和端口中间有空格)
(3)连接上了后,复制粘贴文件的绝对路径。 这个输入操作比较麻烦,会有空格等字符串,导致服务器接收的文件路径不正确。所以测试失败了,看下服务器那边接收的路径是否正确。
CMD客户端截图打印
输入的绝对路径是: F:\dubbo_leaning\DoSpring\src\com\echo\service\echo.txt
文件内容截图如下:
测试结果说明
通过 这个结果看出。客户端打印了文件名称,说明服务端接收数据正确。
客户端接收了文件内容,说明服务器那边能正确获取文件,并能正确发送。
好了。我们的功能正确的实现 了。没有出现丢包和粘包现象。
如果 CMD客户端输入绝对路径很麻烦。大家可以参考用html 来模拟连接和输入绝对路径。
可以参考这篇博客 https://blog.csdn.net/echohuangshihuxue/article/details/128670385。
小伙伴们可以debugger 跟下服务端业务处理类逻辑。能更好的理解本章内容。
总结
本章节介绍了如何利用Netty进行文件传输。由于Netty对文件传输进行了封装,上层应用不需要感知文件操作的细节,Netty提供了多种编码类库,通过组合可以灵活地处理各种文件。
其实 Netty有多种方式实现文件的传输,本章只是实现了比较通用的方式。
比如 Netty 还提供了ChunkedWriteHandler 来解决大文件或者码流传输过程中可能发生的内存溢出问题。总的来说,Netty 的文件传输无论在功能还是可靠性方面,相比较于传统的I/O类库或者其他一些第三方文件传输类库,都有较大的优势。