基于Netty4.xx客户端与服务器端互读|写数据(一)

客户端代码:
============================netty客户端处理逻辑==============================

package org.jy.sso.websocket.stomp.push.netty.chat.system.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateFormatUtils;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler.FirstClientSendMsgHandler;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * Netty基于异步事件驱动的高性能通信框架:
 * 客户端重连处理逻辑
 */
@Slf4j
public class NettyClient {
    // 最大重试次数
    private static final int MAX_RETRY_NUM = 100;
    // 重试初始值
    private static final int INI_RETRY_NUM = 0;
    // 步长
    private static final int STEP_RETRY_NUM_LENGTH = 1;

    // 连接的端口

    private static final int NETTY_SERVER_PORT = 8000;
    // 主机IP
    private static final String NETTY_SERVER_HOST_IP = "127.0.0.1";

    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        NioEventLoopGroup group = new NioEventLoopGroup();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel channel) {
                        // 注册字符串编码器
                        //channel.pipeline().addLast(new StringEncoder());
                        // 在客户端添加一遍逻辑处理器,在客户建立连接成功后,向服务器端写数据
                        channel.pipeline().addLast(new FirstClientSendMsgHandler());
                    }
                });
        connect(bootstrap, NETTY_SERVER_HOST_IP, NETTY_SERVER_PORT, MAX_RETRY_NUM);

    }

    /**
     * https://www.cnblogs.com/fanguangdexiaoyuer/p/6132968.html
     *
     * @param bootstrap 客户端启动器
     * @param host      主机
     * @param port      端口
     * @param retryNum  指数退避重新连接的次数
     * @author yh19166
     * @deprecated 连接和重连机制,实现了指数退避重连
     */
    private static void connect(Bootstrap bootstrap, String host, int port, int retryNum) {
        bootstrap.connect(host, port).addListener(future -> {
            // 通过future.isSuccess() 判断是否连接成功
            if (future.isSuccess()) {
                System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " -->> 连接成功....");
            } else if (retryNum == INI_RETRY_NUM) {
                System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " -->> 重连次数已用完,放弃连接....");
            } else {
                // 第几次重连
                int order = (MAX_RETRY_NUM - retryNum) + STEP_RETRY_NUM_LENGTH;
                // 客户考虑重新连接的逻辑,分梯度连接......
                System.out.println("第 " + order + " 连接失败......");
                // 本次重连的间隔: 通过左移操作快速计算出重连时间间隔
                int delay = STEP_RETRY_NUM_LENGTH << order;
                System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " -->> 连接失败,第" + order + "次重新连接....");
                // 实现定时任务逻辑
                bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retryNum - STEP_RETRY_NUM_LENGTH), delay, TimeUnit.SECONDS);
            }
        });
    }

    /**
     * @param bootstrap 客户端启动类
     * @param host      主机
     * @param port      端口
     * @param retryNum  指数退避重新连接的次数
     * @author yh19166
     * @deprecated 连接和重连机制,实现了指数退避重连
     */
    private static void retryConnect(Bootstrap bootstrap, String host, int port, int retryNum) {
        bootstrap.connect(host, port).addListener(future -> {
            if (future.isSuccess()) {
                log.info("连接服务器成功!");
            } else if (retryNum == INI_RETRY_NUM) {
                log.error("重连次数已用完,放弃连接!");
            } else {
                // 第几次重连
                int order = (MAX_RETRY_NUM - retryNum) + STEP_RETRY_NUM_LENGTH;
                // 本次重连的间隔
                int delay = STEP_RETRY_NUM_LENGTH << order;
                log.error(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + ": 连接失败,第" + order + "次重新连接....");

                bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retryNum - STEP_RETRY_NUM_LENGTH), delay, TimeUnit.SECONDS);
            }
        });
    }

}

 客户端写|读服务器端的处理器:
 

package org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.apache.commons.lang.time.DateFormatUtils;
import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * 客户端向服务器端发送消息第一个处理器
 */
public class FirstClientSendMsgHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("=========================向服务器端写数据============================");
        System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + ": 客户端写出数据");
        // 1.获取数据
        ByteBuf buffer = getByteBuf(ctx);
        // 2.写数据
        ctx.channel().writeAndFlush(buffer);
    }

    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        // 1.获取二进制抽象 ByteBuf
        ByteBuf buffer = ctx.alloc().buffer();
        // 2.准备传递给服务器端的数据,并指定字符编码为UTF-8
        byte[] bytes = "你好,这是Netty客户端传递的数据".getBytes(StandardCharsets.UTF_8);
        // 3.填充数据到ByteBuf
        buffer.writeBytes(bytes);
        return buffer ;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("=========================读取到服务器端的数据============================");
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " : 服务器端读到的客户端传递过来的数据 -> "
                + byteBuf.toString(StandardCharsets.UTF_8)); // 去掉这个编码转换结果为:
    }
}

================================netty服务器端处理逻辑=====================

package org.jy.sso.websocket.stomp.push.netty.chat.system.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler.FirstServerReceiveDataHandler;

/**
 * 居于异步事件驱动高性能网络通信框架
 * VisualVM使用教程:
 * https://blog.csdn.net/weixin_42652596/article/details/122240268
 * 插件下载
 * https://visualvm.github.io/archive/uc/8u40/updates.html
 * // 插件的位置
 * D:\program_files\jdk_vm_tool
 */
public class NettyServer {
    public static void main(String[] args) {
        // 服务器端启动类
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // boss主线程(父线程)
        NioEventLoopGroup bossLoopGroup = new NioEventLoopGroup();
        // 工作线程(子线程)
        NioEventLoopGroup workerLoopGroup = new NioEventLoopGroup();
        serverBootstrap // 父子线程建立组连接
                .group(bossLoopGroup, workerLoopGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        // 初始化一个Channel(通道),pipeline(管道线)
                        // 字符串解码器
                        //ch.pipeline().addLast(new StringDecoder());
                        /*ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
                            @Override
                            protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
                                System.out.println("服务器端Netty接收到的消息: " + msg);
                            }
                        });*/

                        ch.pipeline().addLast(new FirstServerReceiveDataHandler());

                    }
                }).bind(8000);
    }
}

 服务器端读|写处理器:

package org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.apache.commons.lang3.time.DateFormatUtils;

import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * 服务器端接收客户端数据的处理器:
 * 服务器端接收客户端数据
 * 响应客户端请求,并向客户端写数据
 */
public class FirstServerReceiveDataHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object requestContent) {

        //接收客户端的数据逻辑
        System.out.println("============================接收客户端的数据=============================");
        ByteBuf byteBuf = (ByteBuf) requestContent;
        System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " : 服务器端读到的客户端传递过来的数据 -> "
                + byteBuf.toString(StandardCharsets.UTF_8)); // 去掉这个编码转换结果为:
        System.out.println("============================向客户端写数据=============================");
        ByteBuf response = getByteBuf(ctx);
        ctx.channel().writeAndFlush(response);


    }

    /**
     * 向客户端写响应数据
     *
     * @param ctx 通道处理器上下文
     * @return {@link io.netty.buffer.ByteBuf}
     */
    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        byte[] responseBytes = "你好,欢迎关注杨哥哥的微信公众号,<<财务自由耕耘者>>!".getBytes(StandardCharsets.UTF_8);
        ByteBuf buffer = ctx.alloc().buffer();
        buffer.writeBytes(responseBytes);
        return buffer;
    }
}

客户端测试日志如下:

23:18:23.516 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
23:18:23.516 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
23:18:23.516 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
23:18:23.531 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
23:18:23.531 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 65536
23:18:23.531 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
第 1 连接失败......
2023-05-22 23:18:24 -->> 连接失败,第1次重新连接....
第 2 连接失败......
2023-05-22 23:18:27 -->> 连接失败,第2次重新连接....
第 3 连接失败......
2023-05-22 23:18:32 -->> 连接失败,第3次重新连接....
2023-05-22 23:18:40 -->> 连接成功....
=========================向服务器端写数据============================
2023-05-22 23:18:40: 客户端写出数据
23:18:40.686 [nioEventLoopGroup-2-7] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 32768
23:18:40.686 [nioEventLoopGroup-2-7] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
23:18:40.686 [nioEventLoopGroup-2-7] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
23:18:40.686 [nioEventLoopGroup-2-7] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
23:18:40.702 [nioEventLoopGroup-2-7] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true
23:18:40.705 [nioEventLoopGroup-2-7] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@e5f4a2
=========================读取到服务器端的数据============================
2023-05-22 23:18:40 : 服务器端读到的客户端传递过来的数据 -> 你好,欢迎关注杨哥哥的微信公众号,<<财务自由耕耘者>>!

 服务器端测试日志如下:

23:18:35.467 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
23:18:35.467 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
23:18:35.467 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
23:18:35.483 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
23:18:35.483 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 65536
23:18:35.483 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
23:18:40.723 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 32768
23:18:40.723 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
23:18:40.724 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
23:18:40.724 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
23:18:40.739 [nioEventLoopGroup-3-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true
23:18:40.752 [nioEventLoopGroup-3-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@e80c32
============================接收客户端的数据=============================
2023-05-22 23:18:40 : 服务器端读到的客户端传递过来的数据 -> 你好,这是Netty客户端传递的数据
============================向客户端写数据=============================

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值