网络编程-Netty-03 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;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旧梦昂志

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值