一、简介
使用Netty进行文件传输主要涉及到FileChannel文件通道,它用来连接文件,可以通过这个通道读写文件。在使用FileChannel之前必须先打开它,FileChannel无法直接打开,可以通过InputStream、OutputStream或RandomAccessFile来获取FileChannel实例,比如:
RandomAccessFile file=new RandomAccessFile("D:\test.txt","rw");
FileChannel channel=file.getChannel();
如果需要从FileChannel中读取数据,要申请一个ByteBuffer,将数据从FileChannel读取到缓冲区中。read()方法返回的int值表示有多少个字节被读取到了缓冲区中,如果返回-1,表示读取到了文件末尾。
如果需要通过FileChannel向文件中写入数据,需要将数据复制或直接写入到ByteBuffer中,然后调用FileChannel.write()方法进行写操作,比如:
String content="test filechannel";
ByteBuffer writeBuffer=ByteBuffer.allocate(1024);
writeBuffer.put(content.getBytes());
writeBuffer.flip();
channel.write(buf);
使用完FileChannel之后需要通过close()方法关闭文件句柄,否则可能出现句柄泄漏。
可以通过FileChannel的position(long pos)方法设置文件的位置指针,通过这种方式可以实现文件的随机读写。
二、Netty文件传输服务端
实现步骤如下:
1)Netty文件服务器启动,绑定8888作为内部监听端口;
2)在命令提示符窗口,通过telnet和文件服务器建立TCP连接;
3)在控制台输入需要下载的文件的绝对路径;
4)文件服务器接收到请求后进行合法性判断,如果文件存在,就将文件发送给控制台;
5)控制台打印文件名和文件内容。
下面是服务端实现:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.CharsetUtil;
public class FileServer
{
public void run (int port)throws Exception{
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workerGroup=new NioEventLoopGroup();
try
{
ServerBootstrap b=new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>()
{
@Override
protected void initChannel(SocketChannel ch)
throws Exception
{
ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8),
//按照回车换行符对数据包进行解码
new LineBasedFrameDecoder(1024),
new StringDecoder(CharsetUtil.UTF_8),
new FileServerHandler());
}
});
ChannelFuture f=b.bind(port).sync();
System.out.println("Start file server at port : "+port);
f.channel().closeFuture().sync();
}
catch (Exception e)
{
e.printStackTrace();
}
finally{
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args)throws Exception
{
int port =8888;
try
{
if (args!=null&&args.length>0)
{
port=Integer.valueOf(args[0]);
}
}
catch (Exception e)
{
e.printStackTrace();
}
new FileServer().run(port);
}
}
下面是网络IO事件的处理:
import java.io.File;
import java.io.RandomAccessFile;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.channel.FileRegion;
import io.netty.channel.SimpleChannelInboundHandler;
public class FileServerHandler extends SimpleChannelInboundHandler<String>
{
//操作系统识别的换行符
private static final String CR=System.getProperty("line.separator");
@Override
protected void messageReceived(ChannelHandlerContext ctx, String msg)
throws Exception
{
File file=new File(msg);
if (file.exists())
{
if (!file.isFile())
{
//写入换行符表示文件结束
ctx.writeAndFlush("Not a file: "+file+CR);
return;
}
//换行符表示文件结尾
ctx.write(file+" "+file.length()+CR);
RandomAccessFile randomAccessFile=new RandomAccessFile(msg, "r");
FileRegion region=new DefaultFileRegion(
randomAccessFile.getChannel(), 0, randomAccessFile.length());
ctx.write(region);
//写入换行符表示文件结束
ctx.writeAndFlush(CR);
randomAccessFile.close();
}else {
ctx.writeAndFlush("File not found: "+file+CR);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
cause.printStackTrace();
ctx.close();
}
}
三、测试
启动文件服务器服务端后,打开命令提示符窗口,输入telnet localhost 8888和文件服务器建立TCP连接,然后输入一个文件路径进行验证:
参考书籍《Netty权威指南》