netty 自定義協議

netty 自定義協議

netty 是什麼呢?

相信很多人都被人問過這個問題。如果快速準確的回覆這個問題呢?網絡編程框架,netty可以讓你快速和簡單的開發出一個高性能的網絡應用。netty是一個網絡編程框架。那netty又有什麼框框呢?主要有二個框。

框1:客戶和服務的啓動

一切通訊都有收與發,所有的服務端和客戶端都是這樣的姿勢啓動。具體的參數可以看文檔。

服務端

 public void bind() throws Exception {
        // 配置服務端的NIO線程組
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap b = new ServerBootstrap();

        
        //需要兩個線程組
        b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
            .option(ChannelOption.SO_BACKLOG, 1024)
            .childHandler(new ServerInit());

        // 綁定端口,同步等待成功
        b.bind(NettyConstant.REMOTE_PORT).sync();
            LOG.info("Netty server start : "
                + (NettyConstant.REMOTE_IP + " : " + NettyConstant.REMOTE_PORT));
    }

客戶端

          Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ClientInit());
            // 發起異步連接操作
            ChannelFuture future = b.connect(
                    new InetSocketAddress(host, port)).sync();
            channel = future.sync().channel();
            
            future.channel().closeFuture().sync();

框2:處理器ChannelHandler

netty將所有接收到或發出去的數據都交給處理器處理,hannelInboundHandler入站處理器,ChannelOutboundHandler出站處理器。入站既接收數據時觸發,出站是發送時觸發。包括編碼器和解碼器分別實現的也是這兩個接口。所以我們要爲服務端或客戶端擴展功能的話!只要對應創建 對個類去實現這兩個接口添加到通道上就行了。netty作爲一個框架,當然也幫我們實現了很多經常的用的處理器了,如:StringDecoder(字串解碼器),StringEncoder(字串編碼器),LengthFieldPrepender(給數據添加長度解決黏包半包問題),ReadTimeoutHandler(超時檢測)

服務端出入站處理器添加


**
 * @author hdk
 * 類說明:服務端入站處理器初始化類
 */
public class ServerInit extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        /*Netty提供的日誌打印Handler,可以展示發送接收出去的字節*/
        //ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
        /*剝離接收到的消息的長度字段,拿到實際的消息報文的字節數組*/
        ch.pipeline().addLast("frameDecoder",
                new LengthFieldBasedFrameDecoder(65535,
                        0,2,0,
                        2));
        /*給發送出去的消息增加長度字段*/
        ch.pipeline().addLast("frameEncoder",
                new LengthFieldPrepender(2));
        /*反序列化,將字節數組轉換爲消息實體*/
        ch.pipeline().addLast(new KryoDecoder());
        /*序列化,將消息實體轉換爲字節數組準備進行網絡傳輸*/
        ch.pipeline().addLast("MessageEncoder",
                new KryoEncoder());
        /*超時檢測*/
        ch.pipeline().addLast("readTimeoutHandler",
                new ReadTimeoutHandler(10));
        /*登錄應答*/
        ch.pipeline().addLast(new LoginAuthRespHandler());

        /*心跳應答*/
        ch.pipeline().addLast("HeartBeatHandler",
                new HeartBeatRespHandler());

        /*服務端業務處理*/
        ch.pipeline().addLast("ServerBusiHandler",
                new ServerBusiHandler());

    }
}

客戶端出入站處理器添加

/**
 * @author hdk
 * 類說明:客戶端Handler的初始化
 */
public class ClientInit extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        /*剝離接收到的消息的長度字段,拿到實際的消息報文的字節數組*/
        ch.pipeline().addLast("frameDecoder",
                new LengthFieldBasedFrameDecoder(65535,
                        0,2,0,
                        2));

        /*給發送出去的消息增加長度字段*/
        ch.pipeline().addLast("frameEncoder",
                new LengthFieldPrepender(2));

        /*反序列化,將字節數組轉換爲消息實體*/
        ch.pipeline().addLast(new KryoDecoder());
        /*序列化,將消息實體轉換爲字節數組準備進行網絡傳輸*/
        ch.pipeline().addLast("MessageEncoder",
                new KryoEncoder());

        /*超時檢測*/
        ch.pipeline().addLast("readTimeoutHandler",
                new ReadTimeoutHandler(10));

        /*發出登錄請求*/
        ch.pipeline().addLast("LoginAuthHandler",
                new LoginAuthReqHandler());

       /*發出心跳請求*/
        ch.pipeline().addLast("HeartBeatHandler",
                new HeartBeatReqHandler());
    }
}

處理器注意事項

入站:在入站實現方法中ChannelInboundHandler.channelRead必需release下一個處理器會接着觸發。釋放netty的ByteBuf給其他處理器讀取: ReferenceCountUtil.release(msg);如果不想下個處理器處理就調用ctx.fireChannelRead(msg);

出站:在入站實現方法中ChannelOutboundHandler.write同樣要調用ReferenceCountUtil.release(msg),在其他的處理器中纔可以讀取本處理器處理過的數據,如果

處理器的擴展編碼器與解碼器

在網絡編程中定義一種高效編碼規則是很重要的,節省帶寬提交傳輸效率。提高傳輸速度。所以netty封裝了兩種特殊的處理器叫:ByteToMessageDecoder解碼器和MessageToByteEncoder編碼器。使用方法也很簡單,繼承重寫一下decode和encode就可以了.。

解碼器 ByteToMessageDecoder

/**
 * @author hdk
 * 類說明:反序列化的Handler
 */
public class KryoDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in,
                          List<Object> out) throws Exception {
        Object obj = KryoSerializer.deserialize(in);
        //數據信息處理後添加目標編碼格式的數據到ByteBuf中就供後面的處理器處理。
        out.add(obj);
    }
}

編碼器 MessageToByteEncoder


/**
 * @author hdk
 * 類說明:序列化的Handler
 */
public class KryoEncoder  extends MessageToByteEncoder<MyMessage> {

    @Override
    protected void encode(ChannelHandlerContext ctx, MyMessage message,
                          ByteBuf out) throws Exception {
        KryoSerializer.serialize(message, out);
        //發送ByteBuf到目標IP
        ctx.flush();
    }
}

總結

netty爲什麼那麼流行,個人認爲有幾點:

  • 1事件編程讓代碼變得很簡潔。
  • 2提供很多協議如mqtt,http,websocket,smtp,redis等等,所有可以很快速的就可以開發出這些協議的服務端程序,不用自己去實現了。
  • 1563789405876

netty擴展-自定義協議

Netty協議棧消息定義包含兩部分:消息頭、消息體

1563791266642

名稱 類型 長度 描述
crcCode Int 32 Netty消息校驗碼
Length Int 32 整個消息長度
sessionID Long 64 會話ID
Type Byte 8 0:業務請求消息1:業務響應消息2:業務one way消息3握手請求消息4握手應答消息5:心跳請求消息6:心跳應答消息
Priority Byte 8 消息優先級:0~255
Attachment Map<String,Object> 變長 可選字段,由於推展消息頭
/**
 * @author hdk
 * 類說明:消息頭
 */
public final class MyHeader {

    private int crcCode = 0xabef0101;

    private int length;// 消息長度

    private long sessionID;// 會話ID

    private byte type;// 消息類型

    private byte priority;// 消息優先級

    private Map<String, Object> attachment = new HashMap<String, Object>(); // 附件

    public final int getCrcCode() {
        return crcCode;
    }

    public final void setCrcCode(int crcCode) {
        this.crcCode = crcCode;
    }

    public final int getLength() {
        return length;
    }

    public final void setLength(int length) {
        this.length = length;
    }

    public final long getSessionID() {
        return sessionID;
    }

    public final void setSessionID(long sessionID) {
        this.sessionID = sessionID;
    }

    public final byte getType() {
        return type;
    }

    public final void setType(byte type) {
        this.type = type;
    }

    public final byte getPriority() {
        return priority;
    }

    public final void setPriority(byte priority) {
        this.priority = priority;
    }

    public final Map<String, Object> getAttachment() {
        return attachment;
    }

    public final void setAttachment(Map<String, Object> attachment) {
        this.attachment = attachment;
    }

    @Override
    public String toString() {
        return "MyHeader [crcCode=" + crcCode + ", length=" + length
            + ", sessionID=" + sessionID + ", type=" + type + ", priority="
            + priority + ", attachment=" + attachment + "]";
    }

}
/**
 * @author hdk
 * 類說明:消息實體類
 */
public final class MyMessage {

    private MyHeader myHeader;

    private Object body;

    public final MyHeader getMyHeader() {
        return myHeader;
    }

    public final void setMyHeader(MyHeader myHeader) {
        this.myHeader = myHeader;
    }

    public final Object getBody() {
        return body;
    }

    public final void setBody(Object body) {
        this.body = body;
    }

    @Override
    public String toString() {
        return "MyMessage [myHeader=" + myHeader + "][body="+body+"]";
    }
}

源碼

https://github.com/huangdongkui/netty_project

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章