netty实现tcp见:netty4+tcp+springboot,server+client
下面只记录了数据传输使用protobuf。
1.ServerChannelInitializer、ClientChannelInitializer中使用netty提供的protubuf的编解码处理类:
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* description: 通道初始化,主要用于设置各种Handler
* author:
* date: 2018-11-28 14:55
**/
@Component
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Autowired
ServerChannelHandler serverChannelHandler;
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//IdleStateHandler心跳机制,如果超时触发Handle中userEventTrigger()方法
pipeline.addLast("idleStateHandler",
new IdleStateHandler(15, 0, 0, TimeUnit.MINUTES));
//字符串编解码器
// pipeline.addLast(
// new StringDecoder(),
// new StringEncoder()
// );
//请求的解码
pipeline.addLast(new ProtobufVarint32FrameDecoder());
//解码封装成的目标类
pipeline.addLast(new ProtobufDecoder(ConfigMsgBuilder.ConfigMsg.getDefaultInstance()));//ChannelInboundHandlerAdapter
//响应的编码
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());//ChannelOutboundHandlerAdapter
//自定义Handler
pipeline.addLast("serverChannelHandler", serverChannelHandler);
}
}
ClientChannelInitializer也是一样,核心就是使用protobuf的编解码类替换之前的字符串编解码类。
其中ConfigMsgBuilder.ConfigMsg是protobuf生成的java类,用来封装传输的数据。
2.ServerChannelHandler中响应数据使用protobuf生成的java传输类:
@Component
@ChannelHandler.Sharable
@Slf4j
public class ServerChannelHandler extends SimpleChannelInboundHandler<Object> {
/**
* 拿到传过来的msg数据,开始处理
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Netty tcp server receive msg : " + msg);
// ctx.channel().writeAndFlush(" response msg ").syncUninterruptibly();
ConfigMsgBuilder.ConfigMsg.Builder builder = ConfigMsgBuilder.ConfigMsg.newBuilder();
builder.setMessageType(2);
builder.setMessage("no change2");
builder.setVersion(10002);
ConfigMsgBuilder.ConfigMsg build = builder.build();
ctx.channel().writeAndFlush(build).syncUninterruptibly();
}
}
3.NettyTcpClient的请求数据也换为protobuf的格式:
import com.h3c.iot.app.engine.netty.tcp.client.NettyTcpClient;
import com.h3c.iot.app.engine.netty.tcp.server.NettyTcpServer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
/**
* ClassName: SpringBootApplication
* description:
* author:
* date: 2018-09-30 09:15
**/
@org.springframework.boot.autoconfigure.SpringBootApplication//@EnableAutoConfiguration @ComponentScan
public class SpringBootApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SpringBootApplication.class, args);
}
@Autowired
NettyTcpServer nettyTcpServer;
@Autowired
NettyTcpClient nettyTcpClient;
@Override
public void run(String... args) throws Exception {
ChannelFuture start = nettyTcpServer.start();
//启动客户端,发送数据
nettyTcpClient.connect();
// for (int i = 0; i < 10; i++) {
// nettyTcpClient.sendMsg("hello world" + i);
// }
ConfigMsgBuilder.ConfigMsg.Builder builder = ConfigMsgBuilder.ConfigMsg.newBuilder();
builder.setMessageType(1);
builder.setMessage("no change");
builder.setVersion(100);
ConfigMsgBuilder.ConfigMsg build = builder.build();
nettyTcpClient.sendMsg(build);
start.channel().closeFuture().syncUninterruptibly();
}
}
这样就实现了netty的tcp传输数据时使用protobuf的序列化数据协议。
重点补充:ProtobufVarint32FrameDecoder解码器、ProtobufVarint32LengthFieldPrepender编码器作用。
1。protobuf 消息格式举例:Length + Protobuf Data (消息头+消息数据)
Length表示Data的大小。为了更减少传输量,消息头采用的是varint 格式。
什么是varint?
Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用的字节数越少。
Varint 中的每个byte 的最高位 bit 有特殊的含义,如果该位为 1,表示下一个 byte 也是该数字的一部分,如果该位为 0,则结束。每个byte剩余的 7 位 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,会用两个字节。
例如整数1的表示,仅需一个字节:
0000 0001
例如300的表示,需要两个字节:
1010 1100 0000 0010
采 用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。多数情况下,Length是比较小的,所以,采用 Varint 后,可以用更少的字节数来表示数字信息。
2。server端处理数据流程:
接收到protobuf数据 --> ProtobufVarint32FrameDecoder 消息头的解码(处理半包) --> ProtobufDecoder 消息数据体的解码 --> 自定义ChannelInboundHandler 即业务逻辑的处理 --> 自定义ChannelOutboundHandler 即输出数据的处理 --> ProtobufVarint32LengthFieldPrepender 消息头的编码,即加长度 --> ProtobufEncoder 消息数据体的编码 --> 响应protubuf数据
以上就是我个人理解的netty的server端处理protobuf的流程,client端同理。解释如下:
2.1 ProtobufVarint32LengthFieldPrepender 是对protobuf协议的的消息头上加上一个长度为32的整形字段,用于标志这个消息的长度,即Length。
2.2 ProtobufVarint32FrameDecoder 是针对protobuf协议的ProtobufVarint32LengthFieldPrepender 所加长度属性的解码器。
2.3 ProtobufDecoder 是将消息数据体解码成对应的的java类。
2.4 ProtobufEncoder 是将java类编码成protobuf的消息数据体,它是一个ChannelOutboundHandler,会自动将编码后数据加入到out中,响应给client。
网上解释记录:
ProtobufEncoder:非常简单,内部直接使用了message.toByteArray()将字节数据放入bytebuf中输出(out中,交由下一个encoder处理)。
ProtobufVarint32LengthFieldPrepender:因为ProtobufEncoder只是将message的各个filed按照规则输出了,并没有serializedSize,所以socket无法判定package(封包)。这个Encoder的作用就是在ProtobufEncoder生成的字节数组前,prepender(前置)一个varint32数字,表示serializedSize。
ProtobufVarint32FrameDecoder:这个decoder和Prepender做的工作正好对应,作用就是“成帧”,根据seriaziedSize读取足额的字节数组--一个完整的package。
ProtobufDecoder:和ProtobufEncoder对应,这个Decoder需要指定一个默认的instance,decoder将会解析byteArray,并根据format规则为此instance中的各个filed赋值。
网上解释个人总结:ProtobufVarint32LengthFieldPrepender 让socket知道packet大小,从而进行封包;
ProtobufVarint32FrameDecoder 根据seriaziedSize读取足额的字节数组,得到一个完整的package。
参考博客: