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等等,所有可以很快速的就可以開發出這些協議的服務端程序,不用自己去實現了。
netty擴展-自定義協議
Netty協議棧消息定義包含兩部分:消息頭、消息體
Header
名稱 | 類型 | 長度 | 描述 |
---|---|---|---|
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