Java大文件傳輸(Netty應(yīng)用一)

說明

基于Netty的FileRegion模式和ChunkedFile模式實(shí)現(xiàn)的大文件傳輸demo,其中ChunkedFile使用了SSL。

由于最近想在兩臺(tái)不同操作系統(tǒng)的電腦之間傳輸較大的(3G左右)單個(gè)大文件的需要,于是用netty自己寫個(gè)文件傳輸?shù)耐暾鹍emo。(當(dāng)然可以通過U盤或移動(dòng)硬盤可以輕松實(shí)現(xiàn)這個(gè)需求)

從netty的官方文件傳輸?shù)膃xample中參考了server端的實(shí)現(xiàn),但是沒有找到客戶端的例子來運(yùn)行程序,于是自己寫了個(gè)發(fā)到gitee上(https://gitee.com/bbstone101/pisces.git)。

Netty源碼中的文件傳輸example的路徑:/netty-4.1.48.Final/example/src/main/java/io/netty/example/file

Bootstrap編碼解碼過程說明

Server Bootstrap使用的channel handler說明:

    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
                        public void initChannel(SocketChannel ch) throws Exception {

                            ChannelPipeline p = ch.pipeline();
                            if (sslCtx != null) {
                                p.addLast(sslCtx.newHandler(ch.alloc()));
                            }
                            // outbound (default ByteBuf)
                            // no encoder, direct send ByteBuf
                            // if os not support zero-copy, used ChunkedWriteHandler
                            p.addLast(new ChunkedWriteHandler());

                            // inbound(decode by the delimiter, then forward to protobuf decoder, last forward to handler)
                            ByteBuf delimiter = Unpooled.copiedBuffer(ConstUtil.delimiter.getBytes(CharsetUtil.UTF_8));
                            p.addLast(new DelimiterBasedFrameDecoder(8192, delimiter)); // frameLen = BFileReq bytes
                            p.addLast(new ProtobufDecoder(BFileMsg.BFileReq.getDefaultInstance()));
                            p.addLast(new FileServerHandler());
                        }
                    });

Server端outbound(發(fā)送出去)使用了ChunkedWriteHandler,在chunkedFile 模式下用到(FileRegion模式會(huì)跳過此handler),ChunkedFile會(huì)經(jīng)過ChunkedWriteHandler來一塊一塊發(fā)送文件數(shù)據(jù)。

Inbound(接收傳入)的數(shù)據(jù)流經(jīng)過自定義的delimiter解碼,

然后再經(jīng)過protobuf解碼后,

最后傳遞給FileServerHandler讀取請(qǐng)求的文件或目錄,返回文件BFileInfo列表給客戶端。

Client Bootstrap使用的channel handler說明:

    Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {

                            ChannelPipeline p = ch.pipeline();
                            if (sslCtx != null) {
                                p.addLast(sslCtx.newHandler(ch.alloc()));
                            }
                            // outbound(BFileReq)
                            p.addLast(new ProtobufEncoder());

                            // --- inbound
                            // if os not support zero-copy/sslEnabled, used this, must be the first inbound handler
                            p.addLast(new ChunkedReadHandler());

                            // ----- decode and handle (BFileRsp + FileRegion) data stream
                            ByteBuf delimiter = Unpooled.copiedBuffer(ConstUtil.delimiter.getBytes(CharsetUtil.UTF_8));
                            // inbound frameLen = chunkSize[default: 8192] + BFileRsp header)
//                            p.addLast(new DelimiterBasedFrameDecoder(10240, delimiter));
                            p.addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, delimiter));
                            p.addLast(new FileClientHandler());
                        }
                    });

請(qǐng)求頭和響應(yīng)頭

請(qǐng)求頭消息格式-protobuf(由client端編碼,server端解碼)

message BFileReq{
    string id = 1;
    string cmd = 2;
    string filepath = 3;
    
    uint64 ts = 4;
}

響應(yīng)頭消息格式-protobuf(由server端編碼,client端解碼)

message BFileRsp{
    string id = 1;
    string cmd = 2;
    string filepath = 3;

    uint64 fileSize = 4;
    string checksum = 5;
    string rspData = 6;
    bytes chunkData = 7;

    uint64 reqTs = 8;
    uint64 rspTs = 9;
}

消息格式說明:

文件請(qǐng)求的消息格式

REQ_FILE 請(qǐng)求指令的消息格式

----------------------+
| BFileReq| delimiter |
----------------------+

文件響應(yīng)的消息格式(FileRegion模式)

RSP_FILE 響應(yīng)指令的消息格式

------------------------------------+
| BFileRsp | chunk_data | delimiter |
------------------------------------+

文件響應(yīng)的消息格式(ChunkedFile模式)

第一條是文件信息的消息,有界定符(解決粘包和拆包問題)

----------------------+
| BFileRsp| delimiter |
----------------------+

第二條是ChunkedFile經(jīng)過ChunkedWriteHandler按照一塊塊發(fā)送的數(shù)據(jù),沒有界定符。

-------------+
| chunk_data |
-------------+

文件傳輸請(qǐng)求-響應(yīng) 過程說明

由client端觸發(fā)操作,client連上server后,發(fā)起查詢文件列表請(qǐng)求,server將指定的目錄下的所有文件BFileInfo列表返回給client端。

client端收到列表后,根據(jù)列表逐個(gè)發(fā)起文件下載請(qǐng)求。

FileRegion模式:

server端通過FileRegion將文件切割成8192 Byte大小的chunk,逐個(gè)寫到channel中。每個(gè)chunk都附加上BFileRsp的響應(yīng)頭信息。(詳細(xì)格式見 設(shè)計(jì)說明 RSP_FILE 指令碼格式 章節(jié))

client端收到消息后,進(jìn)行解碼,先解出BFileRsp的頭信息,然后根據(jù)頭信息的指令碼(cmd),選擇對(duì)應(yīng)的CmdHandler來處理消息。為了減少頻繁寫磁盤,client收到chunk后,先緩存起來,直到緩存滿4M或文件數(shù)據(jù)接收完畢后才寫一次文件數(shù)據(jù)到磁盤。

ChunkedFile模式:

server端首先發(fā)送一條BFileRsp結(jié)構(gòu)的文件信息(包括cmd,文件相對(duì)server.dir的路徑,checksum等信息)。然后接著發(fā)送ChunkedFile。

client端收到響應(yīng)后,首先解析第一條BFileRsp的消息,解析后,如果是RSP_FILE命令,就給ClientCache.recvFileKey賦值,并保存接收到的BFileRsp信息。第二條消息開始就是chunked file的純文件數(shù)據(jù)(具體發(fā)送多少字節(jié)數(shù)據(jù)由ChunkedWriteHandler決定)。文件接收完成后,重置recvFileKey為null,刪除第一條消息保存的BFileRsp的文件信息。

已知問題

  1. 斷點(diǎn)續(xù)傳功能未實(shí)現(xiàn)

  2. client端接收文件的目錄如果已經(jīng)有一樣的文件,會(huì)直接覆蓋,不會(huì)跳過。

  3. server端下載文件的目錄和client端接收文件的目錄只能通過config.propertis預(yù)先配置好,還不支持通過命令交互方式輸入源文件路徑和保存的目標(biāo)路徑。

附錄:SSL中使用的數(shù)字證書創(chuàng)建過程

基本流程

  1. 搞一個(gè)虛擬的CA機(jī)構(gòu),生成一個(gè)證書

  2. 生成一個(gè)自己的密鑰,然后填寫證書認(rèn)證申請(qǐng),拿給上面的CA機(jī)構(gòu)去簽名

  3. 于是就得到了自(自建CA機(jī)構(gòu)認(rèn)證的)簽名證書

Server/Client都用ca.crt來簽名

首先,虛構(gòu)一個(gè)CA認(rèn)證機(jī)構(gòu)出來

生成CA認(rèn)證機(jī)構(gòu)的證書密鑰key# 需要設(shè)置密碼,輸入兩次(123456)

openssl genrsa -des3 -out ca.key 1024

去除密鑰里的密碼(可選)# 這里需要再輸入一次原來設(shè)的密碼

openssl rsa -in ca.key -out ca.key

用私鑰ca.key生成CA認(rèn)證機(jī)構(gòu)的證書ca.crt# 其實(shí)就是相當(dāng)于用私鑰生成公鑰,再把公鑰包裝成證書

openssl req -new -x509 -key ca.key -out ca.crt -days 3650

這個(gè)證書ca.crt有的又稱為"根證書",因?yàn)榭梢杂脕碚J(rèn)證其他證書

其次,才是生成網(wǎng)站的證書

用上面那個(gè)虛構(gòu)出來的CA機(jī)構(gòu)來認(rèn)證,不收錢!

server 簽名

生成密鑰server.key,輸入秘密:123456

openssl genrsa -des3 -out server.key 1024

生成證書的請(qǐng)求文件

如果找外面的CA機(jī)構(gòu)認(rèn)證,也是發(fā)個(gè)請(qǐng)求文件給他們

這個(gè)私鑰就包含在請(qǐng)求文件中了,認(rèn)證機(jī)構(gòu)要用它來生成公鑰,然后包裝成一個(gè)證書

openssl req -new -key server.key -out server.csr

使用虛擬的CA認(rèn)證機(jī)構(gòu)的證書ca.crt,來對(duì)證書請(qǐng)求文件server.csr進(jìn)行處理,生成簽名后的證書server.crt

注意設(shè)置序列號(hào)和有效期(設(shè)10年)

openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt -days 3650

將server.key RSA private key 轉(zhuǎn)換成pkcs8 的private key

openssl pkcs8 -topk8 -in server.key -out pkcs8_server.key -nocrypt

Client簽名

生成密鑰client.key,輸入秘密:123456

openssl genrsa -des3 -out client.key 1024

生成證書的請(qǐng)求文件

如果找外面的CA機(jī)構(gòu)認(rèn)證,也是發(fā)個(gè)請(qǐng)求文件給他們

這個(gè)私鑰就包含在請(qǐng)求文件中了,認(rèn)證機(jī)構(gòu)要用它來生成網(wǎng)站的公鑰,然后包裝成一個(gè)證書

openssl req -new -key client.key -out client.csr

使用虛擬的CA認(rèn)證機(jī)構(gòu)的證書ca.crt,來對(duì)證書請(qǐng)求文件client.csr進(jìn)行處理,生成簽名后的證書client.crt

注意設(shè)置序列號(hào)和有效期(設(shè)10年)

openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt -days 3650

將server.key RSA private key 轉(zhuǎn)換成pkcs8 的private key

openssl pkcs8 -topk8 -in client.key -out pkcs8_client.key -nocrypt

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容