Netty实现编解码、粘包粘包、心跳检测、断线重连
1、ByteBuf详解
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
/**
* cse中最常见使用的双指针队列
*/
public class ByteBufDemo {
public static void main(String[] args) {
// 创建byteBuf对象,该对象内部包含一个字节数组byte[10]
// 通过readerindex和writerIndex和capacity,将buffer分成三个区域
// 已经读取的区域:[0,readerindex) 可读取的区域:[readerindex,writerIndex) 可写的区域: [writerIndex,capacity)
ByteBuf byteBuf = Unpooled.buffer(10);
System.out.println("byteBuf=" + byteBuf);
for (int i = 0; i < 8; i++) {
byteBuf.writeByte(i);
}
System.out.println("byteBuf=" + byteBuf);
for (int i = 0; i < 5; i++) {
System.out.println(byteBuf.getByte(i));
}
System.out.println("byteBuf=" + byteBuf);
for (int i = 0; i < 5; i++) {
System.out.println(byteBuf.readByte());
}
System.out.println("byteBuf=" + byteBuf);
//用Unpooled工具类创建ByteBuf
ByteBuf byteBuf2 = Unpooled.copiedBuffer("hello,zhuge!", CharsetUtil.UTF_8);
//使用相关的方法
if (byteBuf2.hasArray()) {
byte[] content = byteBuf2.array();
//将 content 转成字符串
System.out.println(new String(content, CharsetUtil.UTF_8));
System.out.println("byteBuf=" + byteBuf2);
System.out.println(byteBuf2.readerIndex()); // 0
System.out.println(byteBuf2.writerIndex()); // 12
System.out.println(byteBuf2.capacity()); // 36
System.out.println(byteBuf2.getByte(0)); // 获取数组0这个位置的字符h的ascii码,h=104
int len = byteBuf2.readableBytes(); //可读的字节数 12
System.out.println("len=" + len);
//使用for取出各个字节
for (int i = 0; i < len; i++) {
System.out.println((char) byteBuf2.getByte(i));
}//范围读取
System.out.println(byteBuf2.getCharSequence(0, 6, CharsetUtil.UTF_8));
System.out.println(byteBuf2.getCharSequence(6, 6, CharsetUtil.UTF_8));
}
}
}
2、Netty编解码
import com.alibaba.fastjson.JSON;
/**
* 文件传输时使用Json序列化
*/
public class JSONSerializer{
public static byte[] serialize(Object object) {
return JSON.toJSONBytes(object);
}
public static <T> T deserialize(Class<T> clazz, byte[] bytes) {
return JSON.parseObject(bytes, clazz);
}
}
import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 编解码器protostuff工具
*/
public class ProtoStuffUtil {
private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<Class<?>,Schema<?>>();
private static <T> Schema<T> getSchema(Class<T> clazz){
@SuppressWarnings("unchecked")
Schema<T> schema = (Schema<T>) cachedSchema.get(clazz);
if (schema == null){
schema = RuntimeSchema.getSchema(clazz);
}
if (schema != null){
//cachedSchema.put(clazz,schema);
}
return schema;
}
/**
* 序列化
*/
public static <T> byte[] serializer(T obj){
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) obj.getClass();
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
try{
Schema<T> schema = getSchema(clazz);
return ProtostuffIOUtil.toByteArray(obj,schema,buffer);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
} finally {
buffer.clear();
}
}
/**
* 反序列化
* @param data
* @param clazz
* @param <T>
* @return
*/
public static <T> T deserializer(byte[] data, Class<T> clazz) {
try {
T obj = clazz.newInstance();
Schema<T> schema = getSchema(clazz);
ProtostuffIOUtil.mergeFrom(data, obj, schema);
return obj;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
3、Netty粘包拆包
TPC流拆包
import com.huawei.tuling05.week04_netty._04netty.core.codec.JSONSerializer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
* 消息解码器 ->
* 1、拆包将TCP协议缓冲区数据拆分
* 2、将byte[]类型的内容转为String或其他类型
*/
public class MessageDecoderHandler extends ByteToMessageDecoder {
int length = 0;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//需要将得到二进制字节码-> MyMessageProtocol 数据包(对象)
System.out.println(in);
if(in.readableBytes() >= 4) {
if (length == 0){
length = in.readInt();
}
if (in.readableBytes() < length) {
System.out.println("当前可读数据不够,继续等待。。");
return;
}
byte[] content = new byte[length];
if (in.readableBytes() >= length){
in.readBytes(content);
//封装成MyMessageProtocol对象,传递到下一个handler业务处理
//此处提现将具体内容反序列化的思想
MessageContent deserialize = JSONSerializer.deserialize(MessageContent.class, content);
System.out.println("【入口】:Step 1 接收消息:"+ deserialize);
out.add(deserialize);
}
length = 0;
}
}
}
粘包
import com.huawei.tuling05.week04_netty._04netty.core.codec.JSONSerializer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* 消息编码器 ->
* 1、将发送内容的长度和内容分别写入
* 2、将String或Json类型的内容转为byte[]
*/
public class MessageEncoderHandler extends MessageToByteEncoder<MessageContent> {
private Class<?> clazz;
public MessageEncoderHandler(Class<?> clazz) {
this.clazz = clazz;
}
@Override
protected void encode(ChannelHandlerContext ctx, MessageContent msg, ByteBuf out) throws Exception {
System.out.println("【出口】:Step last,发送消息:"+msg.getContent());
if (clazz != null && clazz.isInstance(msg)) {
byte[] bytes = JSONSerializer.serialize(msg);
out.writeInt(bytes.length);
out.writeBytes(bytes);
}
}
}
4、Netty心跳检测机制
//Step3 建立心跳连接 另起线程schedule检查读超时
pipeline.addLast(new IdleStateHandler(3, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new HeartBeatServerHandler());//专用与心跳连接的处理器
源码分析:
5、客户端断线自动重连
6、基于Netty实现分布式框架
基于Nettu实现分布式框架,类似RPC、Dubbo功能,实际是实现Pipeline管道中各个Handler的业务逻辑。Netty通过ctx.firexxx来实现各Handler按照顺序的调用。下面来实现上述编解码、粘包粘包、心跳连接、自动断线连接的功能。
将上述功能集成于下面demo:
6.1 Client端代码
public class NettyFuncClient {
Bootstrap bootstrap = new Bootstrap();
public NettyFuncClient(){
EventLoopGroup eventLoopGroup = new NioEventLoopGroup(2);
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//Step2 出入站第二道工序 粘包拆包(TPC指定消息长度进行拆包) 此处合二为一
pipeline.addLast(new MessageEncoderHandler(MessageContent.class));
pipeline.addLast(new MessageDecoderHandler());//处理服务器的心跳响应
pipeline.addLast(new HeartBeatClientHandler(NettyFuncClient.this));
}
});
}
public static void main(String[] args) throws InterruptedException {
NettyFuncClient client = new NettyFuncClient();
client.connect();
}
public void connect() throws InterruptedException {
System.out.println("netty client start...");
ChannelFuture connect = bootstrap.connect("127.0.0.1", 9090);
connect.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
//时时保持socket心跳
sendHeartBeat(future.channel() ,"Heartbeat Packet");
}else {
//重试动作
future.channel().eventLoop().schedule(() -> {
System.err.println("重连服务端...");
try {
connect();
} catch (Exception e) {
e.printStackTrace();}
}, 3000, TimeUnit.MILLISECONDS);
}}
});
//对通道关闭进行监听
connect.channel().closeFuture().sync();
}
/**
* 客户端随时需要于服务端保持连接
* socket心跳保持连接
*
* @param channel
* @param msg
* @throws InterruptedException
*/
private static void sendHeartBeat(Channel channel ,String msg) throws InterruptedException {
while (channel.isActive()) {
UUID uuid = UUID.randomUUID();
//隔段时间发送一次socket心跳
MessageContent messageContent = new MessageContent();
messageContent.setRequestId(uuid.toString());
messageContent.setContent(msg);
channel.writeAndFlush(messageContent);
Random random = new Random();
int num = random.nextInt(8);
//每秒1次的socket心跳
Thread.sleep(1000*num);
}
}
}
Client端handler2处理心跳的处理器
public class HeartBeatClientHandler extends ChannelInboundHandlerAdapter {
private NettyFuncClient nettyClient;
public HeartBeatClientHandler(NettyFuncClient nettyClient) {
this.nettyClient = nettyClient;
}
// channel 处于不活动状态时调用
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.err.println("运行中断开重连。。。");
nettyClient.connect();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
6.2 Server端代码
- 服务端启动代码
/**
* 集成Netty的核心功能:
* 1、编解码
* 2、粘包拆包
* 3、心跳连接
* 4、客户端断线重连
*
*/
public class NettyFuncServer {
public static void main(String[] args) throws InterruptedException {
//设置2组线程,分别用于接收请求和处理业务
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();//默认逻辑核心数*2
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)//反射获取该类对象
.option(ChannelOption.SO_BACKLOG,1024)//阻塞队列
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {//pipeline -> handler 入站顺序、出站逆序
//流水线工作
ChannelPipeline pipeline = ch.pipeline();
//Step1、Step2 入站第一道工序 解码 出站最后一道工序 编码 序列化
pipeline.addLast(new MessageEncoderHandler(MessageContent.class));
pipeline.addLast(new MessageDecoderHandler());
//Step3 建立心跳连接
pipeline.addLast(new IdleStateHandler(3, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new HeartBeatServerHandler());//专用与心跳连接的处理器
// Step4 业务逻辑及断线重连
pipeline.addLast(new NettyFuncServerHandler());
//业务逻辑 channelInactive方法中实现建立断线重建机制
}
});
System.out.println("Netty Server start...");
//启动服务器(绑定端口号) bind是异步操作 sync是等待异步绑定结果的同步操作
ChannelFuture cf = bootstrap.bind(9090).sync();
//等待服务器监听端口关闭,closeFuture是异步操作
//通过sync同步等待通道关闭处理完毕,这里会阻塞等待通道关闭完成,内部调用的是Object.wait()方法
cf.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
- 心跳及重连功能
public class HeartBeatServerHandler extends SimpleChannelInboundHandler<String> {
int readIdleTimes = 0;
@Override
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
System.out.println(" ====== > [server] message received : " + s);
if ("Heartbeat Packet".equals(s)) {
ctx.channel().writeAndFlush("ok");
} else {
System.out.println(" 其他信息处理 ... ");
}
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent event = (IdleStateEvent) evt;
String eventType = null;
switch (event.state()) {
case READER_IDLE:
eventType = "读空闲";
readIdleTimes++; // 读空闲的计数加1
break;
case WRITER_IDLE:
eventType = "写空闲";
// 不处理
break;
case ALL_IDLE:
eventType = "读写空闲";
// 不处理
break;
}
System.out.println(ctx.channel().remoteAddress() + "超时事件:" + eventType);
if (readIdleTimes > 3) {
System.out.println(" [server]读空闲超过3次,关闭连接,释放更多资源");
ctx.channel().writeAndFlush("idle close");
ctx.channel().close();
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.err.println("=== " + ctx.channel().remoteAddress() + " is active ===");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.err.println("=== " + ctx.channel().remoteAddress() + " is InActive ===");
}
}
6.3 编解码及序列化代码
- 自定义消息体
/**
* 自定义协议包
*/
public class MessageContent {
public MessageContent() {
}
public MessageContent(String requestId, String content) {
this.requestId = requestId;
this.content = content;
}
private String requestId;
//一次发送包体内容
// content可以是String 或者是Json(此处以String为例)
private String content;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "MessageProtocol{" +
"requestId=" + requestId +
", content='" + content + '\'' +
'}';
}
}
- 编码及序列化
/**
* 消息编码器 ->
* 1、将发送内容的长度和内容分别写入
* 2、将String或Json类型的内容转为byte[]
*/
public class MessageEncoderHandler extends MessageToByteEncoder<MessageContent> {
private Class<?> clazz;
public MessageEncoderHandler(Class<?> clazz) {
this.clazz = clazz;
}
@Override
protected void encode(ChannelHandlerContext ctx, MessageContent msg, ByteBuf out) throws Exception {
System.out.println("【出口】:Step last,发送消息:"+msg.getContent());
if (clazz != null && clazz.isInstance(msg)) {
byte[] bytes = JSONSerializer.serialize(msg);
out.writeInt(bytes.length);
out.writeBytes(bytes);
}
}
}
- 解码及反序列化
/**
* 消息解码器 ->
* 1、拆包将TCP协议缓冲区数据拆分
* 2、将byte[]类型的内容转为String或其他类型
*/
public class MessageDecoderHandler extends ByteToMessageDecoder {
int length = 0;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//需要将得到二进制字节码-> MyMessageProtocol 数据包(对象)
System.out.println(in);
if(in.readableBytes() >= 4) {
if (length == 0){
length = in.readInt();
}
if (in.readableBytes() < length) {
System.out.println("当前可读数据不够,继续等待。。");
return;
}
byte[] content = new byte[length];
if (in.readableBytes() >= length){
in.readBytes(content);
//封装成MyMessageProtocol对象,传递到下一个handler业务处理
//此处提现将具体内容反序列化的思想
MessageContent deserialize = JSONSerializer.deserialize(MessageContent.class, content);
System.out.println("【入口】:Step 1 接收消息:"+ deserialize);
out.add(deserialize);
}
length = 0;
}
}
}