在上一篇中介紹了基于netty4.x搭建一款靈活、穩(wěn)健的TCP數(shù)據(jù)傳輸服務(wù)器圆仔,并處理了TCP通信中可能發(fā)生的的粘包缘挽、拆包問題(實際上是netty幫我們解決了)。能夠在不改動解碼器源碼的前提下恬汁,通過Class.forName的作用闸翅,在反序列化的時候動態(tài)傳入Class再芋,實現(xiàn)任意Object的網(wǎng)絡(luò)傳輸+靈活解碼。但是處理不了集合類:Map坚冀、List等济赎。
1.分析問題
回顧昨天的數(shù)據(jù)交互協(xié)議:
一個字節(jié)作為Header+4個字節(jié)作為長度len+len個字節(jié)的實際內(nèi)容+一個字節(jié)的tail結(jié)束
協(xié)議的實體類是這樣的:
public class TcpProtocol {
private byte header=0x58;
private int len;
private byte [] data;
private byte tail=0x63;
}
在解碼的時候反序列化了兩次:
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);//序列化成DTObject 讀取FullClassName
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()));//獲取到FullClassName后才成功反序列成真實要獲取的對象
logger.info("收到實體對象:"+object);
}
}
}catch (Exception e){
logger.info("對象反序列化出現(xiàn)問題:"+e);
}
}
由于沒有考慮到List、Map的情況记某,因此這部分缺少判斷類型的信息司训,解決辦法有兩種:1.是在協(xié)議中添加數(shù)據(jù)類型信息。2.是在DTObject中添加類型描述的字段液南。這里選擇將數(shù)據(jù)類型放協(xié)議中去的方式壳猜。
2.方案一
將包含object類型的信息(map、list滑凉、普通object)添加到協(xié)議中后:
- 新的協(xié)議類中新增一個type字段:
/**
*type 0x51 0x52 0x53
* mean: object list map
* */
public class TcpProtocol_2_0 {
private byte header=0x58;
private byte type;
private int len;
private byte [] data;
private byte tail=0x63;
}
- 編碼器也對應(yīng)新增一個字節(jié)的內(nèi)容:
public class EncoderHandler_2_0 extends MessageToByteEncoder {
private Logger logger = Logger.getLogger(this.getClass());
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
if (msg instanceof TcpProtocol_2_0){
TcpProtocol_2_0 protocol = (TcpProtocol_2_0) msg;
out.writeByte(protocol.getHeader());
out.writeByte(protocol.getType());//新增Type
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_2_0.class);
}
}
}
- 解碼器解析順序變成Header-->Type-->len--->data--->tail:
public class DecoderHandler_2_0 extends ByteToMessageDecoder {
//最小的數(shù)據(jù)長度:開頭標準位1字節(jié)
private static int MIN_DATA_LEN=6+1+1+1;
//數(shù)據(jù)解碼協(xié)議的開始標志
private static byte PROTOCOL_HEADER=0x58;
//數(shù)據(jù)解碼協(xié)議的結(jié)束標志
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ù)……");
//標記讀操作的指針
in.markReaderIndex();
byte header=in.readByte();
if (header==PROTOCOL_HEADER){
logger.debug("數(shù)據(jù)開頭格式正確");
//讀取class類型
byte type=in.readByte();
int dataLen=in.readInt();
if (dataLen<in.readableBytes()){
byte [] data=new byte[dataLen];
in.readBytes(data);
byte tail=in.readByte();
try {
if (tail==PROTOCOL_TAIL){
ObjectMapper objectMapper = ByteUtils.InstanceObjectMapper();
DTObject dtObject=objectMapper.readValue(data,DTObject.class);
Class<?> Type = Class.forName(dtObject.getClassName());
logger.debug("數(shù)據(jù)解碼成功");
logger.debug("開始封裝數(shù)據(jù)……");
if (type==ProtocolUtils.OBJ_TYPE){
Object o = objectMapper.readValue(dtObject.getObject(), Type);
out.add(o);
}else if (type==ProtocolUtils.MAP_TYPE){
JavaType javaType= TypeFactory.defaultInstance().constructMapType(Map.class,String.class,Type);
Object o = objectMapper.readValue(dtObject.getObject(), javaType);
out.add(o);
}else if (type==ProtocolUtils.LIST_TYPE){
JavaType javaType=TypeFactory.defaultInstance().constructCollectionType(List.class,Type);
Object o = objectMapper.readValue(dtObject.getObject(), javaType);
out.add(o);
}
//如果out有值统扳,且in仍然可讀,將繼續(xù)調(diào)用decode方法再次解碼in中的內(nèi)容畅姊,以此解決粘包問題
}else {
logger.debug(String.format("數(shù)據(jù)解碼協(xié)議結(jié)束標志位:%1$d [錯誤!]咒钟,期待的結(jié)束標志位是:%2$d",tail,PROTOCOL_TAIL));
return;
}
}catch (ClassNotFoundException e){
logger.error(String.format("反序列化對象的類找不到,注意包名匹配! "));
return;
}catch (Exception e){
logger.error(e);
return;
}
}else{
logger.debug(String.format("數(shù)據(jù)長度不夠涡匀,數(shù)據(jù)協(xié)議len長度為:%1$d,數(shù)據(jù)包實際可讀內(nèi)容為:%2$d正在等待處理拆包……",dataLen,in.readableBytes()));
in.resetReaderIndex();
/*
**結(jié)束解碼盯腌,這種情況說明數(shù)據(jù)沒有到齊,在父類ByteToMessageDecoder的callDecode中會對out和in進行判斷
* 如果in里面還有可讀內(nèi)容即in.isReadable位true,cumulation中的內(nèi)容會進行保留陨瘩,腕够,直到下一次數(shù)據(jù)到來,將兩幀的數(shù)據(jù)合并起來舌劳,再解碼帚湘。
* 以此解決拆包問題
*/
return;
}
}else {
logger.debug("開頭不對,可能不是期待的客服端發(fā)送的數(shù)甚淡,將自動略過這一個字節(jié)");
}
}else {
logger.debug("數(shù)據(jù)長度不符合要求大诸,期待最小長度是:"+MIN_DATA_LEN+" 字節(jié)");
return;
}
}
}
這里利用到了jackSon的JavaType來描述泛型,去反序列化Map和List類型的實體贯卦,也是反序列化了兩次资柔,第一次反序列化成DTObject獲取全類名,第二次根據(jù)全類名和類型去反序列化真實的實體類撵割。
- 最后是在
EchoHandler
的channelActive
方法中去測試發(fā)生數(shù)據(jù):
public class EchoHandler_2_0 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(24);
HashMap<String, User> map = new HashMap<>();
map.put("數(shù)據(jù)一",user);
map.put("數(shù)據(jù)2",user);
map.put("數(shù)據(jù)3",user);
ArrayList<User> list = new ArrayList<>();
list.add(user);
list.add(user);
list.add(user);
list.add(user);
TcpProtocol_2_0 tcpProtocol= ProtocolUtils_2_0.prtclInstance(list,user.getClass().getName());
ctx.write(tcpProtocol);
ctx.flush();
}
}
ProtocolUtils工具類是用來快速獲取tcpProtocol對象的贿堰,具體代碼:
public class ProtocolUtils_2_0 {
public final static byte OBJ_TYPE=0x51;
public final static byte LIST_TYPE=0x52;
public final static byte MAP_TYPE=0x53;
/**
* 創(chuàng)建集合類list map對象
* */
public static TcpProtocol_2_0 prtclInstance(Object o, String className){
TcpProtocol_2_0 protocol = new TcpProtocol_2_0();
if (o instanceof List){
protocol.setType(LIST_TYPE);
}else if (o instanceof Map){
protocol.setType(MAP_TYPE);
}else if (o instanceof Object){
protocol.setType(OBJ_TYPE);
}
initProtocol(o, className, protocol);
return protocol;
}
/***
*
* 創(chuàng)建單一的對象
*/
public static TcpProtocol_2_0 prtclInstance(Object o){
TcpProtocol_2_0 protocol = new TcpProtocol_2_0();
protocol.setType(OBJ_TYPE);
initProtocol(o, o.getClass().getName(), protocol);
return protocol;
}
private static void initProtocol(Object o, String className, TcpProtocol_2_0 protocol) {
try {
DTObject dtObject = new DTObject();
byte [] objectBytes= ByteUtils.InstanceObjectMapper().writeValueAsBytes(o);
dtObject.setObject(objectBytes);
dtObject.setClassName(className);
byte[] bytes = ByteUtils.InstanceObjectMapper().writeValueAsBytes(dtObject);
protocol.setLen(bytes.length);
protocol.setData(bytes);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
最后是運行結(jié)果測試:
- 發(fā)送List對象時:
2019-01-15 18:21:10 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 開始解碼數(shù)據(jù)……
2019-01-15 18:21:10 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 數(shù)據(jù)開頭格式正確
2019-01-15 18:21:10 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 數(shù)據(jù)解碼成功
2019-01-15 18:21:10 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 開始封裝數(shù)據(jù)……
2019-01-15 18:21:10 INFO [org.wisdom.server.business.BusinessHandler_2_0] 這是一個List:[User{name='冉鵬峰', age=24, UID='2f7600e2-4714-4625-ad24-99947f182b76', birthday=Tue Jan 15 06:21:00 CST 2019}, User{name='冉鵬峰', age=24, UID='2f7600e2-4714-4625-ad24-99947f182b76', birthday=Tue Jan 15 06:21:00 CST 2019}, User{name='冉鵬峰', age=24, UID='2f7600e2-4714-4625-ad24-99947f182b76', birthday=Tue Jan 15 06:21:00 CST 2019}, User{name='冉鵬峰', age=24, UID='2f7600e2-4714-4625-ad24-99947f182b76', birthday=Tue Jan 15 06:21:00 CST 2019}]
List中的User泛型也成功的解碼出來
- 發(fā)生Map對象時
2019-01-15 18:22:49 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 開始解碼數(shù)據(jù)……
2019-01-15 18:22:49 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 數(shù)據(jù)開頭格式正確
2019-01-15 18:22:49 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 數(shù)據(jù)解碼成功
2019-01-15 18:22:49 DEBUG [org.wisdom.server.decoder.DecoderHandler_2_0] 開始封裝數(shù)據(jù)……
2019-01-15 18:22:49 INFO [org.wisdom.server.business.BusinessHandler_2_0] 這是一個Map:{數(shù)據(jù)一=User{name='冉鵬峰', age=24, UID='b975ac02-ae29-4190-866a-bdf19d373924', birthday=Tue Jan 15 06:22:00 CST 2019}, 數(shù)據(jù)2=User{name='冉鵬峰', age=24, UID='b975ac02-ae29-4190-866a-bdf19d373924', birthday=Tue Jan 15 06:22:00 CST 2019}, 數(shù)據(jù)3=User{name='冉鵬峰', age=24, UID='b975ac02-ae29-4190-866a-bdf19d373924', birthday=Tue Jan 15 06:22:00 CST 2019}}
Map中的泛型User也被成功解碼并識別出來了
這里通過測試說明是能夠處理傳輸集合類的信息的,實體類當然不在話下啡彬。但是通過一套流程下來:在客戶端那里序列化了兩次羹与,在服務(wù)端反序列化了兩次故硅。序列化和反序列都是十分消耗性能的操作,按理說只序列化一次才是正常操作:對象實體--序列化---->字節(jié)數(shù)組 纵搁;解碼時候:字節(jié)數(shù)組----反序列化---->對象實體吃衅。下面是只序列化一次的方案2。
- 方案2
為了數(shù)據(jù)接收端能夠動態(tài)的反序列化對象腾誉,因此把實體對象的class信息也一并傳輸過去徘层,并將對象字節(jié)組和className放到DTObject
這個實體做為數(shù)據(jù)的二次載體。
public class DTObject {
private String className;
private byte[] object;
}
這樣導(dǎo)致發(fā)送端和接收端都會為了DTObject
而額外多做一次解析利职。如果目的是為了簡化協(xié)議結(jié)構(gòu)則用方案一比較合適惑灵,如果考慮性更多性能上的問題,下面這種方式可能會更好眼耀。
- 重新設(shè)計傳輸協(xié)議
分析:在傳入泛型對象時候英支,由于反編譯需要同時聲明泛型class和實體class,因此在協(xié)議中需要將這個泛型的類型type哮伟、實體的全類名className傳入到接收端干花。可以在兩邊約定一種鍵值對類型的類型參照表:
|泛型類型| 代替數(shù)字 |
|map | 0x51 |
|list | 0x52 |
|單實體 | 0x53 |
甚至將實體的類型也對應(yīng)的使用上面的方式:數(shù)字(key)楞黄,全類名(value)在兩端約定好池凄。但是由于實體多樣特效,可能需要將這些配置信息保存到一個激活的map中去鬼廓,去而避免復(fù)雜的if else判斷寫法肿仑。隨著實體類型的增加被激活的map的體積也要不斷增加。
所以碎税,簡單處理:直接將className也包含到傳輸?shù)臄?shù)據(jù)中去尤慰。最終協(xié)議如下:
對應(yīng)的
TcpProtocol_3_0
代碼:
public class TcpProtocol_3_0 {
private final byte header=0x58;
private byte type;
private byte classLen;
private int len;
private byte[] className;
private byte [] data;
private final byte tail=0x63;
}
將協(xié)議實體組裝工具ProtocolUtils
稍作修改:
public class ProtocolUtils {
public final static byte OBJ_TYPE=0x51;
public final static byte LIST_TYPE=0x52;
public final static byte MAP_TYPE=0x53;
/**
* 創(chuàng)建集合類list map對象
* */
public static TcpProtocol_3_0 prtclInstance(Object o, String className){
TcpProtocol_3_0 protocol = new TcpProtocol_3_0();
if (o instanceof List){
protocol.setType(LIST_TYPE);
}else if (o instanceof Map){
protocol.setType(MAP_TYPE);
}else if (o instanceof Object){
protocol.setType(OBJ_TYPE);
}
initProtocol(o, className, protocol);
return protocol;
}
/***
*
* 創(chuàng)建單一的對象
*/
public static TcpProtocol_3_0 prtclInstance(Object o){
TcpProtocol_3_0 protocol = new TcpProtocol_3_0();
protocol.setType(OBJ_TYPE);
initProtocol(o, o.getClass().getName(), protocol);
return protocol;
}
private static void initProtocol(Object o, String className, TcpProtocol_3_0 protocol) {
byte [] classBytes=className.getBytes();
try {
byte [] objectBytes= ByteUtils.InstanceObjectMapper().writeValueAsBytes(o);
protocol.setClassLen((byte) classBytes.length);
protocol.setLen(objectBytes.length);
protocol.setData(objectBytes);
protocol.setClassName(classBytes);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
- 編碼器
public class EncoderHandler_3_0 extends MessageToByteEncoder {
private Logger logger = Logger.getLogger(this.getClass());
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
if (msg instanceof TcpProtocol_3_0){
TcpProtocol_3_0 protocol = (TcpProtocol_3_0) msg;
out.writeByte(protocol.getHeader());
out.writeByte(protocol.getType());
out.writeByte(protocol.getClassLen());
out.writeInt(protocol.getLen());
out.writeBytes(protocol.getClassName());
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_3_0.class);
}
}
}
-
解碼器
解碼器的設(shè)計邏輯,仍然按照設(shè)計的協(xié)議來:1.解析并驗證協(xié)議開頭標志位
0x58
2.解析出泛型type
3.解析出類名長度len1和數(shù)據(jù)組長度len2
4.根據(jù)剩余可讀位數(shù)和len1+len2+1大小處理粘包/拆包
5.讀取出類名className
6.讀取實際的數(shù)據(jù)字節(jié)組data
7.解析并驗證結(jié)束標志位
8.根據(jù)泛型type和className去反編譯出data雷蹂,獲得傳輸?shù)膶嶋Hjava實體
解碼器代碼:
public class DecoderHandler_3_0 extends ByteToMessageDecoder {
//最小的數(shù)據(jù)長度:開頭標準位1字節(jié)
private static int MIN_DATA_LEN=6+1+1+1;
//數(shù)據(jù)解碼協(xié)議的開始標志
private static byte PROTOCOL_HEADER=0x58;
//數(shù)據(jù)解碼協(xié)議的結(jié)束標志
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ù)……");
//標記讀操作的指針
in.markReaderIndex();
byte header=in.readByte();
if (header==PROTOCOL_HEADER){
logger.debug("數(shù)據(jù)開頭格式正確");
//讀取字節(jié)數(shù)據(jù)的長度
byte type=in.readByte();
int typeLen=in.readByte()&255;
int dataLen=in.readInt();
if (typeLen+dataLen<in.readableBytes()){
byte [] fullClassName=new byte[typeLen];
byte [] data=new byte[dataLen];
in.readBytes(fullClassName);
in.readBytes(data);
byte tail=in.readByte();
try {
Class<?> Type = Class.forName(new String(fullClassName));
if (tail==PROTOCOL_TAIL){
logger.debug("數(shù)據(jù)解碼成功");
logger.debug("開始封裝數(shù)據(jù)……");
ObjectMapper objectMapper = ByteUtils.InstanceObjectMapper();
if (type==ProtocolUtils.OBJ_TYPE){
Object o = objectMapper.readValue(data, Type);
out.add(o);
}else if (type==ProtocolUtils.MAP_TYPE){
JavaType javaType= TypeFactory.defaultInstance().constructMapType(Map.class,String.class,Type);
Object o = objectMapper.readValue(data, javaType);
out.add(o);
}else if (type==ProtocolUtils.LIST_TYPE){
JavaType javaType=TypeFactory.defaultInstance().constructCollectionType(List.class,Type);
Object o = objectMapper.readValue(data, javaType);
out.add(o);
}
//如果out有值伟端,且in仍然可讀,將繼續(xù)調(diào)用decode方法再次解碼in中的內(nèi)容匪煌,以此解決粘包問題
}else {
logger.debug(String.format("數(shù)據(jù)解碼協(xié)議結(jié)束標志位:%1$d [錯誤!]责蝠,期待的結(jié)束標志位是:%2$d",tail,PROTOCOL_TAIL));
return;
}
}catch (ClassNotFoundException e){
logger.error(String.format("反序列化對象的類找不到,期待的全類名是:%1$s,注意包名匹配萎庭! ",fullClassName));
return;
}catch (Exception e){
logger.error(e);
return;
}
}else{
logger.debug(String.format("數(shù)據(jù)長度不夠霜医,數(shù)據(jù)協(xié)議len長度為:%1$d,數(shù)據(jù)包實際可讀內(nèi)容為:%2$d正在等待處理拆包……",dataLen+typeLen,in.readableBytes()));
in.resetReaderIndex();
/*
**結(jié)束解碼,這種情況說明數(shù)據(jù)沒有到齊驳规,在父類ByteToMessageDecoder的callDecode中會對out和in進行判斷
* 如果in里面還有可讀內(nèi)容即in.isReadable位true,cumulation中的內(nèi)容會進行保留肴敛,,直到下一次數(shù)據(jù)到來达舒,將兩幀的數(shù)據(jù)合并起來值朋,再解碼。
* 以此解決拆包問題
*/
return;
}
}else {
logger.debug("開頭不對巩搏,可能不是期待的客服端發(fā)送的數(shù)昨登,將自動略過這一個字節(jié)");
}
}else {
logger.debug("數(shù)據(jù)長度不符合要求,期待最小長度是:"+MIN_DATA_LEN+" 字節(jié)");
return;
}
}
}
- 業(yè)務(wù)處理類的channelRead
public class BusinessHandler_3_0 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 List){
logger.info("這是一個List:"+(List)msg);
}else if (msg instanceof Map){
logger.info("這是一個Map:"+(Map)msg);
}else{
logger.info("這是一個對象:"+msg.getClass().getName());
logger.info("這是一個對象:"+msg);
}
}
}
- 客戶端發(fā)送消息的處理器
public class EchoHandler_3_0 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(24);
Map<String,User> map=new HashMap<>();
map.put("數(shù)據(jù)一",user);
List<User> users=new ArrayList<>();
users.add(user);
TcpProtocol_3_0 protocol = ProtocolUtils.prtclInstance(map,user.getClass().getName());
//傳map
ctx.write(protocol);//由于設(shè)置了編碼器贯底,這里直接傳入自定義的對象
ctx.flush();
//傳list
ctx.write(ProtocolUtils.prtclInstance(users,user.getClass().getName()));
ctx.flush();
//傳單一實體
ctx.write(ProtocolUtils.prtclInstance(user));
ctx.flush();
}
}
- 測試運行結(jié)果
客戶端運行情況:
2019-02-18 11:25:22 DEBUG [org.wisdom.server.encoder.EncoderHandler_3_0] 數(shù)據(jù)編碼成功:PooledUnsafeDirectByteBuf(ridx: 0, widx: 138, cap: 256)
2019-02-18 11:25:22 DEBUG [DEBUG] [id: 0x5b246c7d, L:/127.0.0.1:63155 - R:/127.0.0.1:8777] FLUSH
2019-02-18 11:25:22 DEBUG [org.wisdom.server.encoder.EncoderHandler_3_0] 數(shù)據(jù)編碼成功:PooledUnsafeDirectByteBuf(ridx: 0, widx: 126, cap: 256)
2019-02-18 11:25:22 DEBUG [DEBUG] [id: 0x5b246c7d, L:/127.0.0.1:63155 - R:/127.0.0.1:8777] FLUSH
2019-02-18 11:25:22 DEBUG [org.wisdom.server.encoder.EncoderHandler_3_0] 數(shù)據(jù)編碼成功:PooledUnsafeDirectByteBuf(ridx: 0, widx: 124, cap: 256)
2019-02-18 11:25:22 DEBUG [DEBUG] [id: 0x5b246c7d, L:/127.0.0.1:63155 - R:/127.0.0.1:8777] FLUSH
服務(wù)端接收到的運行情況:
2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 開始解碼數(shù)據(jù)……
2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 數(shù)據(jù)開頭格式正確
2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 數(shù)據(jù)解碼成功
2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 開始封裝數(shù)據(jù)……
2019-02-18 11:25:22 INFO [org.wisdom.server.business.BusinessHandler_3_0] 這是一個Map:{數(shù)據(jù)一=User{name='冉鵬峰', age=24, UID='3bb87b9f-a89c-4968-beec-a7b2a3b912b4', birthday=Mon Feb 18 11:25:00 CST 2019}}
2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 開始解碼數(shù)據(jù)……
2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 數(shù)據(jù)開頭格式正確
2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 數(shù)據(jù)解碼成功
2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 開始封裝數(shù)據(jù)……
2019-02-18 11:25:22 INFO [org.wisdom.server.business.BusinessHandler_3_0] 這是一個List:[User{name='冉鵬峰', age=24, UID='3bb87b9f-a89c-4968-beec-a7b2a3b912b4', birthday=Mon Feb 18 11:25:00 CST 2019}]
2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 開始解碼數(shù)據(jù)……
2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 數(shù)據(jù)開頭格式正確
2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 數(shù)據(jù)解碼成功
2019-02-18 11:25:22 DEBUG [org.wisdom.server.decoder.DecoderHandler_3_0] 開始封裝數(shù)據(jù)……
2019-02-18 11:25:22 INFO [org.wisdom.server.business.BusinessHandler_3_0] 這是一個對象:pojo.User
2019-02-18 11:25:22 INFO [org.wisdom.server.business.BusinessHandler_3_0] 這是一個對象:User{name='冉鵬峰', age=24, UID='3bb87b9f-a89c-4968-beec-a7b2a3b912b4', birthday=Mon Feb 18 11:25:00 CST 2019}
運行結(jié)果正常丰辣,可以混合傳入單實體、泛型