前言
學習Netty也有一段時間了,Netty作為一個高性能的異步框架,很多RPC框架也運用到了Netty中的知識,在rpc框架中豐富的數據協議及編解碼可以讓使用者更加青睞;
Netty支持豐富的編解碼框架,其本身內部提供的編解碼也可以應對各種業務場景;
今天主要就是學習下Netty中提供的編、解碼類,之前只是簡單的使用了下Netty提供的解碼類,今天更加深入的研究下Netty中編、解碼的源碼及部分使用。
編、解碼的概念
編碼(Encoder)
編碼就是將我們發送的數據編碼成字節數組方便在網絡中進行傳輸,類似Java中的序列化,將對象序列化成字節傳輸
解碼(Decoder)
解碼和編碼相反,將傳輸過來的字節數組轉化為各種對象來進行展示等,類似Java中的反序列化
如:
// 將字節數組轉化為字符串
new String(byte bytes[], Charset charset)
編、解碼超類
ByteToMessageDecoder: 解碼超類,將字節轉換成消息
解碼解碼一般用於將獲取到的消息解碼成系統可識別且自己需要的數據結構;因此ByteToMessageDecoder需要繼承ChannelInboundHandlerAdapter入站適配器來獲取到入站的數據,在handler使用之前通過channelRead獲取入站數據進行一波解碼;
ByteToMessageDecoder類圖
源碼分析
通過channelRead獲取入站數據,將數據緩存至cumulation數據緩衝區,最後在傳給decode進行解碼,在read完成之後清空緩存的數據
1. 獲取入站數據
/**
* 通過重寫channelRead方法來獲取入站數據
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 檢測是否是byteBuf對象格式數據
if (msg instanceof ByteBuf) {
// 實例化字節解碼成功輸出集合 即List<Object> out
CodecOutputList out = CodecOutputList.newInstance();
try {
// 獲取到的請求的數據
ByteBuf data = (ByteBuf) msg;
// 如果緩衝數據區為空則代表是首次觸發read方法
first = cumulation == null;
if (first) {
// 如果是第一次read則當前msg數據為緩衝數據
cumulation = data;
} else {
// 如果不是則觸發累加,將緩衝區的舊數據和新獲取到的數據通過 expandCumulation 方法累加在一起存入緩衝區cumulation
// cumulator 累加類,將緩衝池中數據和新數據進行組合在一起
// private Cumulator cumulator = MERGE_CUMULATOR;
cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
}
// 將緩衝區數據cumulation進行解碼
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Throwable t) {
throw new DecoderException(t);
} finally {
// 在解碼完畢后釋放引用和清空全局字節緩衝區
if (cumulation != null && !cumulation.isReadable()) {
numReads = 0;
cumulation.release();
cumulation = null;
// discardAfterReads為netty中設置的讀取多少次后開始丟棄字節 默認值16
// 可通過setDiscardAfterReads(int n)來設置值不設置默認16次
} 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
// 讀取次數重置為0
numReads = 0;
// 重置讀寫指針或丟棄部分已讀取的字節
discardSomeReadBytes();
}
// out為解碼成功的傳遞給下一個handler
int size = out.size();
decodeWasNull = !out.insertSinceRecycled();
// 結束當前read傳遞到下個ChannelHandler
fireChannelRead(ctx, out, size);
// 回收響應集合 將insertSinceRecycled設置為false;
// insertSinceRecycled用於channelReadComplete判斷使用
out.recycle();
}
} else {
// 不是的話直接fire傳遞給下一個handler
ctx.fireChannelRead(msg);
}
}
2. 初始化字節緩衝區計算器: Cumulator主要用於全局字節緩衝區和新讀取的字節緩衝區組合在一起擴容
public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
/**
* alloc ChannelHandlerContext分配的字節緩衝區
* cumulation 當前ByteToMessageDecoder類全局的字節緩衝區
* in 入站的字節緩衝區
**/
@Override
public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
final ByteBuf buffer;
// 如果全局ByteBuf寫入的字節+當前入站的字節數據大於全局緩衝區最大的容量或者全局緩衝區的引用數大於1個或全局緩衝區只讀
if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes()
|| cumulation.refCnt() > 1 || cumulation.isReadOnly()) {
// Expand cumulation (by replace it) when either there is not more room in the buffer
// or if the refCnt is greater then 1 which may happen when the user use slice().retain() or
// duplicate().retain() or if its read-only.
//
// See:
// - https://github.com/netty/netty/issues/2327
// - https://github.com/netty/netty/issues/1764
// 進行擴展全局字節緩衝區(容量大小 = 新數據追加到舊數據末尾組成新的全局字節緩衝區)
buffer = expandCumulation(alloc, cumulation, in.readableBytes());
} else {
buffer = cumulation;
}
// 將新數據寫入緩衝區
buffer.writeBytes(in);
// 釋放當前的字節緩衝區的引用
in.release();
return buffer;
}
};
/**
* alloc 字節緩衝區操作類
* cumulation 全局累加字節緩衝區
* readable 讀取到的字節數長度
*/
// 字節緩衝區擴容方法
static ByteBuf expandCumulation(ByteBufAllocator alloc, ByteBuf cumulation, int readable) {
// 舊數據
ByteBuf oldCumulation = cumulation;
// 通過ByteBufAllocator將緩衝區擴大到oldCumulation + readable大小
cumulation = alloc.buffer(oldCumulation.readableBytes() + readable);
// 將舊數據重新寫入到新的字節緩衝區
cumulation.writeBytes(oldCumulation);
// 舊字節緩衝區引用-1
oldCumulation.release();
return cumulation;
}
3. ByteBuf釋放當前字節緩衝區的引用: 通過調用ReferenceCounted接口中的release方法來釋放
@Override
public boolean release() {
return release0(1);
}
@Override
public boolean release(int decrement) {
return release0(checkPositive(decrement, "decrement"));
}
/**
* decrement 減量
*/
private boolean release0(int decrement) {
for (;;) {
int refCnt = this.refCnt;
// 當前引用小於減量
if (refCnt < decrement) {
throw new IllegalReferenceCountException(refCnt, -decrement);
}
// 這裏就利用里線程併發中的知識CAS,線程安全的設置refCnt的值
if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {
// 如果減量和引用量相等
if (refCnt == decrement) {
// 全部釋放
deallocate();
return true;
}
return false;
}
}
}
4. 將全局字節緩衝區進行解碼
/**
* ctx ChannelHandler的上下文,用於傳輸數據與下一個handler來交互
* in 入站數據
* out 解析之後的出站集合 (此出站不是返回給客戶端的而是傳遞給下個handler的)
*/
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
// 如果入站數據還有沒解析的
while (in.isReadable()) {
// 解析成功的出站集合長度
int outSize = out.size();
// 如果大於0則說明解析成功的數據還沒被消費完,直接fire掉給通道中的後續handler繼續 消費
if (outSize > 0) {
fireChannelRead(ctx, out, outSize);
out.clear();
// Check if this handler was removed before continuing with decoding.
// 在這個handler刪除之前檢查是否還在繼續解碼
// If it was removed, it is not safe to continue to operate on the buffer.
// 如果移除了,它繼續操作緩衝區是不安全的
//
// See:
// - https://github.com/netty/netty/issues/4635
if (ctx.isRemoved()) {
break;
}
outSize = 0;
}
// 入站數據字節長度
int oldInputLength = in.readableBytes();
// 開始解碼數據
decodeRemovalReentryProtection(ctx, in, out);
// Check if this handler was removed before continuing the loop.
//
// If it was removed, it is not safe to continue to operate on the buffer.
//
// See https://github.com/netty/netty/issues/1664
if (ctx.isRemoved()) {
break;
}
// 解析完畢跳出循環
if (outSize == out.size()) {
if (oldInputLength == in.readableBytes()) {
break;
} else {
continue;
}
}
if (oldInputLength == in.readableBytes()) {
throw new DecoderException(
StringUtil.simpleClassName(getClass()) +
".decode() did not read anything but decoded a message.");
}
if (isSingleDecode()) {
break;
}
}
} catch (DecoderException e) {
throw e;
} catch (Throwable cause) {
throw new DecoderException(cause);
}
}
final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 設置解碼狀態為正在解碼 STATE_INIT = 0; STATE_CALLING_CHILD_DECODE = 1; STATE_HANDLER_REMOVED_PENDING = 2; 分別為初始化; 解碼; 解碼完畢移除
decodeState = STATE_CALLING_CHILD_DECODE;
try {
// 具體的解碼邏輯(netty提供的解碼器或自定義解碼器中重寫的decode方法)
decode(ctx, in, out);
} finally {
// 此時decodeState為正在解碼中 值為1,返回false
boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
// 在設置為初始化等待解碼
decodeState = STATE_INIT;
// 解碼完成移除當前ChannelHandler標記為不處理
// 可以看看handlerRemoved源碼。如果緩衝區還有數據直接傳遞給下一個handler
if (removePending) {
handlerRemoved(ctx);
}
}
}
5. 執行channelReadComplete
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// 讀取次數重置
numReads = 0;
// 重置讀寫index
discardSomeReadBytes();
// 在channelRead meth中定義賦值 decodeWasNull = !out.insertSinceRecycled();
// out指的是解碼集合List<Object> out; 咱們可以點進
if (decodeWasNull) {
decodeWasNull = false;
if (!ctx.channel().config().isAutoRead()) {
ctx.read();
}
}
// fire掉readComplete傳遞到下一個handler的readComplete
ctx.fireChannelReadComplete();
}
/**
* 然後我們可以搜索下insertSinceRecucled在什麼地方被賦值了
* Returns {@code true} if any elements where added or set. This will be reset once {@link #recycle()} was called.
*/
boolean insertSinceRecycled() {
return insertSinceRecycled;
}
// 搜索下insert的調用我們可以看到是CodecOutputList類即為channelRead中的out集合,眾所周知在 decode完之後,解碼數據就會被調用add方法,此時insertSinceRecycled被設置為true
private void insert(int index, Object element) {
array[index] = element;
insertSinceRecycled = true;
}
/**
* 清空回收數組內部的所有元素和存儲空間
* Recycle the array which will clear it and null out all entries in the internal storage.
*/
// 搜索recycle的調用我么可以知道在channelRead的finally邏輯中 調用了out.recycle();此時 insertSinceRecycled被設置為false
void recycle() {
for (int i = 0 ; i < size; i ++) {
array[i] = null;
}
clear();
insertSinceRecycled = false;
handle.recycle(this);
}
至此ByteToMessageDecoder解碼類應該差不多比較清晰了!!!
MessageToByteEncoder: 編碼超類,將消息轉成字節進行編碼發出
何謂編碼,就是將發送數據轉化為客戶端和服務端約束好的數據結構和格式進行傳輸,我們可以在編碼過程中將消息體body的長度和一些頭部信息有序的設置到ByteBuf字節緩衝區中;方便解碼方靈活的運用來判斷(是否完整的包等)和處理業務;解碼是繼承入站數據,反之編碼應該繼承出站的數據;接下來我們看看編碼類是怎麼進行編碼的;
MessageToByteEncoder類圖如下
源碼分析
既然是繼承出站類,我們直接看看write方法是怎麼樣的
/**
* 通過write方法獲取到出站的數據即要發送出去的數據
* ctx channelHandler上下文
* msg 發送的數據 Object可以通過繼承類指定的泛型來指定
* promise channelPromise異步監聽,類似ChannelFuture,只不過promise可以設置監聽的結果,future只能通過獲取監聽的成功失敗結果;可以去了解下promise和future的區別
*/
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf buf = null;
try {
// 檢測發送數據的類型 通過TypeParameterMatcher類型匹配器
if (acceptOutboundMessage(msg)) {
@SuppressWarnings("unchecked")
I cast = (I) msg;
// 分配字節緩衝區 preferDirect默認為true
buf = allocateBuffer(ctx, cast, preferDirect);
try {
// 進行編碼
encode(ctx, cast, buf);
} finally {
// 完成編碼后釋放對象的引用
ReferenceCountUtil.release(cast);
}
// 如果緩衝區有數據則通過ctx發送出去,promise可以監聽數據傳輸並設置是否完成
if (buf.isReadable()) {
ctx.write(buf, promise);
} else {
// 如果沒有數據則釋放字節緩衝區的引用併發送一個empty的空包
buf.release();
ctx.write(Unpooled.EMPTY_BUFFER, promise);
}
buf = null;
} else {
// 非TypeParameterMatcher類型匹配器匹配的類型直接發送出去
ctx.write(msg, promise);
}
} catch (EncoderException e) {
throw e;
} catch (Throwable e) {
throw new EncoderException(e);
} finally {
if (buf != null) {
buf.release();
}
}
}
// 初始化設置preferDirect為true
protected MessageToByteEncoder() {
this(true);
}
protected MessageToByteEncoder(boolean preferDirect) {
matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
this.preferDirect = preferDirect;
}
編碼: 重寫encode方法,根據實際業務來進行數據編碼
// 此處就是我們需要重寫的編碼方法了,我們和根據約束好的或者自己定義好想要的數據格式發送給對方
// 下面是我自己寫的demo的編碼方法;頭部設置好body的長度,服務端可以根據長度來判斷是否是完整的包,僅僅自學寫的簡單的demo非正常線上運營項目的邏輯
public class MyClientEncode extends MessageToByteEncoder<String> {
@Override
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
if (null != msg) {
byte[] request = msg.getBytes(Charset.forName("UTF-8"));
out.writeInt(request.length);
out.writeBytes(request);
}
}
}
編碼類相對要簡單很多,因為只需要將發送的數據序列化,按照一定的格式進行發送數據!!!
項目實戰
項目主要簡單的實現下自定義編解碼器的運用及LengthFieldBasedFrameDecoder的使用
項目結構如下
│ hetangyuese-netty-06.iml
│ pom.xml
│
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─hetangyuese
│ │ │ └─netty
│ │ │ ├─client
│ │ │ │ MyClient06.java
│ │ │ │ MyClientChannelInitializer.java
│ │ │ │ MyClientDecoder.java
│ │ │ │ MyClientEncode.java
│ │ │ │ MyClientHandler.java
│ │ │ │ MyMessage.java
│ │ │ │
│ │ │ └─server
│ │ │ MyChannelInitializer.java
│ │ │ MyServer06.java
│ │ │ MyServerDecoder.java
│ │ │ MyServerDecoderLength.java
│ │ │ MyServerEncoder.java
│ │ │ MyServerHandler.java
│ │ │
│ │ └─resources
│ └─test
│ └─java
服務端
Serverhandler: 只是簡單的將解碼的內容輸出
public class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客戶端連接成功 time: " + new Date().toLocaleString());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客戶端斷開連接 time: " + new Date().toLocaleString());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String) msg;
System.out.println("content:" + body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 出現異常關閉通道
cause.printStackTrace();
ctx.close();
}
}解碼器
public class MyServerDecoder extends ByteToMessageDecoder {
// 此處我頭部只塞了長度字段佔4個字節,別問為啥我知道,這是要客戶端和服務端約束好的
private static int min_head_length = 4;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 解碼的字節長度
int size = in.readableBytes();
if(size < min_head_length) {
System.out.println("解析的數據長度小於頭部長度字段的長度");
return ;
}
// 讀取的時候指針已經移位到長度字段的尾端
int length = in.readInt();
if (size < length) {
System.out.println("解析的數據長度與長度不符合");
return ;
}
// 上面已經讀取到了長度字段,後面的長度就是body
ByteBuf decoderArr = in.readBytes(length);
byte[] request = new byte[decoderArr.readableBytes()];
// 將數據寫入空數組
decoderArr.readBytes(request);
String body = new String(request, Charset.forName("UTF-8"));
out.add(body);
}
}將解碼器加入到channelHandler中:記得加到業務handler的前面否則無效
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
// .addLast(new MyServerDecoderLength(10240, 0, 4, 0, 0))
// .addLast(new LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0))
.addLast(new MyServerDecoder())
.addLast(new MyServerHandler())
;
}
}客戶端
ClientHandler
public class MyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("與服務端連接成功");
for (int i = 0; i<10; i++) {
ctx.writeAndFlush("hhhhh" + i);
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("與服務端斷開連接");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("收到服務端消息:" +msg+ " time: " + new Date().toLocaleString());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}編碼器
public class MyClientEncode extends MessageToByteEncoder<String> {
@Override
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
if (null != msg) {
byte[] request = msg.getBytes(Charset.forName("UTF-8"));
out.writeInt(request.length);
out.writeBytes(request);
}
}
}將編碼器加到ClientHandler的前面
public class MyClientChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new MyClientDecoder())
.addLast(new MyClientEncode())
.addLast(new MyClientHandler())
;
}
}服務端運行結果
MyServer06 is start ...................
客戶端連接成功 time: 2019-11-19 16:35:47
content:hhhhh0
content:hhhhh1
content:hhhhh2
content:hhhhh3
content:hhhhh4
content:hhhhh5
content:hhhhh6
content:hhhhh7
content:hhhhh8
content:hhhhh9如果不用自定義的解碼器怎麼獲取到body內容呢
將自定義編碼器換成LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0)
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
// .addLast(new MyServerDecoderLength(10240, 0, 4, 0, 0))
.addLast(new LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0))
// .addLast(new MyServerDecoder())
.addLast(new MyServerHandler())
;
}
}
// 怕忘記的各個參數的含義在這在說明一次,自己不斷的修改每個值觀察結果就可以更加深刻的理解
/**
* maxFrameLength:消息體的最大長度,好像默認最大值為1024*1024
* lengthFieldOffset 長度字段所在字節數組的下標 (我這是第一個write的所以下標是0)
* lengthFieldLength 長度字段的字節長度(int類型佔4個字節)
* lengthAdjustment 長度字段補償的數值 (lengthAdjustment = 數據包長度 - lengthFieldOffset - lengthFieldLength - 長度域的值),解析需要減去對應的數值
* initialBytesToStrip 是否去掉長度字段(0不去除,對應長度域字節長度)
*/
public LengthFieldBasedFrameDecoder(
int maxFrameLength,
int lengthFieldOffset, int lengthFieldLength,
int lengthAdjustment, int initialBytesToStrip)結果: 前都帶上了長度
MyServer06 is start ...................
客戶端連接成功 time: 2019-11-19 17:53:42
收到客戶端發來的消息: hhhhh0, time: 2019-11-19 17:53:42
收到客戶端發來的消息: hhhhh1, time: 2019-11-19 17:53:42
收到客戶端發來的消息: hhhhh2, time: 2019-11-19 17:53:42
收到客戶端發來的消息: hhhhh3, time: 2019-11-19 17:53:42
收到客戶端發來的消息: hhhhh4, time: 2019-11-19 17:53:42
收到客戶端發來的消息: hhhhh5, time: 2019-11-19 17:53:42
收到客戶端發來的消息: hhhhh6, time: 2019-11-19 17:53:42
收到客戶端發來的消息: hhhhh7, time: 2019-11-19 17:53:42
收到客戶端發來的消息: hhhhh8, time: 2019-11-19 17:53:42
收到客戶端發來的消息: hhhhh9, time: 2019-11-19 17:53:42如果我們在客戶端的長度域中做手腳 LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0)
舊: out.writeInt(request.length);
新: out.writeInt(request.length + 1);// 看結果就不正常,0後面多了一個0;但是不知道為啥只解碼了一次??? 求解答
MyServer06 is start ...................
客戶端連接成功 time: 2019-11-19 17:56:55
收到客戶端發來的消息: hhhhh0 , time: 2019-11-19 17:56:55
// 正確修改為 LengthFieldBasedFrameDecoder(10240, 0, 4, -1, 0)
// 結果:
MyServer06 is start ...................
客戶端連接成功 time: 2019-11-19 18:02:18
收到客戶端發來的消息: hhhhh0, time: 2019-11-19 18:02:18
收到客戶端發來的消息: hhhhh1, time: 2019-11-19 18:02:18
收到客戶端發來的消息: hhhhh2, time: 2019-11-19 18:02:18
收到客戶端發來的消息: hhhhh3, time: 2019-11-19 18:02:18
收到客戶端發來的消息: hhhhh4, time: 2019-11-19 18:02:18
收到客戶端發來的消息: hhhhh5, time: 2019-11-19 18:02:18
收到客戶端發來的消息: hhhhh6, time: 2019-11-19 18:02:18
收到客戶端發來的消息: hhhhh7, time: 2019-11-19 18:02:18
收到客戶端發來的消息: hhhhh8, time: 2019-11-19 18:02:18
收到客戶端發來的消息: hhhhh9, time: 2019-11-19 18:02:18捨棄長度域 :LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 4)
// 結果
MyServer06 is start ...................
客戶端連接成功 time: 2019-11-19 18:03:44
收到客戶端發來的消息:hhhhh0, time: 2019-11-19 18:03:44
收到客戶端發來的消息:hhhhh1, time: 2019-11-19 18:03:44
收到客戶端發來的消息:hhhhh2, time: 2019-11-19 18:03:44
收到客戶端發來的消息:hhhhh3, time: 2019-11-19 18:03:44
收到客戶端發來的消息:hhhhh4, time: 2019-11-19 18:03:44
收到客戶端發來的消息:hhhhh5, time: 2019-11-19 18:03:44
收到客戶端發來的消息:hhhhh6, time: 2019-11-19 18:03:44
收到客戶端發來的消息:hhhhh7, time: 2019-11-19 18:03:44
收到客戶端發來的消息:hhhhh8, time: 2019-11-19 18:03:44
收到客戶端發來的消息:hhhhh9, time: 2019-11-19 18:03:44分析源碼示例中的 lengthAdjustment = 消息字節長度 - lengthFieldOffset-lengthFieldLength-長度域中的值
源碼中的示例
* <pre>
* lengthFieldOffset = 0
* lengthFieldLength = 2
* <b>lengthAdjustment</b> = <b>-2</b> (= the length of the Length field)
* initialBytesToStrip = 0
*
* BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
* +--------+----------------+ +--------+----------------+
* | Length | Actual Content |----->| Length | Actual Content |
* | 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" |
* +--------+----------------+ +--------+----------------+
* </pre>長度域中0x000E為16進制,轉換成10進制是14,說明消息體長度為14;根據公式:14-0-2-14 = -2
* <pre>
* lengthFieldOffset = 0
* lengthFieldLength = 3
* <b>lengthAdjustment</b> = <b>2</b> (= the length of Header 1)
* initialBytesToStrip = 0
*
* BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
* +----------+----------+----------------+ +----------+----------+----------------+
* | Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content |
* | 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" |
* +----------+----------+----------------+ +----------+----------+----------------+
* </pre>從上的例子可以知道;lengthAdjustment(2) = 17- 12(00000C)-lengthFieldOffset(0) - lengthFieldLength(3);
.......等等
【其他文章推薦】
※帶您來了解什麼是 USB CONNECTOR ?
※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益
※試算大陸海運運費!
Orignal From: Netty學習篇⑤--編、解碼源碼分析
留言
張貼留言