Netty的protobuf的图书订购服务端开发
一.Protobuf简介
Google的protobuf在业界非常流行,很多商业项目选择protobuf作为编解码框架。它是一个灵活,高效,结构化的数据序列化框架,相比于XML等传统的序列化工具,它更快,更小,更简单。支持数据结构化一次可以到处使用。
二. Protobuf编解码开发
package protobuf;
import java.util.List;
public class TestSubscribeReqProto {
privatestatic byte[] encode(SubscribeReqproto.SubscribeReq req){
returnreq.toByteArray();
}
privatestatic SubscibeReqProto.SubscribeReq decode(byte[] body)
throwsInvalidProtocolBufferException{
returnSubscribeReqProto.SubscribeReq.parseFrom(body);
}
privatestatic SubscribeReqProto.SubscribeReq createSubscribeReq(){
SubscribeReqProto.SubscribeReq.Builderbuilder=
SubscribeReqProto.SubscribeReq.newBuilder();
builder.setSubReqID(1);
builder.setUserName("zhengguang");
builder.setProductName("NettyBook");
List<String>address=new ArrayList<>();
address.add("wuhan");
address.add("shanghai");
address.add("beijing");
builder.addAllAddress(address);
returnbuilder.build();
}
publicstatic void main(String[] args) throws InvalidprotocolBufferException{
SubscribeReqProto.SubscirbeReqreq=CreateSubscribeReq();
System.out.println("Beforeencode:"+req.toString());
SubscribeReqProto.SubscribeReqreq2=decode(encode(req));
System.out.println("Afterdecode:"+req.toString());
System.out.println("Assertequal: -->"+req2.equals(req));
}
}
通过SubscribeReqProto.SubscribeReq静态方法newBuilder创建builder实例,通过Builder构建器对SubscribeReq属性进行设置,对于集合类型,通过addAllxxx()方法可以将集合对象设置到对应属性中。
编码时通过调用SubscribeReqProto.SubscribeReq实例的toByte即可,将SubscribeReq编码为byte数组,使用非常方便。
解码时通过SubscribeReqProto.SubscribeReq的静态方法parseForm将二进制byte数组解码为原始的对象。
三. Netty的Protobuf服务端开发
3.1 SubReqServe实现
package protobuf;
import serializable.SubReqClient;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.socket.SocketChannel;
public class SubReqServer {
publicvoid bind(int port) throws Exception{
//配置服务端的NIO线程组
EventLoopGroupbossGroup =new NioEventLoopGroup();
EventLoopGroupworkerGroup=new NioEventLoopGroup();
try{
ServerBootstrapb=new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,100)
.handler(newLoggingHandler(LogLevel.INFO))
.childHandler(newChannelInitializer<SocketChannel>() {
@Override
publicvoid initChannel(SocketChannel ch){
//用于半包处理
ch.pipeline().addLast(
newProtobufVarint32FrameDecoder());
//添加解码器,参数是com.google.protobuf.MessageLite
//实际上就是要告诉ProtobufDecoder需要解码的目标类是什么
ch.pipeline().addLast(
newProtobufDecoder(
SubscribeReqProto.SubscribeReq
.getDefaultInstance()));
ch.pipeline().addLast(
newProtobufVarint32LengthFieldPrepender());
ch.pipeline().addLast(newprotobufEncoder());
ch.pipeline().addLast(newSubReqServerHandler());
)
});
//绑定端口,同步等待成功
ChannelFuturef=b.bind(port).sync();
//等待服务器监听端口关闭
f.channel().closeFuture().sync();
}catch (Exception e) {
//TODO: handle exception
}finally{
//优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
publicstatic void main(String[] args) throws Exception{
intport=8080;
if(args!=null&&args.length>0){
try{
port=Integer.valueOf(args[0]);
}catch (NumberFormatException e) {
//TODO: handle exception
}
newSubReqClient().connect(port,"127.0.0.1");
}
}
}
3.2 SubReqServeHandler实现
package protobuf;
public class SubReqServerHandler extendsChannelHandlerAdapter{
@Override
publicvoid channelRead(ChannelHandlerContext ctx,Object msg) throws Exception{
SubscribeReqProto.SubscribeReqreq=(SubscribeReqProto.SubscribeReq) msg;
if("zhengguang".equalsIgnoreCase(req.getUserName())){
System.out.println("Serviceaccept client subscribe req:["+req.toString()+"]");
ctx.writeAndFlush(req.getSubReqID()));
}
}
privateSubscribeRespProto.SubscribeResp resp(int subReqID) {
SubscribeRespProto.SubscribeReq.Builderbuilder=SubscribeRespProto.SubscribeResp
.newBuilder();
builder.setSubReqID(subReqID);
builder.setRespCode(0);
builder.setDesc("Nettybook order succeed,3 days later,sent "
+"to the desianated address");
returnbuilder.build();
}
@Override
publicviod exceptionCaught(ChannelHandlerContext ctx,Throwable ctx) {
cause.printStackTrace();
ctx.close();// 发送异常,关闭链路
}
}
由于ProtobufDecoder已经对消息进行了自动解码,因此接收到的订购请求消息可以直接使用。对用户名进行校验,校验通过后构造应答消息返回给客户端,由于使用了ProtobufEncoder,所以不需要对SubscribeRespProto.SubscribeReq进行手工编码。
四. Netty的Protobuf客户端开发
4.1 SubReqClient实现
package protobuf;
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;
importio.netty.channel.nio.NioEventLoopGroup;
importio.netty.channel.socket.SocketChannel;
importio.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
importio.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
/*
*profobuf的图书订购客户端
*/
public class SubReqClient {
publicvoid connect(int port,String host) throws Exception{
//配置客户端NIO线程组
EventLoopGroupgroup=new NioEventLoopGroup();
try{
Bootstrapb=new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(newChannelInitializer<SocketChannel>() {
@Override
publicvoid initChannel(SocketChannel ch) throws Exception{
ch.pipeline().addLast(
newProtobufVarint32LengthFieldPrepender());
ch.pipeline().addLast(newProtobufEncoder());
ch.pipeline().addLast(newProtobufEncoder());
ch.pipeline().addLast(newSubReqClientHandler());
}
});
//发起异步连接操作
ChannelFuturef=b.connect(host,port).sync();
//等待客户端链路关闭
f.channel().closeFuture().sync();
}catch (Exception e) {
//TODO: handle exception
}finally{
//优雅退出,释放NIO线程组
group.shutdownGracefully();
}
}
publicstatic void main(String[] args) throws Exception{
intport=8080;
if(args!=null&&args.length>0){
try{
port=Integer.valueOf(args[0]);
}catch (NumberFormatException e) {
//TODO: handle exception
}
newSubReqClient().connect(port,"127.0.0.1");
}
}
}
需要指出的是客户端需要解码的对象是订购响应,所以使用SubscibeRespProto.SubscribeResp的实例做入参。
4.1 SubReqClientHandler实现
package protobuf;
import java.util.ArrayList;
import java.util.List;
importio.netty.channel.ChannelHandlerAdapter;
importio.netty.channel.ChannelHandlerContext;
public class SubReqClientHandler extendsChannelHandlerAdapter{
publicSubReqClientHandler(){
}
@Override
publicvoid channelActive(ChannelHandlerContext ctx){
for(inti=0;i<10;i++){
ctx.write(subReq(i));
}
ctx.flush();
}
privateSubscribeReqProto.SubscribeReq subReq(int i){
SubscribeReqProto.SubscribeReq.Builderbuilder=SubscribeReqProto.SubscribeReq.newBuilder();
builder.setSubReqID(i);
builder.setUserName("zhengguang");
builder.setProductName("NettyBook For Protobuf");
List<String>address=new ArrayList<>();
address.add("wuhan");
address.add("shanghai");
address.add("beijing");
returnbuilder.build();
}
@Override
publicvoid channelRead(ChannelHandlerContext ctx,Object msg) throws Exception{
System.out.println("Receiveserver response:["+msg+"]");
}
@Override
publicvoid channelReadComplete(ChannelHandlerContext ctx) throws Exception{
ctx.flush();
}
@Override
publicvoid exceptionCaucht(ChannelHandlerContext ctx,Throwable cause){
cause.printStackTrace();
ctx.close();
}
}
客户端接收到服务端的应答消息之后会直接打印,按照设计,应该打印10次。利用netty提供的protobuf编解码能力,我们在不需要了解protobuf实现和使用细节情况下就能轻松支持protobuf编辑码,可以方便实现跨语言的远程服务调用与周边异构系统进行通信对接。