Netty結(jié)合Protobuf進(jìn)行編解碼的方法
一般在使用netty時(shí),數(shù)據(jù)傳輸?shù)臅r(shí)候都會(huì)選擇對(duì)傳輸?shù)臄?shù)據(jù)進(jìn)行編解碼,編碼后的數(shù)據(jù)變小, 有利于在有限的帶寬下傳輸更多的數(shù)據(jù)。
由于java本身序列化的缺點(diǎn)較多(無(wú)法跨語(yǔ)言,序列化后的碼流太大,序列化的性能太低等),業(yè)界主流的編解碼框架主要有如下三個(gè):
- Google的Protobuf
- Facebook的Thrift
- JBoss的Marshalling
今天我們簡(jiǎn)單介紹一下Netty結(jié)合google的Protobuf框架進(jìn)行數(shù)據(jù)的編解碼。
1. 什么是Protobuf?
Protobuf全稱(chēng)是Google Protocol Buffers, 它是谷歌公司開(kāi)源的一個(gè)序列化框架。
它將數(shù)據(jù)結(jié)構(gòu)以.proto文件進(jìn)行描述,通過(guò)代碼生成工具可以生成對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu)的POJO對(duì)象和Protobuf相關(guān)的方法和屬性。
它的特點(diǎn)如下:
- 結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式
- 高效的編解碼性能
- 語(yǔ)言無(wú)關(guān)、平臺(tái)無(wú)關(guān)、擴(kuò)展性好
- 官方支持多個(gè)語(yǔ)言(java,c++,python,c#等)
2. 下載安裝
Protobuf已經(jīng)托管在github上,可以在release頁(yè)面下載,本案例使用的是v3.6(要下載后綴為-win32.zip的)。
下載后,將壓縮包進(jìn)行解壓。這里主要用到bin目錄下的protoc.exe。
3. 定義好proto文件
SubscribeReq.proto
// 區(qū)分不同的protobuf版本,必須有
syntax = "proto2";
package netty;
// 生成的目標(biāo)類(lèi)的包路徑
option java_package = "cn.ddlover.nettystudy.protobuf";
// 生成的目標(biāo)類(lèi)的名字
option java_outer_classname = "SubscribeReqProto";
message SubscribeReq{
required int32 subReqID = 1;
required string userName = 2;
required string productName = 3;
repeated string address = 4;
}
SubscribeResp.proto
syntax = "proto2";
package netty;
option java_package = "cn.ddlover.nettystudy.protobuf";
option java_outer_classname = "SubscribeRespProto";
message SubscribeResp{
required int32 subReqID = 1;
required int32 respCode = 2;
required string desc = 3;
}
此時(shí)項(xiàng)目結(jié)構(gòu)如下

重點(diǎn)關(guān)注一下兩個(gè)proto文件的位置,是位于項(xiàng)目的根路徑下
4. 分別執(zhí)行以下命令
D:\xhb\protoc-3.6.1-win32\bin\protoc.exe --java_out=.\src\main\java SubscribeResp.proto
D:\xhb\protoc-3.6.1-win32\bin\protoc.exe --java_out=.\src\main\java SubscribeReq.proto
這里需要把protoc.exe的位置換成自己的, --java_out命令設(shè)置的是proto文件中java_package指令的父目錄。
5. 此時(shí)項(xiàng)目結(jié)構(gòu)如下

下面開(kāi)始代碼方面的開(kāi)發(fā),
1. 先貼一波maven的配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.ddlover</groupId>
<artifactId>nettystudy</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.33.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
</dependencies>
</project>
2. 這里貼上完整代碼的項(xiàng)目結(jié)構(gòu)

3. SubReqServer.java
import cn.ddlover.nettystudy.handler.SubReqServerHandler;
import cn.ddlover.nettystudy.protobuf.SubscribeReqProto;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
/**
* Protobuf版本圖書(shū)訂購(gòu)代碼
*/
public class SubReqServer {
private static final int PORT = 8080;
public static void main(String[] args) {
bind(PORT);
}
private static void bind(int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
try {
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 半包處理
socketChannel.pipeline().addLast(new ProtobufVarint32FrameDecoder());
socketChannel.pipeline().addLast(new ProtobufDecoder(SubscribeReqProto.SubscribeReq.getDefaultInstance()));
socketChannel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
socketChannel.pipeline().addLast(new ProtobufEncoder());
socketChannel.pipeline().addLast(new SubReqServerHandler());
}
});
ChannelFuture future = b.bind(port).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
4. SubReqServerHandler.java
import cn.ddlover.nettystudy.protobuf.SubscribeReqProto;
import cn.ddlover.nettystudy.protobuf.SubscribeRespProto;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class SubReqServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
SubscribeReqProto.SubscribeReq req = (SubscribeReqProto.SubscribeReq)msg;
if ("張三".equals(req.getUserName())) {
System.out.println("Server accept clietn subscribe req : ["+req.toString()+"]");
ctx.writeAndFlush(resp(req.getSubReqID()));
}
}
private SubscribeRespProto.SubscribeResp resp(int subReqID) {
SubscribeRespProto.SubscribeResp.Builder builder = SubscribeRespProto.SubscribeResp.newBuilder();
builder.setSubReqID(subReqID);
builder.setRespCode(0);
builder.setDesc("netty書(shū)籍下單成功,3天后將會(huì)送到你的住處");
return builder. build();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
5. SubReqClient.java
import cn.ddlover.nettystudy.handler.SubReqClientHandler;
import cn.ddlover.nettystudy.protobuf.SubscribeRespProto;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
public class SubReqClient {
private static final String HOST = "localhost";
private static final int PORT = 8080;
public static void main(String[] args) {
connect(HOST, PORT);
}
private static void connect(String host, int port) {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
try {
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 半包處理
socketChannel.pipeline().addLast(new ProtobufVarint32FrameDecoder());
socketChannel.pipeline().addLast(new ProtobufDecoder(SubscribeRespProto.SubscribeResp.getDefaultInstance()));
socketChannel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
socketChannel.pipeline().addLast(new ProtobufEncoder());
socketChannel.pipeline().addLast(new SubReqClientHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
}
}
6. SubReqClientHandler.java
import cn.ddlover.nettystudy.protobuf.SubscribeReqProto;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.ArrayList;
import java.util.List;
public class SubReqClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i =0;i<10;i++) {
ctx.write(subReq(i));
}
ctx.flush();
}
private SubscribeReqProto.SubscribeReq subReq(int i) {
SubscribeReqProto.SubscribeReq.Builder builder = SubscribeReqProto.SubscribeReq.newBuilder();
builder.setSubReqID(i);
builder.setUserName("張三");
builder.setProductName("Netty Book");
List<String> address = new ArrayList<>();
address.add("NanJing YuHuaTai");
address.add("BeiJing LiuLiChang");
address.add("ShenZhen HongShuLin");
builder.addAllAddress(address);
return builder.build();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Receive server response : ["+ msg +"]");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
代碼部分到此就結(jié)束了,然后分別運(yùn)行SubReqServer和SubReqClient, 就可以發(fā)現(xiàn)運(yùn)行成功了。
這里要注意的事,調(diào)用toString的時(shí)候,中文在控制臺(tái)打印的是字節(jié),而調(diào)用對(duì)應(yīng)屬性的getter方法的時(shí)候,是可以做中文的判斷的,顯然這里protobuf的toString沒(méi)有被修改好呀。
當(dāng)然,我們也可以發(fā)現(xiàn),netty本身已經(jīng)封裝好了對(duì)谷歌的protobuf的支持。Netty還是很強(qiáng)大的。
到此這篇關(guān)于Netty結(jié)合Protobuf進(jìn)行編解碼的文章就介紹到這了,更多相關(guān)Netty結(jié)合Protobuf編解碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- java程序中protobuf的基本用法示例
- 淺談序列化之protobuf與avro對(duì)比(Java)
- 基于Protobuf動(dòng)態(tài)解析在Java中的應(yīng)用 包含例子程序
- protobuf c++編程筆記
- python使用protobufde的過(guò)程解析
- SpringBoot使用protobuf格式的接口方式
- Python使用protobuf序列化和反序列化的實(shí)現(xiàn)
- Protobuf在Cmake中的正確使用方法詳解
- C#語(yǔ)言使用gRPC、protobuf(Google Protocol Buffers)實(shí)現(xiàn)文件傳輸功能
- 在java程序中使用protobuf
相關(guān)文章
mybatis if test條件判斷語(yǔ)句中的判斷問(wèn)題分析
這篇文章主要介紹了mybatis if test條件判斷語(yǔ)句中的判斷問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
mybatis映射文件mapper.xml的具體寫(xiě)法
在開(kāi)發(fā)過(guò)程中,需要開(kāi)發(fā)人員配置mapper映射文件,本文主要介紹了mybatis映射文件mapper.xml的具體寫(xiě)法,感興趣的可以了解一下2021-09-09
詳解Spring注解驅(qū)動(dòng)開(kāi)發(fā)之屬性賦值
今天帶大家學(xué)習(xí)Spring注解驅(qū)動(dòng)開(kāi)發(fā)的相關(guān)知識(shí),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)Java的小伙伴們很有幫助,需要的朋友可以參考下2021-05-05
SpringBoot深入講解單元測(cè)試與熱部署應(yīng)用
這篇文章介紹了SpringBoot單元測(cè)試與熱部署,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06
Java?Spring?Dubbo三種SPI機(jī)制的區(qū)別
這篇文章主要介紹了Java?Spring?Dubbo三種SPI機(jī)制的區(qū)別,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-08-08
mybatis 如何返回list<String>類(lèi)型數(shù)據(jù)
這篇文章主要介紹了mybatis 如何返回list<String>類(lèi)型數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Java中幾種常用數(shù)據(jù)庫(kù)連接池的使用
數(shù)據(jù)庫(kù)連接池在編寫(xiě)應(yīng)用服務(wù)是經(jīng)常需要用到的模塊,太過(guò)頻繁的連接數(shù)據(jù)庫(kù)對(duì)服務(wù)性能來(lái)講是一個(gè)瓶頸,使用緩沖池技術(shù)可以來(lái)消除這個(gè)瓶頸,本文就來(lái)介紹Java常見(jiàn)的幾種,感興趣的可以了解一下2021-05-05

