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

##

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() {
                         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>() {

         @Override
         public void operationComplete(Future 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() {
      
       	@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() {
      
       	@Override
       	protected void initChannel(NioServerSocketChannel channel) throws Exception {
       		// 取出服务端属性
       		Attribute serverName = channel.attr(AttributeKey.valueOf("serverName") );
       		System.out.println( serverName.get()+"服务端启动中...");	
       	}
       });
        
           
    3. childAttr() : 可以通过childAttr 给每一条连接设置自定义属性

       //给连接设置自定义属性
       serverBootstrap.childAttr(AttributeKey.newInstance("clientName"), "NettyClient");
       
       serverBootstrap.childHandler(new ChannelInitializer() {
      
       	@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)
       //表示系统用于存放已经完成三次握手的请求的队列的最大长度 , 如果建立连接频繁, 服务器处理创建新连接较慢, 可以适当调大这个参数。
      
    6. 总结

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

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