基于netty的文件传输

最近有用到netty来进行文件的传输,建立通道然后进行文件的读写,主要是用到RandomAccessFile这个类,对可以对文件进行指定位置和指定字节大小读写,下面为具体实现思路:

  • 服务端用于发送FileUploadFile Java对象,里面包括文件,文件信息等,使用RandomAccessFile对文件进行读取,每次1024b(1kb),分片段发送,首次连接时就开始发送第一段,然后由客户端返回,接收的字节大小,再从该位置开始读取下一片段,如果剩余片段小于1kb,则发送完,大于就只发送1kb大小。
  • 客户端用于接收FileUploadFile Java对象,解析对象后,使用RandomAccessFile写入文件,每次写入1kb,然后重新确定位置,再返回数据给服务端。
    代码实现
    FileUploadFile 类,存放文件对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FileUploadFile implements Serializable { //Serializable序列化不能漏
    /* 文件 */
    private File file;
    /* 文件名称 */
    private String fileName;
    /* 开始位置 */
    private int startPos;
    /* 文件字节数组 */
    private byte[] body;
    /* 结束位置 */
    private int endPos;

}

服务端连接Server类

package com.demo.netty.server;

import com.demo.LoggerFactory;
import com.demo.netty.file.FileUploadFile;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;

import java.io.File;

public class Server {

    private static final File file = new File("D:\\temp\\res.txt");
    private static final int port = 8888;

    public static void main(String[] args)throws Exception {
        FileUploadFile fileUploadFile = new FileUploadFile();
        fileUploadFile.setFile(file);
        fileUploadFile.setFileName(file.getName());
        new Server().serverStart(port,fileUploadFile );
    }

    public void serverStart(int port, final FileUploadFile fileUploadFile) throws InterruptedException {
        //1 第一个线程组 是用于接收client端的连接
        EventLoopGroup bossgroup = new NioEventLoopGroup();
        //2 第二个线程组 用于实际的业务开发处理操作
        EventLoopGroup workgroup = new NioEventLoopGroup();
        //3 创建一个辅助类Bootstrap,就是对我们的server进行一系列的配置
        ServerBootstrap sb = new ServerBootstrap();
        //4 把两个线程组添加进来
        sb.group(bossgroup,workgroup)
                //指定使用NioServerSocketChannl这种类型的通道
                .channel(NioServerSocketChannel.class)
                //一定要使用childHandler去绑定具体的事件处理器
                .childHandler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        ch.pipeline().addLast(new ObjectEncoder());
                        ch.pipeline().addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.weakCachingConcurrentResolver(null))); // 最大长度
                        ch.pipeline().addLast(new ServerHandler(fileUploadFile));
                    }
                });
        //绑定指定的端口 进行监听
        LoggerFactory.log.info("启动netty Server");
        ChannelFuture f = sb.bind(port).sync();

        f.channel().closeFuture().sync();
        //关闭线程
        bossgroup.shutdownGracefully();
        workgroup.shutdownGracefully();
    }
}

服务端处理ServerHandler类

package com.demo.netty.server;
import com.demo.netty.file.FileUploadFile;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
@Slf4j
public class ServerHandler extends ChannelInboundHandlerAdapter {
    private int total; //总包数量
    private int row;//剩余包数
    private int byteRead; //读取结束位置
    private volatile int start = 0; //开始位置
    private volatile int lastLength = 0;//文件读取长度
    public RandomAccessFile randomAccessFile;
    private FileUploadFile fileUploadFile;

    public ServerHandler(FileUploadFile fileUploadFile) {
        if (fileUploadFile.getFile().exists()){
            if (!fileUploadFile.getFile().isFile()){
                log.warn("this is not a file");
                return;
            }
        }
        this.fileUploadFile = fileUploadFile;
    }

    /*
    * 客户端连接会触发该方法
    * */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress();
        String ip = remoteAddress.getAddress().getHostAddress();
        log.info("有客户端连接,ip地址为:{}",ip);
        log.info("开始发送FileUploadFile 对象");
        try{
            randomAccessFile = new RandomAccessFile(fileUploadFile.getFile(),"r");//文件只读,不存在不创建,抛出异常
            randomAccessFile.seek(fileUploadFile.getStartPos());//开始位置初始为0
            lastLength =1024;
            if(((randomAccessFile.length())%lastLength)!=0){
                total = (int) (randomAccessFile.length()/lastLength+1);
            }else {
                total = (int) (randomAccessFile.length()/lastLength);
            }
            row = total;
            byte[] body = new byte[lastLength];//创建一个字节长度为1024数组
            if ((byteRead = randomAccessFile.read(body))!=-1){
                fileUploadFile.setEndPos(byteRead);//存入结束位置
                fileUploadFile.setBody(body);//存入文件字节
                ctx.writeAndFlush(fileUploadFile);//发送对象
            }
            row--;
            log.info("第一段文件读取完毕!");
            log.info("文件总长度:[{}],剩余长度:[{}],发送长度:[{}]",randomAccessFile.length(),randomAccessFile.length()-byteRead,byteRead);
            log.info("总包数:[{}],剩余包数:[{}]",total,row);

        }catch (Exception e){
            e.printStackTrace();
        }
    }


    /*
    * 接收信息
    * */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception{
            if (msg instanceof Integer){
                start  = (int) msg;
                if (start!=-1){
                    randomAccessFile = new RandomAccessFile(fileUploadFile.getFile(),"r");
                    randomAccessFile.seek(start); //定位文件位置
                    int  a = (int) (randomAccessFile.length()-start);
                    if (a < lastLength){ //剩余长度小于1024一次发完
                        lastLength = a;
                    }
                    byte[] body = new byte[lastLength];
                    if ((byteRead = randomAccessFile.read(body))!=-1 && a>0){
                        fileUploadFile.setBody(body);
                        fileUploadFile.setEndPos(byteRead);
                        ctx.writeAndFlush(fileUploadFile);
                        row--;
                        log.info("文件总长度:[{}],剩余长度:[{}],发送长度:[{}]",randomAccessFile.length(),a,byteRead);
                        log.info("总包数:[{}],剩余包数:[{}]",total,row);
                    }else {
                        log.info("文件已全部发送完毕,即将关闭通道");
                        randomAccessFile.close();
                        ctx.flush();
                        ctx.close();
                    }
                }
            }
    }

    /*
    * 连接异常触发
    * */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

客户端连接类Client

package com.demo.netty.client;

import com.demo.LoggerFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;

/*
* netty 客户端
* */
public class Client {

    private static final String host = "127.0.0.1";

    public static void main(String[] args) throws Exception {
        new Client().clientStart(8888);
    }

    public void clientStart(int port) throws InterruptedException {
        EventLoopGroup workgroup = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(workgroup);
        b.channel(NioSocketChannel.class);
        b.handler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(Channel ch) {
                ch.pipeline().addLast(new ObjectEncoder());
                ch.pipeline().addLast(new ObjectDecoder(ClassResolvers.weakCachingConcurrentResolver(null)));
                ch.pipeline().addLast(new ClientHandler());
            }
        });
        ChannelFuture cf1 = b.connect(host,port).sync();

        LoggerFactory.log.info("Netty Client start");

        cf1.channel().closeFuture().sync();
        workgroup.shutdownGracefully();
    }
}

客户端处理类ClientHandler

package com.demo.netty.client;


import com.demo.netty.file.FileUploadFile;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.RandomAccessFile;
@Slf4j
public class ClientHandler extends ChannelInboundHandlerAdapter {

    private int byteRead;
    private volatile int start = 0; //开始位置
    private static final String file_path = "D:\\Download\\";

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        log.info("已连接到服务器");
    }


    @Override
    public void channelRead(ChannelHandlerContext cxt,Object msg) throws Exception {
       if (msg instanceof FileUploadFile){
            log.info("接收到服务端的FileUploadFile对象");
            FileUploadFile fileUploadFile = (FileUploadFile) msg;
            byte[] body = fileUploadFile.getBody(); //文件内容转换为字节流
            byteRead = fileUploadFile.getEndPos(); //获取文件上次读取的位置
            String file_name = fileUploadFile.getFileName();//获取文件名称
            String path = file_path+file_name; //文件路径
            File file = new File(path);//根据文件路径创建文件
            RandomAccessFile randomAccessFile = new RandomAccessFile(file,"rw");//读写操作,如果文件不存在,则自动创建文件
            randomAccessFile.seek(start); //移动至start位置开始写入文件
            randomAccessFile.write(body);
            start+=byteRead;
            cxt.writeAndFlush(start);//向服务端发送数据,下次开始的位置
            log.info("写入完毕,文件路径为:{}",path);
       }
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

}

先启动Server类的主程序,再启动Client类的主程序,就可以运行了,路径可以自己修改一下,运行截图如下:

Server端

java使用netty结合spring做文件上传 netty 文件传输_ide


client端

java使用netty结合spring做文件上传 netty 文件传输_.net_02

最后上个maven依赖:

<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.12.Final</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.0.8</version>
        </dependency>
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.0.8</version>
        </dependency>