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

說明

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

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

從netty的官方文件傳輸?shù)膃xample中參考了server端的實現(xiàn)分唾,但是沒有找到客戶端的例子來運行程序,于是自己寫了個發(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模式會跳過此handler)推汽,ChunkedFile會經(jīng)過ChunkedWriteHandler來一塊一塊發(fā)送文件數(shù)據(jù)。

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

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

最后傳遞給FileServerHandler讀取請求的文件或目錄,返回文件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());
                        }
                    });

請求頭和響應(yī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;
}

消息格式說明:

文件請求的消息格式

REQ_FILE 請求指令的消息格式

----------------------+
| 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 |
-------------+

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

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

client端收到列表后糕韧,根據(jù)列表逐個發(fā)起文件下載請求。

FileRegion模式:

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

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

ChunkedFile模式:

server端首先發(fā)送一條BFileRsp結(jié)構(gòu)的文件信息(包括cmd岗照,文件相對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. 斷點續(xù)傳功能未實現(xiàn)

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

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

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

基本流程

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

  2. 生成一個自己的密鑰吓妆,然后填寫證書認證申請,拿給上面的CA機構(gòu)去簽名

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

Server/Client都用ca.crt來簽名

首先吨铸,虛構(gòu)一個CA認證機構(gòu)出來

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

openssl genrsa -des3 -out ca.key 1024

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

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

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

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

這個證書ca.crt有的又稱為"根證書",因為可以用來認證其他證書

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

用上面那個虛構(gòu)出來的CA機構(gòu)來認證舟奠,不收錢!

server 簽名

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

openssl genrsa -des3 -out server.key 1024

生成證書的請求文件

如果找外面的CA機構(gòu)認證沼瘫,也是發(fā)個請求文件給他們

這個私鑰就包含在請求文件中了,認證機構(gòu)要用它來生成公鑰咙俩,然后包裝成一個證書

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

使用虛擬的CA認證機構(gòu)的證書ca.crt耿戚,來對證書請求文件server.csr進行處理,生成簽名后的證書server.crt

注意設(shè)置序列號和有效期(設(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

生成證書的請求文件

如果找外面的CA機構(gòu)認證膜蛔,也是發(fā)個請求文件給他們

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

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

使用虛擬的CA認證機構(gòu)的證書ca.crt皂股,來對證書請求文件client.csr進行處理,生成簽名后的證書client.crt

注意設(shè)置序列號和有效期(設(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)容合作請聯(lián)系作者
  • 序言:七十年代末命黔,一起剝皮案震驚了整個濱河市呜呐,隨后出現(xiàn)的幾起案子就斤,更是在濱河造成了極大的恐慌,老刑警劉巖卵史,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件战转,死亡現(xiàn)場離奇詭異搜立,居然都是意外死亡以躯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門啄踊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忧设,“玉大人,你說我怎么就攤上這事颠通≈吩危” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵顿锰,是天一觀的道長谨垃。 經(jīng)常有香客問我,道長硼控,這世上最難降的妖魔是什么刘陶? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮牢撼,結(jié)果婚禮上匙隔,老公的妹妹穿的比我還像新娘。我一直安慰自己熏版,他們只是感情好纷责,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撼短,像睡著了一般再膳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上曲横,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天喂柒,我揣著相機與錄音,去河邊找鬼胜榔。 笑死胳喷,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的夭织。 我是一名探鬼主播吭露,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼尊惰!你這毒婦竟也來了讲竿?” 一聲冷哼從身側(cè)響起泥兰,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎题禀,沒想到半個月后鞋诗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡迈嘹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年削彬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秀仲。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡融痛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出神僵,到底是詐尸還是另有隱情雁刷,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布保礼,位于F島的核電站沛励,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏炮障。R本人自食惡果不足惜目派,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铝阐。 院中可真熱鬧址貌,春花似錦、人聲如沸徘键。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吹害。三九已至螟凭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間它呀,已是汗流浹背螺男。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纵穿,地道東北人下隧。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像谓媒,于是被迫代替她去往敵國和親淆院。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容