一:概述
在进行网络传输时,我们传输到网络上的数据,并不一定是字符串,而可能是一个对象,这时候我们就需要,将对象以有效的方式转为字节数组传输到网络中,然后收到数据的一方可以解析出有效的数据。例如在java中,我们需要传输对象,可以直接通过将ObjectOutputStream或ObjectInputStream直接作为可存储的字节数组传输到网络中。
通过java这种序列化对象的方式,存在一些问题:
1、无法跨语言
2、众所众知的java序列化后的数据,相比其他序列化方式产生的数据较大。
进行网络上传输的序列化工具有很多种,常用的有:
1、Protobuf
2、Marshalling
3、MesssagePack
二:Protobuf的使用
1、安装
地址:https://github.com/protocolbuffers/protobuf/releases
下载对应的版本安装
安装后配置下bin目录到环境变量path中,最后调试结果:
表示可运行…
2、protobuf的使用
安装后通过在所使用的proto文件路径下打开cmd窗口执行以下命令:
protoc -I=源地址 --java_out=目标地址 源地址/xxx.proto
生成出代码
3、netty中protobuf的使用
看一个使用客户端服务端通信的protobuf案例;
定义数据用户传输的proto文件:
通过proto生成出java代码并copy到项目中.
服务端:
public class NettyProtobufServer {
private int port;
public NettyProtobufServer(int port){
this.port = port;
}
public void start(){
// boss 是处理客户端连接的线程池
// worker 是处理从客户端连接转发过来的IO数据的读写线程池
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try{
// ServerBootstrap 对外一个便利创建服务端,Builder建造者设计模式
ServerBootstrap sb = new ServerBootstrap();
// 绑定线程池
sb.group(boss,worker)
// 绑定channel 服务端绑定NioServerSocketChannel,此实现jdk的ServerSocketChannel
.channel(NioServerSocketChannel.class)
// 绑定服务端相关参数,可添加绑定多个参数
.option(ChannelOption.SO_BACKLOG, 1024) //指定此套接口排队的最大连接个数
// IO事件处理类,主要处理IO事件的读写
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 用于半包处理
pipeline.addLast(new ProtobufVarint32FrameDecoder());
// 解码器,ProtobufDecoder参数是告诉解码器的目标类
pipeline.addLast(new ProtobufDecoder(SubscribeReqProto.SubscribeReq.getDefaultInstance()));
// 编码,对要发送的数据进行封装处理,头长度,尾部数据
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
// protobuf编码
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new SubReqServerHandler());
}
});
// 绑定端口,同步等待成功
ChannelFuture cf = sb.bind(port).sync();
System.out.println("服务已启动.................监听端口:" + port);
// 等待服务器监听端口关闭
cf.channel().closeFuture().sync();
}catch (Exception e){
// 优雅关闭线程资源
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) {
NettyProtobufServer nettyServer = new NettyProtobufServer(9090);
nettyServer.start();
}
}
服务端handler处理:
public class SubReqServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
SubscribeReqProto.SubscribeReq req = (SubscribeReqProto.SubscribeReq) msg;
System.out.println("接受的数据: " + req.toString());
ctx.writeAndFlush(responseMsg(req.getSubReqId()));
}
private SubscribeReqProto.SubscribeResp responseMsg(int subReqID){
SubscribeReqProto.SubscribeResp.Builder builder = SubscribeReqProto.SubscribeResp.newBuilder();
builder.setSubReqId(subReqID);
builder.setRespCode("0");
builder.setDesc("fangyouyun");
return builder.build();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端:
public class NettyProtobufClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
ch.pipeline().addLast(new ProtobufDecoder(SubscribeReqProto.SubscribeResp.getDefaultInstance()));
ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
ch.pipeline().addLast(new ProtobufEncoder());
ch.pipeline().addLast(new SubReqClientHandler());
}
});
// 异步连接服务端
ChannelFuture f = b.connect(new InetSocketAddress(9090)).sync();
// 等待关闭
f.channel().closeFuture().sync();
}finally {
//优雅退出,是否线程池资源
group.shutdownGracefully();
}
}
}
客户端handler处理:
public class SubReqClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for(int i=0; i<10; i++){
System.out.println("发送消息......");
ctx.write(subReq(i));
}
ctx.flush();
}
private SubscribeReqProto.SubscribeReq subReq(int i) {
SubscribeReqProto.SubscribeReq.Builder builder = SubscribeReqProto.SubscribeReq.newBuilder();
builder.setSubReqId(i);
builder.setUserName("fangyouyun");
builder.setProductName("netty learning");
return builder.build();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("收到服务端的消息: " + msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
如此运行服务端,客户端即可通信。
注意, 在使用protobuf进行netty的客户端服务端通信时,ProtobufDecoder仅仅只是对通信数据解码,并不处理半包问题,所以netty针对protobuf处理半包问题,可以采用三种方式:
1.使用netty提供的ProtobufVarint32FrameDecoder,它可以处理半包问题
2.使用netty的ByteToMessageDecoder类,自己实现解码器处理半包问题
3.使用netty提供的通用半包处理器LengthFieldBasedFrameDecoder处理半包问题
文章中案例代码:https://download.csdn.net/download/qq_22871607/11072379