1.前言
上一节,我们主要讲解了一个Netty入门案例,其中无论是客户端还是服务端的代码编写,都是分为 2 个核心步骤,分别是:启动类模板代码 + 自定义业务 Handler,其中 Handler 可以根据不同的业务定义多个。
本节主要介绍 Bootstrap 客户端启动类的代码含义。
2. Bootstrap流程
客户端启动类的写法都是固定模板的写法,需要掌握几个核心的流程,有助于理解模板代码,具体如下:
- 指定线程模型: 通过
.group(group)
给引导类(Bootstrap)配置线程组,这个引导类的线程模型也就定型了。每个通道,也就是 Channel 都需要绑定一个线程,该线程是线程池分配的线程,专门负责处理相关 Handler; - 指定 IO 模型: 我们通过
.channel(NioServerSocketChannel.class)
来指定 NIO 模型。如果指定 IO 模型为 BIO,那么这里配置上OioServerSocketChannel.class
类型即可,通常都是使用 NIO,因为 Netty 的优势就在于 NIO; - 指定处理逻辑器: 通过 childHandler () 方法,给这个引导类创建一个 ChannelInitializer,这里主要就是管理自定义 Handler,最终把这些 Handler 组装成一条双向链表,Channel 有事件时则触发链表进行业务处理逻辑;
- 连接服务端:调用 connect () 连接服务端,需要传递两个参数,分别是服务端的 IP 地址和端口号。
3. 核心方法
方法 | 说明 |
group() | 指定线程模型 |
channel() | 指定 IO 模型 |
attr() | 给客户端 Channel 设置自定义属性 |
handler() | 给客户端 Channel 指定处理逻辑 Handler |
option() | 给客户端 Channel 设置底层 TCP 的属性 |
connect() | 连接服务端,返回 ChannelFuture |
需要指定每个方法的功能是什么,下面将讲解其具体使用。
4. 核心方法详解
4.1 connect()
connect () 用来连接服务端。
常见的运用场景主要有三点,分别是
①监听连接结果;
②失败重连;
③断开重连。
4.1.1 连接监听
connect () 方法返回的是 ChannelFuture,也就是说不需要等待连接成功或失败才往下执行代码,后期可以监听连接结果。
//1.连接Netty服务端
ChannelFuture future=bootstrap.connect("127.0.0.1",80);
//2.监听连接结果
future.addListener(future -> {
if (future.isSuccess()) {
System.out.println("连接成功!");
} else {
System.err.println("连接失败!");
}
});
总结,这种模式的好处是,连接是异步的,无需等待连接响应代码才会往下执行。
4.1.2 失败重连
在网络情况差的情况下,客户端第一次连接可能会连接失败,这个时候我们可能会尝试重新连接,具体实现如下:
方案一: 通过 ChannelFuture 的返回状态来监听连接是否成功。
private static void connect(Bootstrap bootstrap, String host, int port) {
bootstrap.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
System.out.println("连接成功!");
} else {
System.err.println("连接失败,开始重连");
//递归调用连接方法
connect(bootstrap, host, port);
}
});
}
方案二: 避免短时间内频繁的请求连接,可以使用定时线程池来每隔 n 秒重连一次。
private static void connect(Bootstrap bootstrap, String host, int port) {
bootstrap.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
System.out.println("连接成功!");
} else {
//获取EventLoopGroup
EventLoopGroup thread=bootstrap.config().group();
//每隔5秒钟重连一次
thread.schedule(new Runnable() {
public void run() {
connect(bootstrap, host, port)
}
}, 5, TimeUnit.SECONDS);
}
});
}
代码说明:
bootstrap.config().group()
获取的 EventLoopGroup,它是一个线程池,线程池里面有一个叫定时线程池。
4.2 attr()
attr () 方法可以给客户端 Channel 初始属性,也就是 NioSocketChannel 绑定自定义属性,然后我们可以通过 channel.attr()
取出这个属性。其实就是给 NioSocketChannel 维护一个 map。
给 Channel 绑定属性。
//省略其它代码,只保留核心部分
bootstrap.attr(AttributeKey.newInstance("token"), "123");
取出 Channel 所绑定的属性。
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
//客户端启动的时候,触发事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//获取Channel的绑定的属性值
AttributeKey<String> key=AttributeKey.valueOf("token");
String value=ctx.channel().attr(key).get();
}
}
通过以上代码,我们介绍了 attr () 如何绑定属性和取出属性。
4.3 option()
可以通过 option () 方法可以给连接设置一些 TCP 底层相关的属性,以下是常见的三种 TCP 属性设置。
//省略其它代码,只保留核心部分
bootstrap
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
代码说明:
ChannelOption.CONNECT_TIMEOUT_MILLIS
表示连接的超时时间,超过这个时间还是建立不上的话则代表连接失败;ChannelOption.SO_KEEPALIVE
表示是否开启 TCP 底层心跳机制,true 为开启;ChannelOption.TCP_NODELAY
表示是否开始 Nagle 算法,true 表示关闭,false 表示开启,通俗地说,如果要求高实时性,有数据发送时就马上发送,就设置为 true 关闭,如果需要减少发送次数减少网络交互,就设置为 false 开启
5. 问题?
什么是线程模型?
什么是IO模型?