前言
何為編解碼,通俗的來說,我們需要將一串文字資訊從A傳送到B並且將這段文字進行加工處理,如:A將資訊文字資訊編碼為2進位制資訊進行傳輸。B接受到的訊息是一串2進位制資訊,需要將其解碼為文字資訊才能正常進行處理。
上章我們介紹的Netty如何解決拆包和粘包問題,就是運用瞭解碼的這一功能。
java預設的序列化機制
使用Netty大多是java程式猿,我們基於一切都是物件的原則,經常會將物件進行網路傳輸,那麼對於序列化操作肯定大家都是非常熟悉的。
一個物件是不能直接進行網路I/O傳輸的,jdk預設是將物件轉換為可儲存的位元組陣列來進行網路操作。基於JDK預設的序列化機制可以避免操作底層的位元組陣列,從而提升開發效率。
jdk預設的序列化機制雖然能給程式猿帶來極大的方便,但是它也帶來了許多問題:
- 無法跨語言。
- 序列化後的碼流太大,會給網路傳輸帶來極大的開銷。
- 序列化的效能太低,對於高效能的網路架構是極其不友好的。
主流的編解碼框架
- Google的Protobuf。
- Facebok的Thrift。
- Jboss Marshalling
- MessagePack
這幾類編解碼框架都有各自的特點,有興趣的童鞋可以自己對其進行研究。
我們這裡主要對MessagePack進行講解。
MessagePack簡介
MessagePack是一個高效的二進位制序列化框架,它像JSON一樣支援不同的語言間的資料交換,並且它的效能更快,序列化之後的碼流也更小。
它的特點如下:
- 編解碼高效,效能高
- 序列化之後的碼流小,利於網路傳輸或儲存
- 支援跨語言
MessagePack Java Api的使用
- 首先導包
<!-- https://mvnrepository.com/artifact/org.msgpack/msgpack -->
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack</artifactId>
<version>0.6.12</version>
</dependency>
- 使用API進行編碼和解碼
List<String> nameList = new ArrayList<String>();
nameList.add("Tom");
nameList.add("Jack");
MessagePack messagePack = new MessagePack();
//開始序列化
byte[] raw = messagePack.write(nameList);
//使用MessagePack的模版,來接序列化後的位元組陣列轉換為List
List<String> deNameList = messagePack.read(raw,Templates.tList(Templates.TString));
System.out.println(deNameList.get(0));
System.out.println(deNameList.get(1));
System.out.println(deNameList.get(2));
Netty中如何使用MessagePack
編碼器的實現
public class MsgpackEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
MessagePack msgpack = new MessagePack();
//使用MessagePack對要傳送的資料進行序列化
byte[] raw = msgpack.write(msg);
out.writeBytes(raw);
}
}
解碼器的實現
public class MsgpackDecoder extends MessageToMessageDecoder<ByteBuf> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
//從msg中獲取需要解碼的byte陣列
final int length = msg.readableBytes();
byte[] b = new byte[length];
msg.getBytes(msg.readerIndex(), b,0,length);
//使用MessagePack的read方法將其反序列化成Object物件,並加入到解碼列表out中
MessagePack msgpack = new MessagePack();
out.add(msgpack.read(b));
}
}
實現該編碼器和解碼器的Netty服務端
public class NettyServer {
public void bind(int port) throws Exception {
EventLoopGroup bossGruop = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGruop, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// TODO Auto-generated method stub
socketChannel.pipeline()
//新增支援粘包、拆包解碼器,意義:從頭兩個位元組解析出資料的長度,並且長度不超過1024個位元組
.addLast("frameDecoder",new LengthFieldBasedFrameDecoder(1024, 0, 2,0,2))
//反序列化解碼器
.addLast("msgpack decoder",new MsgpackDecoder())
//新增支援粘包、拆包編碼器,傳送的每個資料都在頭部增加兩個位元組表訊息長度
.addLast("frameEncoder",new LengthFieldPrepender(2))
//序列化編碼器
.addLast("msgpack encoder",new MsgpackEncoder()
//後續自己的業務邏輯
.addLast(new ServerHandler());
}
});
try {
ChannelFuture future = bootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGruop.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
實現該編碼器和解碼器的Netty客戶端
public class NettyClient {
private void bind(int port, String host) {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// TODO Auto-generated method stub
socketChannel.pipeline()
.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2))
.addLast("msgpack decoder", new MsgpackDecoder())
.addLast("frameEncoder", new LengthFieldPrepender(2))
.addLast("msgpack encoder", new MsgpackEncoder())
.addLast(new ClientHandler());
}
});
try {
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
可以看出客戶端的程式碼與服務端基本相同,所以啊,如果能熟練掌握Netty,今後在自己的專案中運用上定製化編解碼的傳輸,將會是一件十分簡單的活路。
總結
無論是之前解決粘包拆包問題,還是這裡的使用序列化框架來進行編解碼。我相信讀者學習到這裡,對於Netty的使用都有了較為全面的瞭解。其實Netty幫我們解決了很多底層棘手問題,如客戶端斷連、控制程式碼洩漏和訊息丟失等等。所以我們才能十分簡單開發出一個穩定的網路通訊專案。