如何用JAVA實現(xiàn)一款高可用的TCP數(shù)據(jù)傳輸服務(wù)器(二)——【基于netty4.x】

上一篇中介紹了基于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ù)全類名和類型去反序列化真實的實體類撵割。

  • 最后是在EchoHandlerchannelActive方法中去測試發(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)則用方案一比較合適惑灵,如果考慮性更多性能上的問題,下面這種方式可能會更好眼耀。

  1. 重新設(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();
        }
    }
}
  1. 編碼器
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);
        }
    }
}
  1. 解碼器
    解碼器的設(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;
        }

    }
}
  1. 業(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);
        }
    }
}
  1. 客戶端發(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();
    }
}
  1. 測試運行結(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é)果正常丰辣,可以混合傳入單實體、泛型

代碼下載地址:https://github.com/Siwash/netty_TCP

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末禽捆,一起剝皮案震驚了整個濱河市笙什,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胚想,老刑警劉巖琐凭,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異浊服,居然都是意外死亡统屈,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門牙躺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來愁憔,“玉大人,你說我怎么就攤上這事孽拷《终疲” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵脓恕,是天一觀的道長膜宋。 經(jīng)常有香客問我,道長炼幔,這世上最難降的妖魔是什么激蹲? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮江掩,結(jié)果婚禮上学辱,老公的妹妹穿的比我還像新娘。我一直安慰自己环形,他們只是感情好策泣,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著抬吟,像睡著了一般萨咕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上火本,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天危队,我揣著相機與錄音聪建,去河邊找鬼。 笑死茫陆,一個胖子當著我的面吹牛金麸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播簿盅,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼挥下,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了桨醋?” 一聲冷哼從身側(cè)響起棚瘟,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喜最,沒想到半個月后偎蘸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡瞬内,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年禀苦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遂鹊。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡振乏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秉扑,到底是詐尸還是另有隱情慧邮,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布舟陆,位于F島的核電站误澳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秦躯。R本人自食惡果不足惜忆谓,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望踱承。 院中可真熱鬧倡缠,春花似錦、人聲如沸茎活。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽载荔。三九已至盾饮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丘损。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工普办, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人徘钥。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓衔蹲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吏饿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

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

  • 震驚蔬浙!這可能是我與底層最接近的一次編程體驗 1.netty能做什么 首先netty是一款高性能猪落、封裝性良好且靈活、...
    rpf_siwash閱讀 6,062評論 3 5
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,090評論 1 32
  • 一畴博、簡歷準備 1笨忌、個人技能 (1)自定義控件、UI設(shè)計俱病、常用動畫特效 自定義控件 ①為什么要自定義控件官疲? Andr...
    lucas777閱讀 5,190評論 2 54
  • 在一個方法內(nèi)部定義的變量都存儲在棧中,當這個函數(shù)運行結(jié)束后亮隙,其對應(yīng)的棧就會被回收途凫,此時,在其方法體中定義的變量將不...
    Y了個J閱讀 4,413評論 1 14
  • 每一次超能演說都讓我感動不已溢吻。每一個上臺pk的選手都讓我敬佩维费。從這些人的身上,我們不難發(fā)現(xiàn):其實每一個人的內(nèi)心都是...
    靜蕓思語閱讀 127評論 0 0