SOCKET編程關(guān)于stream-based的一些思考

附上netty 5 用戶指南地址http://ifeve.com/netty5-user-guide/

流數(shù)據(jù)的傳輸處理

一個(gè)小的Socket Buffer問(wèn)題

在基于流的傳輸里比如TCP/IP,接收到的數(shù)據(jù)會(huì)先被存儲(chǔ)到一個(gè)socket接收緩沖里。不幸的是,基于流的傳輸并不是一個(gè)數(shù)據(jù)包隊(duì)列,而是一個(gè)字節(jié)隊(duì)列。即使你發(fā)送了2個(gè)獨(dú)立的數(shù)據(jù)包,操作系統(tǒng)也不會(huì)作為2個(gè)消息處理而僅僅是作為一連串的字節(jié)而言。因此這是不能保證你遠(yuǎn)程寫入的數(shù)據(jù)就會(huì)準(zhǔn)確地讀取。舉個(gè)例子,讓我們假設(shè)操作系統(tǒng)的TCP/TP協(xié)議棧已經(jīng)接收了3個(gè)數(shù)據(jù)包:
netty5_1.png

由于基于流傳輸?shù)膮f(xié)議的這種普通的性質(zhì),在你的應(yīng)用程序里讀取數(shù)據(jù)的時(shí)候會(huì)有很高的可能性被分成下面的片段。

netty5_2.png

因此,一個(gè)接收方不管他是客戶端還是服務(wù)端,都應(yīng)該把接收到的數(shù)據(jù)整理成一個(gè)或者多個(gè)更有意思并且能夠讓程序的業(yè)務(wù)邏輯更好理解的數(shù)據(jù)。在上面的例子中,接收到的數(shù)據(jù)應(yīng)該被構(gòu)造成下面的格式:

netty5_3.png

問(wèn)題總結(jié)一下就是客戶端連續(xù)發(fā)了三個(gè)包,服務(wù)端在收到的時(shí)候可能是不同片段大小收到的,如果在每一次收到一片時(shí)便處理數(shù)據(jù)的話,很有可能數(shù)據(jù)是不完整的。(我的猜測(cè)是,TCP底層的窗口機(jī)制引起的,窗口的挪動(dòng)會(huì)讓已經(jīng)完成的數(shù)據(jù)向上傳送,而沒(méi)有收到還會(huì)等待。這也是保證順序不錯(cuò)亂的機(jī)制,所以我在一開(kāi)始考慮會(huì)不會(huì)有順序錯(cuò)亂問(wèn)題,這個(gè)應(yīng)該是在底層已經(jīng)解決掉了)

Netty提供的解決方案總結(jié)

第一種:緩存收到的字節(jié),當(dāng)緩存的字節(jié)數(shù)目到達(dá)要求時(shí),讀取buff完成業(yè)務(wù)邏輯

(1)在handler生命周期手動(dòng)緩存
package io.netty.example.time;

import java.util.Date;

public class TimeClientHandler extends ChannelHandlerAdapter {
    private ByteBuf buf;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        buf = ctx.alloc().buffer(4); // (1)在生命周期中,聲明一個(gè)4字節(jié)的緩存大小
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        buf.release(); // (1)在生命周期結(jié)束的時(shí)候釋放掉
        buf = null;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf m = (ByteBuf) msg;
        buf.writeBytes(m); // (2)將每次收到的字節(jié)流寫進(jìn)緩存里
        m.release();

        if (buf.readableBytes() >= 4) { // (3)當(dāng)緩存大小達(dá)到4字節(jié)時(shí)處理實(shí)際業(yè)務(wù)
            long currentTimeMillis = (buf.readInt() - 2208988800L) * 1000L;
            System.out.println(new Date(currentTimeMillis));
            ctx.close();
        }
    }

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

(2)利用netty的解碼函數(shù)來(lái)控制字節(jié)

package io.netty.example.time;

public class TimeDecoder extends ByteToMessageDecoder { // (1)extends ByteToMessageDecoder類
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { // (2)回調(diào)函數(shù)將字節(jié)流寫入in中來(lái)積累緩存
        if (in.readableBytes() < 4) { // (3)判斷in的大小
            return;
        }

        out.add(in.readBytes(4)); // (4)將4個(gè)字節(jié)輸出到out中
    }
}

第二種:利用POJO(Plain Ordinary Java Object)來(lái)代替ByteBuf

構(gòu)造新類型(感覺(jué)和javabean一樣)
package io.netty.example.time;

import java.util.Date;

public class UnixTime {

    private final int value;

    public UnixTime() {
        this((int) (System.currentTimeMillis() / 1000L + 2208988800L));
    }

    public UnixTime(int value) {
        this.value = value;
    }

    public int value() {
        return value;
    }

    @Override
    public String toString() {
        return new Date((value() - 2208988800L) * 1000L).toString();
    }
}
在decode回調(diào)函數(shù)中,將bytebuf組裝成unixTime對(duì)象
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
    if (in.readableBytes() < 4) {
        return;
    }

    out.add(new UnixTime(in.readInt()));
}

總結(jié):關(guān)于bytebuf大小的約定,我覺(jué)得在發(fā)送的時(shí)候在首部加上包頭,即用1字節(jié)來(lái)表示整個(gè)包的大小,這樣就可以控制可變的緩存大小了。雖然增加了數(shù)據(jù)量但是更適應(yīng)于可變的環(huán)境。同樣在使用POJO時(shí),加上類型標(biāo)識(shí)符,也能擁有更加廣泛的可行性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,991評(píng)論 19 139
  • 個(gè)人認(rèn)為,Goodboy1881先生的TCP /IP 協(xié)議詳解學(xué)習(xí)博客系列博客是一部非常精彩的學(xué)習(xí)筆記,這雖然只是...
    貳零壹柒_fc10閱讀 5,096評(píng)論 0 8
  • 前奏 https://tech.meituan.com/2016/11/04/nio.html 綜述 netty通...
    jiangmo閱讀 5,918評(píng)論 0 13
  • 前言 問(wèn)題 現(xiàn)如今我們使用通用的應(yīng)用程序或者類庫(kù)來(lái)實(shí)現(xiàn)系統(tǒng)之間地互相訪問(wèn)。例如,我們經(jīng)常使用一個(gè)HTTP客戶端來(lái)從...
    Kohler閱讀 785評(píng)論 0 2
  • 簡(jiǎn)介 用簡(jiǎn)單的話來(lái)定義tcpdump,就是:dump the traffic on a network,根據(jù)使用者...
    保川閱讀 5,990評(píng)論 1 13