欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Netty实战 IM即时通讯系统(四)服务端启动流程

程序员文章站 2022-03-02 15:24:48
...

##

Netty实战 IM即时通讯系统(四)服务端启动流程

零、 目录

  1. IM系统简介
  • Netty 简介
  • Netty 环境配置
  • 服务端启动流程
  • 实战: 客户端和服务端双向通信
  • 数据传输载体ByteBuf介绍
  • 客户端与服务端通信协议编解码
  • 实现客户端登录
  • 实现客户端与服务端收发消息
  • pipeline与channelHandler
  • 构建客户端与服务端pipeline
  • 拆包粘包理论与解决方案
  • channelHandler的生命周期
  • 使用channelHandler的热插拔实现客户端身份校验
  • 客户端互聊原理与实现
  • 群聊的发起与通知
  • 群聊的成员管理(加入与退出,获取成员列表)
  • 群聊消息的收发及Netty性能优化
  • 心跳与空闲检测
  • 总结
  • 扩展

###四、 服务端启动流程

  1. 服务端Demo

     public class NettyServer {
         public static void main(String[] args) {
             NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
             NioEventLoopGroup workerGroup = new NioEventLoopGroup();
     
             ServerBootstrap serverBootstrap = new ServerBootstrap();
             serverBootstrap
                     .group(bossGroup, workerGroup)
                     .channel(NioServerSocketChannel.class)
                     .childHandler(new ChannelInitializer<NioSocketChannel>() {
                         protected void initChannel(NioSocketChannel ch) {
                         }
                     });
     
             serverBootstrap.bind(8000);
         }
     }
    
    1. 首先看到,我们创建了两个NioEventLoopGroup ,这两个对象可以看做是传统IO线程的两大线程组:
      1. bossGroup: 表示监听端口 接收新连接的线程组 一般情况下 接口线程组配置一个线程即可 , NioEventLoopGroup 默认的个数为 CPU核数*2
      2. workerGroup: 表示处理每一条连接上的数据读写的线程组 , (这里不理解可以回顾上一小节 《Netty是什么》)
      3. 用生活中的例子来讲就是: 一个工厂要运作 , 必然要有一个老板 负责在外面接活 ,然后有很多员工负责具体干活 , 老板们就是bossGroup 可以是1个 也可以使多个 , 员工们就是worker . bossGroup就收连接 , 之后交给workerGroup 具体处理
    2. 接下来我们创建了一个引导类 ServerBootStrap , 这个类将引导我们进行服务端的启动工作。
    3. 我们通过 serverBootstrap.group(bossGroup, workerGroup) 来给引导类配置两大线程组 , 这个引导类的线程模型也就定型了
    4. 然后我们指定服务端的IO模型为 NIO , 我们通过serverBootstrap.channel(NioServerSocketChannel.class) 来指定IO模型 , 当然这里也可以有其他的选择 , 如果你想指定IO 模型为 BIO , 那么这里配置OioServerSocketChannel.class类型即可 , 一般不会那么做 , 因为Netty 的优势就在于NIO.
    5. 接着我们调用childHandler()方法 , 给这个引导类创建一个ChannelInitializer , 这里主要就是定义每条连接的数据读写、业务处理逻辑 , 不理解没关系 , 我们后面会详细分析 。 ChannelInitializer 这个类中 我们注意到有一个泛型参数NioSocketChannel , 这个类就是Netty对NIO类型的连接的抽象 , 而我们前面NioServerSocketChannel也是对NIO类型的连接的抽象 , NioServerSocketChannel 和NioSocketChannel的概念相当于BIO 模型中 ServerSocket 和Socket .
  2. 到这里我们最小化参数配置就完成了 , 总结一下 , 想要启动一个Netty服务端 , 我们需要指定三个类属性:线程模型、 IO模型、处理逻辑 , 有了这三者之后再调用 bind(8000) , 我们就可以在本地绑定一个8000端口启动起来 。

  3. 自动绑定递增端口

    1. 在上面代码中我们直接绑定了8000端口 , 接下来我们实现一个稍微复杂的逻辑: 我们指定一个起始端口 , 比如:1000 , 然后判断是否绑定成功 , 如果不成功就 绑定1001 , 直到成功为止。

    2. serverBootStrap.bind() 这个方法时异步的 , 调用之后立即返回结果 , 但是并不知道是否绑定成功, 他的返回值是一个ChannelFuture , 我们可以给这个ChannelFuture 添加一个监听器 GenericFutureListener , 然后我们在GenericFutureListener 的operationComplete 方法里面 , 我们可以监听到端口是否绑定成功 , 接下来是检测端口是否绑定成功的代码:

      /**

      • 自动递增绑定有效端口

      • @author outman

      • */
        private static void bind(ServerBootstrap serverBootstrap, int port) {

        serverBootstrap.bind(port).addListener(new GenericFutureListener<Future<? super Void>>() {

         @Override
         public void operationComplete(Future<? super Void> future) throws Exception {
         	if(future.isSuccess()) {
         		System.out.println("成功绑定端口:"+port);
         	}else {
         		System.out.println("绑定端口失败:"+ port);
         		bind(serverBootstrap, port+1);
         	}
         	
         }
        

        });

      }
      执行结果: (我的mysql 占用了3306端口 , 所以3306绑定失败)
      绑定端口失败:3306
      成功绑定端口:3307

  4. 服务端启动相关的其他方法

    1. handler(): handler() 方法可以和之前分析的childHandler() 对应起来 ,childHandler() 用于指定处理新连接数据的业务逻辑 , handler用于指定服务端启动过程中的一些逻辑

       serverBootstrap.handler(new ChannelInitializer<NioServerSocketChannel>() {
      
       	@Override
       	protected void initChannel(NioServerSocketChannel ch) throws Exception {
       		System.out.println("服务端启动中...");	
       	}
       });
      
    2. attr(): attr()方法可以给服务端的channel , 也就是NioServerSocketChannel指定一些自定义属性 , 然后我们可以通过channel.attr()取出这个属性 , 比如 , 我们可以给服务端channel指定一个serverName 属性 , 属性值NettyServer , 其实说白了就是给NioServerSocketChannel维护了一个map:

       //设置服务端属性
       serverBootstrap.attr(AttributeKey.newInstance("serverName"), "NettyServer");
       
       serverBootstrap.handler(new ChannelInitializer<NioServerSocketChannel>() {
      
       	@Override
       	protected void initChannel(NioServerSocketChannel channel) throws Exception {
       		// 取出服务端属性
       		Attribute<Object> serverName = channel.attr(AttributeKey.valueOf("serverName") );
       		System.out.println( serverName.get()+"服务端启动中...");	
       	}
       });
      
    3. childAttr() : 可以通过childAttr 给每一条连接设置自定义属性

       //给连接设置自定义属性
       serverBootstrap.childAttr(AttributeKey.newInstance("clientName"), "NettyClient");
       
       serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
      
       	@Override
       	protected void initChannel(NioSocketChannel childChannel) throws Exception {
       		// 取出连接中的自定义属性
       		childChannel.attr(AttributeKey.valueOf("clientName"));
       		
       	}
       });
      
    4. childOption(): childOption方法可以给每条连接设置一些TCP底层相关的属性:

      1. ChannelOption.SO_KEEPALIVE表示是否开启TCP底层心跳机制,true为开启

      2. ChannelOption其他参数详解: https://www.cnblogs.com/googlemeoften/p/6082785.html

         serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
         serverBootstrap.childOption(ChannelOption.SO_BACKLOG, 10);
        
    5. option(): 给服务端channel 设置一些属性:

       serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024)
       //表示系统用于存放已经完成三次握手的请求的队列的最大长度 , 如果建立连接频繁, 服务器处理创建新连接较慢, 可以适当调大这个参数。
      
  5. 总结

    1. 本节中我们首先学习了Netty的服务端启动流程 , 一句话来说就是: 创建一个引导类 , 然后给他设置线程模型 , 然后设置IO 模型 , 设置连接之后数据的业务处理逻辑 , 最后绑定端口启动服务。
    2. 然后我们学到了 引导类的bind 方法时异步的 , 我们可以通过这个异步机制来实现端口自动递增绑定 。
    3. 最后我们讨论了Netty服务端启动额外的参数 , 主要包括给服务端Channel或者客户端Channel设置属性值 , 设置底层TCP参数。
    4. 如果你觉得这里讲解比较简单 , 想要深入学习 ,传送门: https://coding.imooc.com/class/chapter/230.html#Anchor
  6. 疑问:

    1. 在传统的BIO 模型中 , 每接收一个新连接 就会创建一个新的线程 , 如果使用Netty 指定IO模型为NIO 则每接收一个新连接则会复用之前的线程处理业务逻辑 , 疑问: 如果使用Netty指定IO 模型为BIO 那么会复用之前的线程还是会创建一个新的线程?
相关标签: Netty