IO编程(三)Netty编程入门(服务端,客户端流程)

简介

Netty 封装了 JDK 的 NIO,让你用得更爽,你不用再写一大堆复杂的代码了。 用官方正式的话来说就是:Netty 是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。

  1. 使用 JDK 自带的NIO需要了解太多的概念,编程复杂,一不小心 bug 横飞
  2. Netty 底层 IO 模型随意切换,而这一切只需要做微小的改动,改改参数,Netty可以直接从 NIO 模型变身为 IO 模型
  3. Netty 自带的拆包解包,异常检测等机制让你从NIO的繁重细节中脱离出来,让你只需要关心业务逻辑
  4. Netty 解决了 JDK 的很多包括空轮询在内的 Bug
  5. Netty 底层对线程,selector 做了很多细小的优化,精心设计的 reactor 线程模型做到非常高效的并发处理
  6. 自带各种协议栈让你处理任何一种通用协议都几乎不用亲自动手
  7. Netty 社区活跃,遇到问题随时邮件列表或者 issue
  8. Netty 已经历各大 RPC 框架,消息中间件,分布式通信中间件线上的广泛验证,健壮性无比强大

下面是netty的服务端启动流程

包含了端口占用后自动递增尝试端口

/**
 * @Author: xuxu
 * @Date: 2019/12/26 9:08
 */
public class NettyServer {
    public static void main(String[] args) {
        //表示监听端口,接收新连接的线程组
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        //表示处理每一条连接的数据读写的线程组
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        //服务端启动引导类
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        //引导类配置线程组
        serverBootstrap.group(bossGroup, workerGroup);
        //指定通道的io模型  nio 或者 bio 使用netty当然一般使用nio
        serverBootstrap.channel(NioServerSocketChannel.class);
        //定义每条连接的数据读写,业务逻辑
        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        System.out.println("处理当前连接数据");
                    }
                });
        //===给服务端channel设置一些属性
        //表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,
        //服务器处理创建新连接较慢,可以适当调大这个参数
        serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);

        //===给每条连接设置一些TCP底层相关的属性
        //表示是否开启TCP底层心跳机制,true为开启
        serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
        //表示是否开启Nagle算法,true表示关闭,false表示开启,通俗地说,如果要求高实时性,
        //有数据发送时就马上发送,就关闭,如果需要减少发送次数减少网络交互,就开启
        serverBootstrap.childOption(ChannelOption.TCP_NODELAY, false);

        //自动递增绑定端口 从8000开始如果8000连接失败则尝试8001...
        autoBind(serverBootstrap, 8000);


        //-----------------其他不常用配置---------------
        //定义服务端启动中逻辑一般很少用
        serverBootstrap.handler(new ChannelInitializer<NioServerSocketChannel>() {
            @Override
            protected void initChannel(NioServerSocketChannel nioServerSocketChannel) throws Exception {
                System.out.println("服务端启动中逻辑");
            }
        });
        //给服务端channel指定属性 相当于map 很少用
        serverBootstrap.attr(AttributeKey.newInstance("serverName"),"testServer");
        //和上面类似 给单条连接指定属性
        serverBootstrap.childAttr(AttributeKey.newInstance("childName"), "childName");
    }

    //方法1判断端口是否绑定成功 异步判断
    private static void bind(ServerBootstrap serverBootstrap){
        serverBootstrap.bind(8000).addListener(new GenericFutureListener<Future<? super Void>>() {
            public void operationComplete(Future<? super Void> future) throws Exception {
                if(future.isSuccess()){
                    System.out.println("绑定成功");
                }else{
                    System.out.println("绑定失败");
                }
            }
        });
    }

    //方法2自动递增绑定端口号,如果端口被占用则继续找下一个端口,下一个端口为在上一个端口上加1
    private static void autoBind(final ServerBootstrap serverBootstrap, final int port){
        serverBootstrap.bind(port).addListener(new GenericFutureListener<Future<? super Void>>() {
            public void operationComplete(Future<? super Void> future) throws Exception {
                if(future.isSuccess()){
                    System.out.println("当前端口号"+port+"绑定成功");
                }else{
                    System.out.println("当前端口号"+port+"绑定失败");
                    autoBind(serverBootstrap, port+1);
                }
            }
        });
    }
}

创建一个引导类,然后给他指定线程模型,IO模型,连接读写处理逻辑,绑定端口之后,服务端就启动起来了.

其中bossGroup可以理解成老板对外谈义务接单(接收新的客户端连接).

workerGroup代表工人,处理生产某个用户发来的订单(客户端发送消息,然后读取并响应逻辑).

如果端口被占用会自动尝试绑定下一个端口

netty客户端代码

包含了常见流程设置,以及连接失败后指定次数重连

/**
 * @Author: xuxu
 * @Date: 2019/12/26 10:19
 */
public class NettyClient {
    //设置连接失败最大尝试次数
    private static final int MAX_RETRY=5;

    public static void main(String[] args) {
        NioEventLoopGroup clientGroup = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        //指定线程模型
        bootstrap.group(clientGroup);
        //指定IO类型为nio
        bootstrap.channel(NioSocketChannel.class);
        //处理IO逻辑
        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                System.out.println("处理IO逻辑");
            }
        });
        //给连接设置一些 TCP 底层相关的属性
        //连接的超时时间,超过这个时间还是建立不上的话则代表连接失败
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
        //是否开启 TCP 底层心跳机制,true 为开启
        bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
        //是否开始 Nagle 算法,true 表示关闭,false 表示开启,通俗地说,如果要求高实时性,有数据发送时就马上发送,
        // 就设置为 true 关闭,如果需要减少发送次数减少网络交互,就设置为 false 开启
        bootstrap.option(ChannelOption.TCP_NODELAY, true);
       //连接
        connect(bootstrap, "127.0.0.1",8000,MAX_RETRY);
    }

    private static void connect(final Bootstrap bootstrap, final String host, final int port, final int retry){
        //因为内部类使用外部变量必须用final修饰,无法改变局部变量retry值,所以采用lambda表达式写法
        /*bootstrap.connect("127.0.0.1", 8000).addListener(new GenericFutureListener<Future<? super Void>>() {
            public void operationComplete(Future<? super Void> future) throws Exception {
                if(future.isSuccess()){
                    System.out.println("连接成功");
                }else if(retry==0){
                    System.out.println("重连次数已经耗尽");
                }else{
                    System.out.println("连接失败");
                    //失败后递归调用重连
                }
            }
        });*/
        bootstrap.connect(host, port).addListener(future->{
            if(future.isSuccess()){
                System.out.println("连接成功!");
            }else if(retry==0){
                System.out.println("重连已达最大次数");
            }else{
                //当前尝试重连次数
                int num = MAX_RETRY-retry+1;
                //本次重试时间间隔(指数次 2,4,8...)
                int interval = 1<<num;
                new Date();
                System.out.println("连接失败,第"+num+"次尝试重连,时间:"+(new Date()));
                //group组定时任务
                bootstrap.config().group().schedule(()->{connect(bootstrap, host, port,retry-1);}, interval, TimeUnit.SECONDS);
            }
        });
    }
}

创建一个引导类,然后给他指定线程模型,IO 模型,连接读写处理逻辑,连接上特定主机和端口,客户端就启动起来了

connect 方法是异步的,我们可以通过这个异步回调机制来实现指数退避重连逻辑。

如果连接失败如下

发布了229 篇原创文章 · 获赞 49 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/baiyan3212/article/details/103699715
今日推荐