##
零、 目录
- IM系统简介
- Netty 简介
- Netty 环境配置
- 服务端启动流程
- 实战: 客户端和服务端双向通信
- 数据传输载体ByteBuf介绍
- 客户端与服务端通信协议编解码
- 实现客户端登录
- 实现客户端与服务端收发消息
- pipeline与channelHandler
- 构建客户端与服务端pipeline
- 拆包粘包理论与解决方案
- channelHandler的生命周期
- 使用channelHandler的热插拔实现客户端身份校验
- 客户端互聊原理与实现
- 群聊的发起与通知
- 群聊的成员管理(加入与退出,获取成员列表)
- 群聊消息的收发及Netty性能优化
- 心跳与空闲检测
- 总结
- 扩展
###四、 服务端启动流程
-
服务端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); } }
- 首先看到,我们创建了两个NioEventLoopGroup ,这两个对象可以看做是传统IO线程的两大线程组:
- bossGroup: 表示监听端口 接收新连接的线程组 一般情况下 接口线程组配置一个线程即可 , NioEventLoopGroup 默认的个数为 CPU核数*2
- workerGroup: 表示处理每一条连接上的数据读写的线程组 , (这里不理解可以回顾上一小节 《Netty是什么》)
- 用生活中的例子来讲就是: 一个工厂要运作 , 必然要有一个老板 负责在外面接活 ,然后有很多员工负责具体干活 , 老板们就是bossGroup 可以是1个 也可以使多个 , 员工们就是worker . bossGroup就收连接 , 之后交给workerGroup 具体处理
- 接下来我们创建了一个引导类 ServerBootStrap , 这个类将引导我们进行服务端的启动工作。
- 我们通过 serverBootstrap.group(bossGroup, workerGroup) 来给引导类配置两大线程组 , 这个引导类的线程模型也就定型了
- 然后我们指定服务端的IO模型为 NIO , 我们通过serverBootstrap.channel(NioServerSocketChannel.class) 来指定IO模型 , 当然这里也可以有其他的选择 , 如果你想指定IO 模型为 BIO , 那么这里配置OioServerSocketChannel.class类型即可 , 一般不会那么做 , 因为Netty 的优势就在于NIO.
- 接着我们调用childHandler()方法 , 给这个引导类创建一个ChannelInitializer , 这里主要就是定义每条连接的数据读写、业务处理逻辑 , 不理解没关系 , 我们后面会详细分析 。 ChannelInitializer 这个类中 我们注意到有一个泛型参数NioSocketChannel , 这个类就是Netty对NIO类型的连接的抽象 , 而我们前面NioServerSocketChannel也是对NIO类型的连接的抽象 , NioServerSocketChannel 和NioSocketChannel的概念相当于BIO 模型中 ServerSocket 和Socket .
- 首先看到,我们创建了两个NioEventLoopGroup ,这两个对象可以看做是传统IO线程的两大线程组:
-
到这里我们最小化参数配置就完成了 , 总结一下 , 想要启动一个Netty服务端 , 我们需要指定三个类属性:线程模型、 IO模型、处理逻辑 , 有了这三者之后再调用 bind(8000) , 我们就可以在本地绑定一个8000端口启动起来 。
-
自动绑定递增端口
-
在上面代码中我们直接绑定了8000端口 , 接下来我们实现一个稍微复杂的逻辑: 我们指定一个起始端口 , 比如:1000 , 然后判断是否绑定成功 , 如果不成功就 绑定1001 , 直到成功为止。
-
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 -
-
-
服务端启动相关的其他方法
-
handler(): handler() 方法可以和之前分析的childHandler() 对应起来 ,childHandler() 用于指定处理新连接数据的业务逻辑 , handler用于指定服务端启动过程中的一些逻辑
serverBootstrap.handler(new ChannelInitializer<NioServerSocketChannel>() { @Override protected void initChannel(NioServerSocketChannel ch) throws Exception { System.out.println("服务端启动中..."); } });
-
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()+"服务端启动中..."); } });
-
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")); } });
-
childOption(): childOption方法可以给每条连接设置一些TCP底层相关的属性:
-
ChannelOption.SO_KEEPALIVE表示是否开启TCP底层心跳机制,true为开启
-
ChannelOption其他参数详解: https://www.cnblogs.com/googlemeoften/p/6082785.html
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); serverBootstrap.childOption(ChannelOption.SO_BACKLOG, 10);
-
-
option(): 给服务端channel 设置一些属性:
serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024) //表示系统用于存放已经完成三次握手的请求的队列的最大长度 , 如果建立连接频繁, 服务器处理创建新连接较慢, 可以适当调大这个参数。
-
-
总结
- 本节中我们首先学习了Netty的服务端启动流程 , 一句话来说就是: 创建一个引导类 , 然后给他设置线程模型 , 然后设置IO 模型 , 设置连接之后数据的业务处理逻辑 , 最后绑定端口启动服务。
- 然后我们学到了 引导类的bind 方法时异步的 , 我们可以通过这个异步机制来实现端口自动递增绑定 。
- 最后我们讨论了Netty服务端启动额外的参数 , 主要包括给服务端Channel或者客户端Channel设置属性值 , 设置底层TCP参数。
- 如果你觉得这里讲解比较简单 , 想要深入学习 ,传送门: https://coding.imooc.com/class/chapter/230.html#Anchor
-
疑问:
- 在传统的BIO 模型中 , 每接收一个新连接 就会创建一个新的线程 , 如果使用Netty 指定IO模型为NIO 则每接收一个新连接则会复用之前的线程处理业务逻辑 , 疑问: 如果使用Netty指定IO 模型为BIO 那么会复用之前的线程还是会创建一个新的线程?