震驚!這可能是我與底層最接近的一次編程體驗
1.netty能做什么
首先netty是一款高性能、封裝性良好且靈活、基于NIO(真·非阻塞IO)的開源框架。可以用來手寫web服務(wù)器、TCP服務(wù)器等,支持的協(xié)議豐富,如:常用的HTTP/HTTPS/WEBSOCKET,并且提供的大量的方法,十分靈活,可以根據(jù)自己的需求量身DIV一款服務(wù)器。
用netty編寫TCP的服務(wù)器/客戶端
1.可以自己設(shè)計數(shù)據(jù)傳輸協(xié)議如下面這樣:

2.可以自定義編碼規(guī)則和解碼規(guī)則
3.可以自定義客戶端與服務(wù)端的數(shù)據(jù)交互細(xì)節(jié),處理socket流攻擊、TCP的粘包和拆包問題
2.Quick Start
-
創(chuàng)建一個普通的maven項目,不依賴任何的三方web服務(wù)器,用main方法執(zhí)行即可。
在這里插入圖片描述 加入POM依賴
<!--netty的依賴集合,都整合在一個依賴?yán)锩媪?->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
<!--這里使用jackson反序列字節(jié)碼-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.7</version>
</dependency>
<!--加入log4j 便于深入學(xué)習(xí)整合運行過程的一些細(xì)節(jié)-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 設(shè)計一套基于TCP的數(shù)據(jù)傳輸協(xié)議
public class TcpProtocol {
private byte header=0x58;
private int len;
private byte [] data;
private byte tail=0x63;
public byte getTail() {
return tail;
}
public void setTail(byte tail) {
this.tail = tail;
}
public TcpProtocol(int len, byte[] data) {
this.len = len;
this.data = data;
}
public TcpProtocol() {
}
public byte getHeader() {
return header;
}
public void setHeader(byte header) {
this.header = header;
}
public int getLen() {
return len;
}
public void setLen(int len) {
this.len = len;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
}
這里使用16進(jìn)制表示協(xié)議的開始位和結(jié)束位,其中0x58代表開始,0x63代表結(jié)束,均用一個字節(jié)來進(jìn)行表示。
- TCP服務(wù)器的啟動類
public class TcpServer {
private int port;
private Logger logger = Logger.getLogger(this.getClass());
public void init(){
logger.info("正在啟動tcp服務(wù)器……");
NioEventLoopGroup boss = new NioEventLoopGroup();//主線程組
NioEventLoopGroup work = new NioEventLoopGroup();//工作線程組
try {
ServerBootstrap bootstrap = new ServerBootstrap();//引導(dǎo)對象
bootstrap.group(boss,work);//配置工作線程組
bootstrap.channel(NioServerSocketChannel.class);//配置為NIO的socket通道
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {//綁定通道參數(shù)
ch.pipeline().addLast("logging",new LoggingHandler("DEBUG"));//設(shè)置log監(jiān)聽器,并且日志級別為debug,方便觀察運行流程
ch.pipeline().addLast("encode",new EncoderHandler());//編碼器。發(fā)送消息時候用過
ch.pipeline().addLast("decode",new DecoderHandler());//解碼器,接收消息時候用
ch.pipeline().addLast("handler",new BusinessHandler());//業(yè)務(wù)處理類,最終的消息會在這個handler中進(jìn)行業(yè)務(wù)處理
}
});
bootstrap.option(ChannelOption.SO_BACKLOG,1024);//緩沖區(qū)
bootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);//ChannelOption對象設(shè)置TCP套接字的參數(shù),非必須步驟
ChannelFuture future = bootstrap.bind(port).sync();//使用了Future來啟動線程,并綁定了端口
logger.info("啟動tcp服務(wù)器啟動成功,正在監(jiān)聽端口:"+port);
future.channel().closeFuture().sync();//以異步的方式關(guān)閉端口
}catch (InterruptedException e) {
logger.info("啟動出現(xiàn)異常:"+e);
}finally {
work.shutdownGracefully();
boss.shutdownGracefully();//出現(xiàn)異常后,關(guān)閉線程組
logger.info("tcp服務(wù)器已經(jīng)關(guān)閉");
}
}
public static void main(String[] args) {
new TcpServer(8777).init();
}
public TcpServer(int port) {
this.port = port;
}
}
只要是基于netty的服務(wù)器,都會用到bootstrap 并用這個對象綁定工作線程組,channel的Class,以及用戶DIV的各種pipeline的handler類,注意在添加自定義handler的時候,數(shù)據(jù)的流動順序和pipeline中添加hanlder的順序是一致的。也就是說,從上往下應(yīng)該為:底層字節(jié)流的解碼/編碼handler、業(yè)務(wù)處理handler。
- 編碼器
編碼器是服務(wù)器按照協(xié)議格式返回數(shù)據(jù)給客戶端時候調(diào)用的,繼承MessageToByteEncoder代碼:
public class EncoderHandler extends MessageToByteEncoder {
private Logger logger = Logger.getLogger(this.getClass());
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
if (msg instanceof TcpProtocol){
TcpProtocol protocol = (TcpProtocol) msg;
out.writeByte(protocol.getHeader());
out.writeInt(protocol.getLen());
out.writeBytes(protocol.getData());
out.writeByte(protocol.getTail());
logger.debug("數(shù)據(jù)編碼成功:"+out);
}else {
logger.info("不支持的數(shù)據(jù)協(xié)議:"+msg.getClass()+"\t期待的數(shù)據(jù)協(xié)議類是:"+TcpProtocol.class);
}
}
}
- 解碼器
解碼器屬于比較核心的部分,自定義解碼協(xié)議、粘包、拆包等都在里面實現(xiàn),繼承自ByteToMessageDecoder,其實ByteToMessageDecoder的內(nèi)部已經(jīng)幫我們處理好了拆包/粘包的問題,只需要按照它的設(shè)計原則去實現(xiàn)decode方法即可:
public class DecoderHandler extends ByteToMessageDecoder {
//最小的數(shù)據(jù)長度:開頭標(biāo)準(zhǔn)位1字節(jié)
private static int MIN_DATA_LEN=6;
//數(shù)據(jù)解碼協(xié)議的開始標(biāo)志
private static byte PROTOCOL_HEADER=0x58;
//數(shù)據(jù)解碼協(xié)議的結(jié)束標(biāo)志
private static byte PROTOCOL_TAIL=0x63;
private Logger logger = Logger.getLogger(this.getClass());
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes()>MIN_DATA_LEN){
logger.debug("開始解碼數(shù)據(jù)……");
//標(biāo)記讀操作的指針
in.markReaderIndex();
byte header=in.readByte();
if (header==PROTOCOL_HEADER){
logger.debug("數(shù)據(jù)開頭格式正確");
//讀取字節(jié)數(shù)據(jù)的長度
int len=in.readInt();
//數(shù)據(jù)可讀長度必須要大于len,因為結(jié)尾還有一字節(jié)的解釋標(biāo)志位
if (len>=in.readableBytes()){
logger.debug(String.format("數(shù)據(jù)長度不夠,數(shù)據(jù)協(xié)議len長度為:%1$d,數(shù)據(jù)包實際可讀內(nèi)容為:%2$d正在等待處理拆包……",len,in.readableBytes()));
in.resetReaderIndex();
/*
**結(jié)束解碼,這種情況說明數(shù)據(jù)沒有到齊,在父類ByteToMessageDecoder的callDecode中會對out和in進(jìn)行判斷
* 如果in里面還有可讀內(nèi)容即in.isReadable為true,cumulation中的內(nèi)容會進(jìn)行保留,,直到下一次數(shù)據(jù)到來,將兩幀的數(shù)據(jù)合并起來,再解碼。
* 以此解決拆包問題
*/
return;
}
byte [] data=new byte[len];
in.readBytes(data);//讀取核心的數(shù)據(jù)
byte tail=in.readByte();
if (tail==PROTOCOL_TAIL){
logger.debug("數(shù)據(jù)解碼成功");
out.add(data);
//如果out有值,且in仍然可讀,將繼續(xù)調(diào)用decode方法再次解碼in中的內(nèi)容,以此解決粘包問題
}else {
logger.debug(String.format("數(shù)據(jù)解碼協(xié)議結(jié)束標(biāo)志位:%1$d [錯誤!],期待的結(jié)束標(biāo)志位是:%2$d",tail,PROTOCOL_TAIL));
return;
}
}else {
logger.debug("開頭不對,可能不是期待的客服端發(fā)送的數(shù),將自動略過這一個字節(jié)");
}
}else {
logger.debug("數(shù)據(jù)長度不符合要求,期待最小長度是:"+MIN_DATA_LEN+" 字節(jié)");
return;
}
}
}
首先是黏包問題:
如圖,正常的數(shù)據(jù)傳輸應(yīng)該是像數(shù)據(jù)A那樣,一包就是一個完整的數(shù)據(jù),但也有不正常的情況,比如一包數(shù)據(jù)包含多個數(shù)據(jù)。而在ByteToMessageDecoder會默認(rèn)把二進(jìn)制的字節(jié)碼放在byteBuf中,因此我們在code的時候要知道會有這樣的場景。

而粘包問題實際上不需要我們?nèi)ソ鉀Q,下面是
ByteToMessageDecoder的源碼,callDecode中回調(diào)我們手寫解碼器的decode方法。
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
while (in.isReadable()) {//buf中是否還有數(shù)據(jù)
int outSize = out.size();//標(biāo)記out的size,解析成功的數(shù)據(jù)會添加的out中
if (outSize > 0) {
fireChannelRead(ctx, out, outSize);//這個是回調(diào)業(yè)務(wù)handler的channelRead方法
out.clear();
if (ctx.isRemoved()) {
break;
}
outSize = 0;//清空了out,將標(biāo)記size清零
}
int oldInputLength = in.readableBytes();//這里開始準(zhǔn)備調(diào)用decode方法,標(biāo)記了解碼前的可讀內(nèi)容
decode(ctx, in, out);//對應(yīng)DecoderHandler中的decode方法
if (ctx.isRemoved()) {
break;
}
if (outSize == out.size()) {//相等說明,并沒有解析出來新的object到out中
if (oldInputLength == in.readableBytes()) {//這里很重要,若相等說明decode中沒有讀取任何內(nèi)容出來,這里一般是發(fā)生拆包后,將ByteBuf的指針手動重置。重置后從這個方法break出來。讓ByteToMessageDecoder去處理拆包問題。這里就體現(xiàn)了要按照netty的設(shè)計原則來寫代碼
break;
} else {
continue;//這里直接continue,是考慮讓開發(fā)者去跳過某些字節(jié),比如收到了socket攻擊時,數(shù)據(jù)不按照協(xié)議體來的時候,就直接跳過這些字節(jié)
}
}
if (oldInputLength == in.readableBytes()) {//這種情況屬于,沒有按照netty的設(shè)計原則來。要么是decode中沒有任何邏輯代碼,要么是在out中添加了內(nèi)容后,調(diào)用了byteBuf的resetReaderIndex重置的讀操作的指針
throw new DecoderException(
StringUtil.simpleClassName(getClass()) +
".decode() did not read anything but decoded a message.");
}
if (isSingleDecode()) {//默認(rèn)為false,用來設(shè)置只解析一條數(shù)據(jù)
break;
}
//這里結(jié)束后,繼續(xù)wile循環(huán),因為bytebuf仍然有可讀的內(nèi)容,將會繼續(xù)調(diào)用decode方法解析bytebuf中的字節(jié)碼,以此解決了粘包問題
}
} catch (DecoderException e) {
throw e;
} catch (Throwable cause) {
throw new DecoderException(cause);
}
}
綜合上面的源碼分析后,我們發(fā)現(xiàn):decode方法在while循環(huán)中,也就是bytebuf只要有內(nèi)容就會一直調(diào)用decode方法進(jìn)行解碼操作,因此在解決粘包問題時,只需要按照正常流程來就行了,解析協(xié)議開頭、數(shù)據(jù)字節(jié)、結(jié)束標(biāo)志后將數(shù)據(jù)放入到out這個list中即可。后面將會有數(shù)據(jù)進(jìn)行粘包測試。
- 拆包問題
有時候,我們接收到的數(shù)據(jù)是不完整的,一個包的數(shù)據(jù)被拆成了很多份被后再發(fā)送出去。這種情況有可能是數(shù)據(jù)太大,被分割成很多份發(fā)送出去。比如數(shù)據(jù)包B被拆成兩份進(jìn)行發(fā)送:
在這里插入圖片描述
拆包問題,同樣在ByteToMessageDecoder給我們解決了,我們只需要按照netty的設(shè)計原則去寫decode代碼即可。
首先,假設(shè)需要我們自己去解決拆包問題應(yīng)該怎么實現(xiàn)?
先從問題開始分析,需要的是數(shù)據(jù)B,但是卻只收到了數(shù)據(jù)B_1,這個時候應(yīng)該等待剩余的數(shù)據(jù)B_2的到來,收到的數(shù)據(jù)B_1應(yīng)該用一個累加器存起來,等到B_2到來的時候?qū)砂鼣?shù)據(jù)合并起來再進(jìn)行解碼。
那么問題是,如何讓ByteToMessageDecoder這個知道數(shù)據(jù)不完整呢,在DecoderHandler.decode中有這樣一段代碼:
if (len>=in.readableBytes()){
logger.debug(String.format("數(shù)據(jù)長度不夠,數(shù)據(jù)協(xié)議len長度為:%1$d,數(shù)據(jù)包實際可讀內(nèi)容為:%2$d正在等待處理拆包……",len,in.readableBytes()));
in.resetReaderIndex();
/*
**結(jié)束解碼,這種情況說明數(shù)據(jù)沒有到齊,在父類ByteToMessageDecoder的callDecode中會對out和in進(jìn)行判斷
* 如果in里面還有可讀內(nèi)容即in.isReadable為true,cumulation中的內(nèi)容會進(jìn)行保留,,直到下一次數(shù)據(jù)到來,將兩幀的數(shù)據(jù)合并起來,再解碼。
* 以此解決拆包問題
*/
return;
}
當(dāng)讀到協(xié)議中的len大于bytebuf的可讀內(nèi)容時候說明數(shù)據(jù)不完整,發(fā)生了拆包,調(diào)用resetReaderIndex將讀操作指針復(fù)位,并結(jié)束方法。再看看父類中的CallDecode方法的一段代碼:
if (outSize == out.size()) {//相等說明,并沒有解析出來新的object到out中
if (oldInputLength == in.readableBytes()) {//這里很重要,若相等說明decode中沒有讀取任何內(nèi)容出來,這里一般是發(fā)生拆包后,將ByteBuf的指針手動重置。重置后從這個方法break出來。讓ByteToMessageDecoder去處理拆包問題。這里就體現(xiàn)了要按照netty的設(shè)計原則來寫代碼
break;//退出該方法
} else {
continue;//這里直接continue,是考慮讓開發(fā)者去跳過某些字節(jié),比如收到了socket攻擊時,數(shù)據(jù)不按照協(xié)議體來的時候,就直接跳過這些字節(jié)
}
}
退出callDecode后,返回到channelRead中:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
CodecOutputList out = CodecOutputList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
first = cumulation == null;
if (first) {
cumulation = data;
} else {
cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
}
callDecode(ctx, cumulation, out);//注意這里傳入的不是data,而是cumulator,這個對象相當(dāng)于一個累加器,也就是說每次調(diào)用callDecode的時候傳入的byteBuf實際上是經(jīng)過累加后的cumulation
} catch (DecoderException e) {
throw e;
} catch (Throwable t) {
throw new DecoderException(t);
} finally {
if (cumulation != null && !cumulation.isReadable()) {//這里若是數(shù)據(jù)被讀取完,會清空累加器cumulation
numReads = 0;
cumulation.release();
cumulation = null;
} else if (++ numReads >= discardAfterReads) {
// We did enough reads already try to discard some bytes so we not risk to see a OOME.
// See https://github.com/netty/netty/issues/4275
numReads = 0;
discardSomeReadBytes();
}
int size = out.size();
decodeWasNull = !out.insertSinceRecycled();
fireChannelRead(ctx, out, size);
out.recycle();
}
} else {
ctx.fireChannelRead(msg);
}
}
而channelRead方法是,收到一包數(shù)據(jù)后就會調(diào)用一次。至此,netty幫我們完美解決了拆包問題。我們只需要按著他的設(shè)計原則:len>byteBuf.readableBytes時候,重置讀指針,結(jié)束decode即可。
- 業(yè)務(wù)處理handler類
這一層中數(shù)據(jù)已經(jīng)被完整的解析出來了,可以直接使用了:
public class BusinessHandler extends ChannelInboundHandlerAdapter {
private ObjectMapper objectMapper= ByteUtils.InstanceObjectMapper();
private Logger logger = Logger.getLogger(this.getClass());
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof byte []){
logger.debug("解碼后的字節(jié)碼:"+new String((byte[]) msg,"UTF-8"));
try {
Object objectContainer = objectMapper.readValue((byte[]) msg, DTObject.class);
if (objectContainer instanceof DTObject){
DTObject data = (DTObject) objectContainer;
if (data.getClassName()!=null&&data.getObject().length>0){
Object object = objectMapper.readValue(data.getObject(), Class.forName(data.getClassName()));
logger.info("收到實體對象:"+object);
}
}
}catch (Exception e){
logger.info("對象反序列化出現(xiàn)問題:"+e);
}
}
}
}
由于在decode中并沒有將字節(jié)碼反序列成對象,因此需要進(jìn)一步反序列化。在傳輸數(shù)據(jù)的時候,可能傳遞的對象不只是一種,因此在反序列化也要考慮到這一問題。解決辦法是將傳輸?shù)膶ο筮M(jìn)行二次包裝,將全名類信息包含進(jìn)去:
public class DTObject {
private String className;
private byte[] object;
}
這樣在反序列化的時候使用
Class.forName()獲取Class,避免了要寫很多if循環(huán)判斷反序列化的對象的Class。前提是要類名和包路徑要完全匹配!
- 接下來編寫一個TCP客戶端進(jìn)行測試
啟動類的init方法:
public void init() throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE,true);
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast("logging",new LoggingHandler("DEBUG"));
ch.pipeline().addLast(new EncoderHandler());
ch.pipeline().addLast(new EchoHandler());
}
});
bootstrap.remoteAddress(ip,port);
ChannelFuture future = bootstrap.connect().sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
group.shutdownGracefully().sync();
}
}
客戶端的handler:
public class EchoHandler extends ChannelInboundHandlerAdapter {
//連接成功后發(fā)送消息測試
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
User user = new User();
user.setBirthday(new Date());
user.setUID(UUID.randomUUID().toString());
user.setName("冉鵬峰");
user.setAge(22);
DTObject dtObject = new DTObject();
dtObject.setClassName(user.getClass().getName());
dtObject.setObject(ByteUtils.InstanceObjectMapper().writeValueAsBytes(user));
TcpProtocol tcpProtocol = new TcpProtocol();
byte [] objectBytes=ByteUtils.InstanceObjectMapper().writeValueAsBytes(dtObject);
tcpProtocol.setLen(objectBytes.length);
tcpProtocol.setData(objectBytes);
ctx.write(tcpProtocol);
ctx.flush();
}
}
這個handler是為了模擬在TCP連接建立好之后發(fā)送一包的數(shù)據(jù)到服務(wù)端經(jīng)行測試,通過channel的write去發(fā)送數(shù)據(jù),只要在啟動類TcpClient配置了編碼器的EncoderHandler,就可以直接將對象tcpProtocol傳進(jìn)去,它將在EncoderHandler的encode方法中被自動轉(zhuǎn)換成字節(jié)碼放入bytebuf中。
- 正常數(shù)據(jù)傳輸測試:
結(jié)果:
2019-01-14 16:30:34 DEBUG [org.wisdom.server.decoder.DecoderHandler] 開始解碼數(shù)據(jù)……
2019-01-14 16:30:34 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)開頭格式正確
2019-01-14 16:30:34 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)解碼成功
2019-01-14 16:30:34 DEBUG [org.wisdom.server.business.BusinessHandler] 解碼后的字節(jié)碼:{"className":"pojo.User","object":"eyJuYW1lIjoi5YaJ6bmP5bOwIiwiYWdlIjoyNCwiYmlydGhkYXkiOiIyMDE5LzAxLzE0IDA0OjMwOjE0IiwidWlkIjoiOGY0OTM0OGEtMWNmMy00ZTEyLWEzZTAtY2M1ZTJjZTkzMDdlIn0="}
2019-01-14 16:30:34 INFO [org.wisdom.server.business.BusinessHandler] 收到實體對象:User{name='冉鵬峰', age=24, UID='8f49348a-1cf3-4e12-a3e0-cc5e2ce9307e', birthday=Mon Jan 14 04:30:00 CST 2019}
可以看到最終的實體對象User被成功的解析出來。
在debug模式下還會看到這樣的一個表格在控制臺輸出:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 58 00 00 00 b5 7b 22 63 6c 61 73 73 4e 61 6d 65 |X....{"className|
|00000010| 22 3a 22 70 6f 6a 6f 2e 55 73 65 72 22 2c 22 6f |":"pojo.User","o|
|00000020| 62 6a 65 63 74 22 3a 22 65 79 4a 75 59 57 31 6c |bject":"eyJuYW1l|
|00000030| 49 6a 6f 69 35 59 61 4a 36 62 6d 50 35 62 4f 77 |Ijoi5YaJ6bmP5bOw|
|00000040| 49 69 77 69 59 57 64 6c 49 6a 6f 79 4e 43 77 69 |IiwiYWdlIjoyNCwi|
|00000050| 59 6d 6c 79 64 47 68 6b 59 58 6b 69 4f 69 49 79 |YmlydGhkYXkiOiIy|
|00000060| 4d 44 45 35 4c 7a 41 78 4c 7a 45 30 49 44 41 30 |MDE5LzAxLzE0IDA0|
|00000070| 4f 6a 4d 77 4f 6a 45 30 49 69 77 69 64 57 6c 6b |OjMwOjE0IiwidWlk|
|00000080| 49 6a 6f 69 4f 47 59 30 4f 54 4d 30 4f 47 45 74 |IjoiOGY0OTM0OGEt|
|00000090| 4d 57 4e 6d 4d 79 30 30 5a 54 45 79 4c 57 45 7a |MWNmMy00ZTEyLWEz|
|000000a0| 5a 54 41 74 59 32 4d 31 5a 54 4a 6a 5a 54 6b 7a |ZTAtY2M1ZTJjZTkz|
|000000b0| 4d 44 64 6c 49 6e 30 3d 22 7d 63 |MDdlIn0="}c |
+--------+-------------------------------------------------+----------------+
這個是相當(dāng)于真實的數(shù)據(jù)抓包展示,數(shù)據(jù)被轉(zhuǎn)換成字節(jié)碼后是以二進(jìn)制的形式在TCP緩存區(qū)沖傳輸過來。但是二進(jìn)制太長了,所以一般都是轉(zhuǎn)換成16進(jìn)制顯示的,一個表格顯示一個字節(jié)的數(shù)據(jù),數(shù)據(jù)由地位到高位由左到右,由上到下進(jìn)行排列。其中0x58為TcpProtocol中設(shè)置的開始標(biāo)志,00 00 00 b5為數(shù)據(jù)的長度,因為是int類型所以占用了四個字節(jié)從7b--7d內(nèi)容為要傳輸?shù)臄?shù)據(jù)內(nèi)容,結(jié)尾的0x63為TcpProtocol設(shè)置的結(jié)束標(biāo)志位。
- 粘包測試
為了模擬粘包,首先將啟動類TcpClient中配置的編碼器的EncoderHandler注釋掉:
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast("logging",new LoggingHandler("DEBUG"));
//ch.pipeline().addLast(new EncoderHandler()); 因為需要在byteBuf中手動模擬粘包的場景
ch.pipeline().addLast(new EchoHandler());
}
});
然后在發(fā)送的時候故意將三幀的數(shù)據(jù),放在一個包中就行發(fā)送,在EchoHanlder做如下修改:
public void channelActive(ChannelHandlerContext ctx) throws Exception {
User user = new User();
user.setBirthday(new Date());
user.setUID(UUID.randomUUID().toString());
user.setName("冉鵬峰");
user.setAge(24);
DTObject dtObject = new DTObject();
dtObject.setClassName(user.getClass().getName());
dtObject.setObject(ByteUtils.InstanceObjectMapper().writeValueAsBytes(user));
TcpProtocol tcpProtocol = new TcpProtocol();
byte [] objectBytes=ByteUtils.InstanceObjectMapper().writeValueAsBytes(dtObject);
tcpProtocol.setLen(objectBytes.length);
tcpProtocol.setData(objectBytes);
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeByte(tcpProtocol.getHeader());
buffer.writeInt(tcpProtocol.getLen());
buffer.writeBytes(tcpProtocol.getData());
buffer.writeByte(tcpProtocol.getTail());
//模擬粘包的第二幀數(shù)據(jù)
buffer.writeByte(tcpProtocol.getHeader());
buffer.writeInt(tcpProtocol.getLen());
buffer.writeBytes(tcpProtocol.getData());
buffer.writeByte(tcpProtocol.getTail());
//模擬粘包的第三幀數(shù)據(jù)
buffer.writeByte(tcpProtocol.getHeader());
buffer.writeInt(tcpProtocol.getLen());
buffer.writeBytes(tcpProtocol.getData());
buffer.writeByte(tcpProtocol.getTail());
ctx.write(buffer);
ctx.flush();
}
運行結(jié)果:
2019-01-14 16:44:51 DEBUG [org.wisdom.server.decoder.DecoderHandler] 開始解碼數(shù)據(jù)……
2019-01-14 16:44:51 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)開頭格式正確
2019-01-14 16:44:51 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)解碼成功
2019-01-14 16:44:51 DEBUG [org.wisdom.server.business.BusinessHandler] 解碼后的字節(jié)碼:{"className":"pojo.User","object":"eyJuYW1lIjoi5YaJ6bmP5bOwIiwiYWdlIjoyNCwiYmlydGhkYXkiOiIyMDE5LzAxLzE0IDA0OjQ0OjE0IiwidWlkIjoiODFkZTU5YWUtMzQ4Mi00ZDFhLWJjNDMtN2NjMTJmOTI1ZTUxIn0="}
2019-01-14 16:44:51 INFO [org.wisdom.server.business.BusinessHandler] 收到實體對象:User{name='冉鵬峰', age=24, UID='81de59ae-3482-4d1a-bc43-7cc12f925e51', birthday=Mon Jan 14 04:44:00 CST 2019}
2019-01-14 16:44:51 DEBUG [org.wisdom.server.decoder.DecoderHandler] 開始解碼數(shù)據(jù)……
2019-01-14 16:44:51 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)開頭格式正確
2019-01-14 16:44:51 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)解碼成功
2019-01-14 16:44:51 DEBUG [org.wisdom.server.business.BusinessHandler] 解碼后的字節(jié)碼:{"className":"pojo.User","object":"eyJuYW1lIjoi5YaJ6bmP5bOwIiwiYWdlIjoyNCwiYmlydGhkYXkiOiIyMDE5LzAxLzE0IDA0OjQ0OjE0IiwidWlkIjoiODFkZTU5YWUtMzQ4Mi00ZDFhLWJjNDMtN2NjMTJmOTI1ZTUxIn0="}
2019-01-14 16:44:51 INFO [org.wisdom.server.business.BusinessHandler] 收到實體對象:User{name='冉鵬峰', age=24, UID='81de59ae-3482-4d1a-bc43-7cc12f925e51', birthday=Mon Jan 14 04:44:00 CST 2019}
2019-01-14 16:44:51 DEBUG [org.wisdom.server.decoder.DecoderHandler] 開始解碼數(shù)據(jù)……
2019-01-14 16:44:51 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)開頭格式正確
2019-01-14 16:44:51 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)解碼成功
2019-01-14 16:44:51 DEBUG [org.wisdom.server.business.BusinessHandler] 解碼后的字節(jié)碼:{"className":"pojo.User","object":"eyJuYW1lIjoi5YaJ6bmP5bOwIiwiYWdlIjoyNCwiYmlydGhkYXkiOiIyMDE5LzAxLzE0IDA0OjQ0OjE0IiwidWlkIjoiODFkZTU5YWUtMzQ4Mi00ZDFhLWJjNDMtN2NjMTJmOTI1ZTUxIn0="}
2019-01-14 16:44:51 INFO [org.wisdom.server.business.BusinessHandler] 收到實體對象:User{name='冉鵬峰', age=24, UID='81de59ae-3482-4d1a-bc43-7cc12f925e51', birthday=Mon Jan 14 04:44:00 CST 2019}
服務(wù)器成功解析出來了三幀的數(shù)據(jù),BusinessHandler的channelRead方法被調(diào)用了三次。
而抓到的數(shù)據(jù)包也確實是模擬的三幀數(shù)據(jù)黏在一個包中:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 58 00 00 00 b5 7b 22 63 6c 61 73 73 4e 61 6d 65 |X....{"className|
|00000010| 22 3a 22 70 6f 6a 6f 2e 55 73 65 72 22 2c 22 6f |":"pojo.User","o|
|00000020| 62 6a 65 63 74 22 3a 22 65 79 4a 75 59 57 31 6c |bject":"eyJuYW1l|
|00000030| 49 6a 6f 69 35 59 61 4a 36 62 6d 50 35 62 4f 77 |Ijoi5YaJ6bmP5bOw|
|00000040| 49 69 77 69 59 57 64 6c 49 6a 6f 79 4e 43 77 69 |IiwiYWdlIjoyNCwi|
|00000050| 59 6d 6c 79 64 47 68 6b 59 58 6b 69 4f 69 49 79 |YmlydGhkYXkiOiIy|
|00000060| 4d 44 45 35 4c 7a 41 78 4c 7a 45 30 49 44 41 30 |MDE5LzAxLzE0IDA0|
|00000070| 4f 6a 51 30 4f 6a 45 30 49 69 77 69 64 57 6c 6b |OjQ0OjE0IiwidWlk|
|00000080| 49 6a 6f 69 4f 44 46 6b 5a 54 55 35 59 57 55 74 |IjoiODFkZTU5YWUt|
|00000090| 4d 7a 51 34 4d 69 30 30 5a 44 46 68 4c 57 4a 6a |MzQ4Mi00ZDFhLWJj|
|000000a0| 4e 44 4d 74 4e 32 4e 6a 4d 54 4a 6d 4f 54 49 31 |NDMtN2NjMTJmOTI1|
|000000b0| 5a 54 55 78 49 6e 30 3d 22 7d 【63】 58 00 00 00 b5 |ZTUxIn0="}cX....|
|000000c0| 7b 22 63 6c 61 73 73 4e 61 6d 65 22 3a 22 70 6f |{"className":"po|
|000000d0| 6a 6f 2e 55 73 65 72 22 2c 22 6f 62 6a 65 63 74 |jo.User","object|
|000000e0| 22 3a 22 65 79 4a 75 59 57 31 6c 49 6a 6f 69 35 |":"eyJuYW1lIjoi5|
|000000f0| 59 61 4a 36 62 6d 50 35 62 4f 77 49 69 77 69 59 |YaJ6bmP5bOwIiwiY|
|00000100| 57 64 6c 49 6a 6f 79 4e 43 77 69 59 6d 6c 79 64 |WdlIjoyNCwiYmlyd|
|00000110| 47 68 6b 59 58 6b 69 4f 69 49 79 4d 44 45 35 4c |GhkYXkiOiIyMDE5L|
|00000120| 7a 41 78 4c 7a 45 30 49 44 41 30 4f 6a 51 30 4f |zAxLzE0IDA0OjQ0O|
|00000130| 6a 45 30 49 69 77 69 64 57 6c 6b 49 6a 6f 69 4f |jE0IiwidWlkIjoiO|
|00000140| 44 46 6b 5a 54 55 35 59 57 55 74 4d 7a 51 34 4d |DFkZTU5YWUtMzQ4M|
|00000150| 69 30 30 5a 44 46 68 4c 57 4a 6a 4e 44 4d 74 4e |i00ZDFhLWJjNDMtN|
|00000160| 32 4e 6a 4d 54 4a 6d 4f 54 49 31 5a 54 55 78 49 |2NjMTJmOTI1ZTUxI|
|00000170| 6e 30 3d 22 7d 【63】 58 00 00 00 b5 7b 22 63 6c 61 |n0="}cX....{"cla|
|00000180| 73 73 4e 61 6d 65 22 3a 22 70 6f 6a 6f 2e 55 73 |ssName":"pojo.Us|
|00000190| 65 72 22 2c 22 6f 62 6a 65 63 74 22 3a 22 65 79 |er","object":"ey|
|000001a0| 4a 75 59 57 31 6c 49 6a 6f 69 35 59 61 4a 36 62 |JuYW1lIjoi5YaJ6b|
|000001b0| 6d 50 35 62 4f 77 49 69 77 69 59 57 64 6c 49 6a |mP5bOwIiwiYWdlIj|
|000001c0| 6f 79 4e 43 77 69 59 6d 6c 79 64 47 68 6b 59 58 |oyNCwiYmlydGhkYX|
|000001d0| 6b 69 4f 69 49 79 4d 44 45 35 4c 7a 41 78 4c 7a |kiOiIyMDE5LzAxLz|
|000001e0| 45 30 49 44 41 30 4f 6a 51 30 4f 6a 45 30 49 69 |E0IDA0OjQ0OjE0Ii|
|000001f0| 77 69 64 57 6c 6b 49 6a 6f 69 4f 44 46 6b 5a 54 |widWlkIjoiODFkZT|
|00000200| 55 35 59 57 55 74 4d 7a 51 34 4d 69 30 30 5a 44 |U5YWUtMzQ4Mi00ZD|
|00000210| 46 68 4c 57 4a 6a 4e 44 4d 74 4e 32 4e 6a 4d 54 |FhLWJjNDMtN2NjMT|
|00000220| 4a 6d 4f 54 49 31 5a 54 55 78 49 6e 30 3d 22 7d |JmOTI1ZTUxIn0="}|
|00000230|【63】 |c |
+--------+-------------------------------------------------+----------------+
可以看到確實存在三個尾巴【63】
在netty4.x版本中,粘包問題確實被netty的ByteToMessageDecoder中的CallDecode方法中給處理掉了。
- 拆包問題
這次還是將TcpClient中的編碼器EncoderHandler注釋掉,然后在EchoHandler的channelActive中模擬數(shù)據(jù)的拆包問題:
public void channelActive(ChannelHandlerContext ctx) throws Exception {
User user = new User();
user.setBirthday(new Date());
user.setUID(UUID.randomUUID().toString());
user.setName("冉鵬峰");
user.setAge(24);
DTObject dtObject = new DTObject();
dtObject.setClassName(user.getClass().getName());
dtObject.setObject(ByteUtils.InstanceObjectMapper().writeValueAsBytes(user));
TcpProtocol tcpProtocol = new TcpProtocol();
byte [] objectBytes=ByteUtils.InstanceObjectMapper().writeValueAsBytes(dtObject);
tcpProtocol.setLen(objectBytes.length);
tcpProtocol.setData(objectBytes);
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeByte(tcpProtocol.getHeader());
buffer.writeInt(tcpProtocol.getLen());
buffer.writeBytes(Arrays.copyOfRange(tcpProtocol.getData(),0,tcpProtocol.getLen()/2));//只發(fā)送二分之一的數(shù)據(jù)包
//模擬拆包
ctx.write(buffer);
ctx.flush();
Thread.sleep(3000);//模擬網(wǎng)絡(luò)延時
buffer = ctx.alloc().buffer();
buffer.writeBytes(Arrays.copyOfRange(tcpProtocol.getData(),tcpProtocol.getLen()/2,tcpProtocol.getLen()));//將剩下的二分之和尾巴發(fā)送過去
buffer.writeByte(tcpProtocol.getTail());
ctx.write(buffer);
ctx.flush();
}
運行結(jié)果:
首先是客戶端這邊:
2019-01-14 17:08:33 DEBUG [DEBUG] [id: 0x3b8cbbbb, L:/127.0.0.1:51138 - R:/127.0.0.1:8777] WRITE: 95B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 58 00 00 00 b5 7b 22 63 6c 61 73 73 4e 61 6d 65 |X....{"className|
|00000010| 22 3a 22 70 6f 6a 6f 2e 55 73 65 72 22 2c 22 6f |":"pojo.User","o|
|00000020| 62 6a 65 63 74 22 3a 22 65 79 4a 75 59 57 31 6c |bject":"eyJuYW1l|
|00000030| 49 6a 6f 69 35 59 61 4a 36 62 6d 50 35 62 4f 77 |Ijoi5YaJ6bmP5bOw|
|00000040| 49 69 77 69 59 57 64 6c 49 6a 6f 79 4e 43 77 69 |IiwiYWdlIjoyNCwi|
|00000050| 59 6d 6c 79 64 47 68 6b 59 58 6b 69 4f 69 49 |YmlydGhkYXkiOiI |
+--------+-------------------------------------------------+----------------+
2019-01-14 17:08:33 DEBUG [DEBUG] [id: 0x3b8cbbbb, L:/127.0.0.1:51138 - R:/127.0.0.1:8777] FLUSH
2019-01-14 17:08:36 DEBUG [DEBUG] [id: 0x3b8cbbbb, L:/127.0.0.1:51138 - R:/127.0.0.1:8777] WRITE: 92B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 79 4d 44 45 35 4c 7a 41 78 4c 7a 45 30 49 44 41 |yMDE5LzAxLzE0IDA|
|00000010| 31 4f 6a 41 34 4f 6a 45 30 49 69 77 69 64 57 6c |1OjA4OjE0IiwidWl|
|00000020| 6b 49 6a 6f 69 4f 57 45 79 5a 6a 49 35 4d 6d 4d |kIjoiOWEyZjI5MmM|
|00000030| 74 4d 6a 4d 35 4f 43 30 30 5a 6a 6b 77 4c 57 46 |tMjM5OC00ZjkwLWF|
|00000040| 6b 5a 57 59 74 5a 6d 46 6c 4e 44 45 7a 5a 6a 55 |kZWYtZmFlNDEzZjU|
|00000050| 35 4e 32 45 33 49 6e 30 3d 22 7d 63 |5N2E3In0="}c |
+--------+-------------------------------------------------+----------------+
2019-01-14 17:08:36 DEBUG [DEBUG] [id: 0x3b8cbbbb, L:/127.0.0.1:51138 - R:/127.0.0.1:8777] FLUSH
確實是將數(shù)據(jù)分成兩包發(fā)送出去了
再看看服務(wù)端的輸出日志:
2019-01-14 17:08:33 DEBUG [DEBUG] [id: 0x8e5811b3, L:/127.0.0.1:8777 - R:/127.0.0.1:51138] RECEIVED: 95B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 58 00 00 00 b5 7b 22 63 6c 61 73 73 4e 61 6d 65 |X....{"className|
|00000010| 22 3a 22 70 6f 6a 6f 2e 55 73 65 72 22 2c 22 6f |":"pojo.User","o|
|00000020| 62 6a 65 63 74 22 3a 22 65 79 4a 75 59 57 31 6c |bject":"eyJuYW1l|
|00000030| 49 6a 6f 69 35 59 61 4a 36 62 6d 50 35 62 4f 77 |Ijoi5YaJ6bmP5bOw|
|00000040| 49 69 77 69 59 57 64 6c 49 6a 6f 79 4e 43 77 69 |IiwiYWdlIjoyNCwi|
|00000050| 59 6d 6c 79 64 47 68 6b 59 58 6b 69 4f 69 49 |YmlydGhkYXkiOiI |
+--------+-------------------------------------------------+----------------+
2019-01-14 17:08:33 DEBUG [org.wisdom.server.decoder.DecoderHandler] 開始解碼數(shù)據(jù)……
2019-01-14 17:08:33 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)開頭格式正確
2019-01-14 17:08:33 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)長度不夠,數(shù)據(jù)協(xié)議len長度為:181,數(shù)據(jù)包實際可讀內(nèi)容為:90正在等待處理拆包……
2019-01-14 17:08:36 DEBUG [DEBUG] [id: 0x8e5811b3, L:/127.0.0.1:8777 - R:/127.0.0.1:51138] RECEIVED: 92B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 79 4d 44 45 35 4c 7a 41 78 4c 7a 45 30 49 44 41 |yMDE5LzAxLzE0IDA|
|00000010| 31 4f 6a 41 34 4f 6a 45 30 49 69 77 69 64 57 6c |1OjA4OjE0IiwidWl|
|00000020| 6b 49 6a 6f 69 4f 57 45 79 5a 6a 49 35 4d 6d 4d |kIjoiOWEyZjI5MmM|
|00000030| 74 4d 6a 4d 35 4f 43 30 30 5a 6a 6b 77 4c 57 46 |tMjM5OC00ZjkwLWF|
|00000040| 6b 5a 57 59 74 5a 6d 46 6c 4e 44 45 7a 5a 6a 55 |kZWYtZmFlNDEzZjU|
|00000050| 35 4e 32 45 33 49 6e 30 3d 22 7d 63 |5N2E3In0="}c |
+--------+-------------------------------------------------+----------------+
2019-01-14 17:08:36 DEBUG [org.wisdom.server.decoder.DecoderHandler] 開始解碼數(shù)據(jù)……
2019-01-14 17:08:36 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)開頭格式正確
2019-01-14 17:08:36 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)解碼成功
2019-01-14 17:08:36 DEBUG [org.wisdom.server.business.BusinessHandler] 解碼后的字節(jié)碼:{"className":"pojo.User","object":"eyJuYW1lIjoi5YaJ6bmP5bOwIiwiYWdlIjoyNCwiYmlydGhkYXkiOiIyMDE5LzAxLzE0IDA1OjA4OjE0IiwidWlkIjoiOWEyZjI5MmMtMjM5OC00ZjkwLWFkZWYtZmFlNDEzZjU5N2E3In0="}
2019-01-14 17:08:36 INFO [org.wisdom.server.business.BusinessHandler] 收到實體對象:User{name='冉鵬峰', age=24, UID='9a2f292c-2398-4f90-adef-fae413f597a7', birthday=Mon Jan 14 05:08:00 CST 2019}
在第一包數(shù)據(jù),判斷到bytebuf中的可讀內(nèi)容不夠的時候,終止解碼,并且從父類的callDecode中的while循環(huán)break出去,在父類的channelRead中等待下一包數(shù)據(jù)到來的時候?qū)砂鼣?shù)據(jù)合并起來再次decode解碼。
- 最后測試下同時出現(xiàn)拆包、粘包的場景
還是將TcpClient中的編碼器EncoderHandler注釋掉,然后在EchoHandler的ChannelActive方法:
public void channelActive(ChannelHandlerContext ctx) throws Exception {
User user = new User();
user.setBirthday(new Date());
user.setUID(UUID.randomUUID().toString());
user.setName("冉鵬峰");
user.setAge(24);
DTObject dtObject = new DTObject();
dtObject.setClassName(user.getClass().getName());
dtObject.setObject(ByteUtils.InstanceObjectMapper().writeValueAsBytes(user));
TcpProtocol tcpProtocol = new TcpProtocol();
byte [] objectBytes=ByteUtils.InstanceObjectMapper().writeValueAsBytes(dtObject);
tcpProtocol.setLen(objectBytes.length);
tcpProtocol.setData(objectBytes);
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeByte(tcpProtocol.getHeader());
buffer.writeInt(tcpProtocol.getLen());
buffer.writeBytes(Arrays.copyOfRange(tcpProtocol.getData(),0,tcpProtocol.getLen()/2));//拆包,只發(fā)送一半的數(shù)據(jù)
ctx.write(buffer);
ctx.flush();
Thread.sleep(3000);
buffer = ctx.alloc().buffer();
buffer.writeBytes(Arrays.copyOfRange(tcpProtocol.getData(),tcpProtocol.getLen()/2,tcpProtocol.getLen())); //拆包發(fā)送剩余的一半數(shù)據(jù)
buffer.writeByte(tcpProtocol.getTail());
//模擬粘包的第二幀數(shù)據(jù)
buffer.writeByte(tcpProtocol.getHeader());
buffer.writeInt(tcpProtocol.getLen());
buffer.writeBytes(tcpProtocol.getData());
buffer.writeByte(tcpProtocol.getTail());
//模擬粘包的第三幀數(shù)據(jù)
buffer.writeByte(tcpProtocol.getHeader());
buffer.writeInt(tcpProtocol.getLen());
buffer.writeBytes(tcpProtocol.getData());
buffer.writeByte(tcpProtocol.getTail());
ctx.write(buffer);
ctx.flush();
}
最后直接查看服務(wù)端的輸出結(jié)果:
2019-01-14 17:19:25 DEBUG [org.wisdom.server.decoder.DecoderHandler] 開始解碼數(shù)據(jù)……
2019-01-14 17:19:25 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)開頭格式正確
2019-01-14 17:19:25 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)長度不夠,數(shù)據(jù)協(xié)議len長度為:181,數(shù)據(jù)包實際可讀內(nèi)容為:90正在等待處理拆包……
2019-01-14 17:19:28 DEBUG [DEBUG] [id: 0xc46234aa, L:/127.0.0.1:8777 - R:/127.0.0.1:51466] RECEIVED: 466B
2019-01-14 17:19:28 DEBUG [org.wisdom.server.decoder.DecoderHandler] 開始解碼數(shù)據(jù)……
2019-01-14 17:19:28 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)開頭格式正確
2019-01-14 17:19:28 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)解碼成功
2019-01-14 17:19:28 DEBUG [org.wisdom.server.business.BusinessHandler] 解碼后的字節(jié)碼:{"className":"pojo.User","object":"eyJuYW1lIjoi5YaJ6bmP5bOwIiwiYWdlIjoyNCwiYmlydGhkYXkiOiIyMDE5LzAxLzE0IDA1OjE5OjE0IiwidWlkIjoiODE2Zjg2ZDItNDBhMS00MDRkLTgwMWItZmY1NzgxMTJhNjFmIn0="}
2019-01-14 17:19:28 INFO [org.wisdom.server.business.BusinessHandler] 收到實體對象:User{name='冉鵬峰', age=24, UID='816f86d2-40a1-404d-801b-ff578112a61f', birthday=Mon Jan 14 05:19:00 CST 2019}
2019-01-14 17:19:28 DEBUG [org.wisdom.server.decoder.DecoderHandler] 開始解碼數(shù)據(jù)……
2019-01-14 17:19:28 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)開頭格式正確
2019-01-14 17:19:28 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)解碼成功
2019-01-14 17:19:28 DEBUG [org.wisdom.server.business.BusinessHandler] 解碼后的字節(jié)碼:{"className":"pojo.User","object":"eyJuYW1lIjoi5YaJ6bmP5bOwIiwiYWdlIjoyNCwiYmlydGhkYXkiOiIyMDE5LzAxLzE0IDA1OjE5OjE0IiwidWlkIjoiODE2Zjg2ZDItNDBhMS00MDRkLTgwMWItZmY1NzgxMTJhNjFmIn0="}
2019-01-14 17:19:28 INFO [org.wisdom.server.business.BusinessHandler] 收到實體對象:User{name='冉鵬峰', age=24, UID='816f86d2-40a1-404d-801b-ff578112a61f', birthday=Mon Jan 14 05:19:00 CST 2019}
2019-01-14 17:19:28 DEBUG [org.wisdom.server.decoder.DecoderHandler] 開始解碼數(shù)據(jù)……
2019-01-14 17:19:28 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)開頭格式正確
2019-01-14 17:19:28 DEBUG [org.wisdom.server.decoder.DecoderHandler] 數(shù)據(jù)解碼成功
2019-01-14 17:19:28 DEBUG [org.wisdom.server.business.BusinessHandler] 解碼后的字節(jié)碼:{"className":"pojo.User","object":"eyJuYW1lIjoi5YaJ6bmP5bOwIiwiYWdlIjoyNCwiYmlydGhkYXkiOiIyMDE5LzAxLzE0IDA1OjE5OjE0IiwidWlkIjoiODE2Zjg2ZDItNDBhMS00MDRkLTgwMWItZmY1NzgxMTJhNjFmIn0="}
2019-01-14 17:19:28 INFO [org.wisdom.server.business.BusinessHandler] 收到實體對象:User{name='冉鵬峰', age=24, UID='816f86d2-40a1-404d-801b-ff578112a61f', birthday=Mon Jan 14 05:19:00 CST 2019}
總結(jié)
對于拆包、粘包只要配合netty的設(shè)計原則去實現(xiàn)代碼,就能愉快且輕松的解決了。本例雖然通過DTObject包裝了數(shù)據(jù),避免解碼時每增加一種對象類型,就要新增一個if判斷的尷尬。但是仍然無法處理傳輸List、Map時候的場景。下一篇將介紹如何處理List、Map、普通對象的場景。

