Netty結合Protobuf進行編解碼的方法
一般在使用netty時,數據傳輸的時候都會選擇對傳輸的數據進行編解碼,編碼後的數據變小, 有利於在有限的帶寬下傳輸更多的數據。
由於java本身序列化的缺點較多(無法跨語言,序列化後的碼流太大,序列化的性能太低等),業界主流的編解碼框架主要有如下三個:
- Google的Protobuf
- Facebook的Thrift
- JBoss的Marshalling
今天我們簡單介紹一下Netty結合google的Protobuf框架進行數據的編解碼。
1. 什麼是Protobuf?
Protobuf全稱是Google Protocol Buffers, 它是谷歌公司開源的一個序列化框架。
它將數據結構以.proto文件進行描述,通過代碼生成工具可以生成對應數據結構的POJO對象和Protobuf相關的方法和屬性。
它的特點如下:
- 結構化數據存儲格式
- 高效的編解碼性能
- 語言無關、平臺無關、擴展性好
- 官方支持多個語言(java,c++,python,c#等)
2. 下載安裝
Protobuf已經托管在github上,可以在release頁面下載,本案例使用的是v3.6(要下載後綴為-win32.zip的)。
下載後,將壓縮包進行解壓。這裡主要用到bin目錄下的protoc.exe。
3. 定義好proto文件
SubscribeReq.proto
// 區分不同的protobuf版本,必須有 syntax = "proto2"; package netty; // 生成的目標類的包路徑 option java_package = "cn.ddlover.nettystudy.protobuf"; // 生成的目標類的名字 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; }
此時項目結構如下
重點關註一下兩個proto文件的位置,是位於項目的根路徑下
4. 分別執行以下命令
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命令設置的是proto文件中java_package指令的父目錄。
5. 此時項目結構如下
下面開始代碼方面的開發,
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. 這裡貼上完整代碼的項目結構
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版本圖書訂購代碼 */ 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書籍下單成功,3天後將會送到你的住處"); 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(); } }
代碼部分到此就結束瞭,然後分別運行SubReqServer和SubReqClient, 就可以發現運行成功瞭。
這裡要註意的事,調用toString的時候,中文在控制臺打印的是字節,而調用對應屬性的getter方法的時候,是可以做中文的判斷的,顯然這裡protobuf的toString沒有被修改好呀。
當然,我們也可以發現,netty本身已經封裝好瞭對谷歌的protobuf的支持。Netty還是很強大的。
到此這篇關於Netty結合Protobuf進行編解碼的文章就介紹到這瞭,更多相關Netty結合Protobuf編解碼內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 在SpringBoot中,如何使用Netty實現遠程調用方法總結
- 如何在Netty中註解使用Service或者Mapper
- 在netty中使用native傳輸協議的方法
- Java實戰之用springboot+netty實現簡單的一對一聊天
- Springboot+Netty+Websocket實現消息推送實例