Netty-2-客户端启动流程

上文中说了服务端的启动流程以及一些额外的设置等, 本文再来详细看一下客户端的启动流程

NettyClient解析

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class NettyClient {
public static void main(String[] args) {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();

Bootstrap bootstrap = new Bootstrap();
bootstrap
// 1.指定线程模型
.group(workerGroup)
// 2.指定 IO 类型为 NIO
.channel(NioSocketChannel.class)
// 3.IO 处理逻辑
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
}
});
// 4.建立连接
bootstrap.connect("127.0.0.1", 80).addListener(future -> {
if (future.isSuccess()) {
System.out.println("连接成功!");
} else {
System.err.println("连接失败!");
}

});
}
}

跟服务端的启动流程是类似的,首先要创建一个引导类Bootstrap,跟服务端的引导类不同,服务端的是ServerBootstrap

线面第一步就是指定线程模型,驱动着连接的数据读写,类似第一篇文章中的IOClient.java类创建的线程

然后指定IO模型是NioSocketChannel,表示是IO模型是NIO, 这里也可以设置成OioSocketChannel,但是一般不会这么做

然后给引导类指定一个handler,主要就是定义连接的业务处理逻辑

最后一步,建立和服务端的连接,传入的参数是ip/域名,和端口号, 然后又加了个监听,监听是否连接成功.

总体大致流程如下:
image
图片来源,侵删

失败重连

在网络情况差的情况下,客户端第一次连接可能会连接失败,这个时候我们可能会尝试重新连接,而且要给定重试次数以及每次间隔时间

通常情况下,连接建立失败不会立即重新连接,而是会通过一个指数退避的方式,比如每隔 1 秒、2 秒、4 秒、8 秒,以 2 的幂次来建立连接,然后到达一定次数之后就放弃连接

我们把connect()方法抽出来,实现重新连接,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void connect(Bootstrap bootstrap, String host, int port, int retry) {
bootstrap.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
log.info("连接成功");
} else if (retry == 0) {
log.info("重试次数已经用完了,连接失败");
} else {
// 第几次重连
int order = (CommonConfig.MAX_RETRY - retry) + 1;
// 本次重连的间隔
int delay = 1 << order;
log.info("连接失败,进行第{}次重连", order);

bootstrap.config().group()
.schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit.SECONDS);
}
});
}

通过判断连接是否成功以及剩余重试次数,分别执行不同的逻辑.

如果连接成功,就输出连接成功,如果重试次数用完,就放弃,如果连接失败而且重试次数还没有用完的话,就计算下一次重连间隔delay,然后定期重连

最后,定时任务是调用bootstrap.config().group().schedule(),其中bootstrap.config()这个方法返回的是BootstrapConfig,他是对Bootstrap配置参数的抽象,然后bootstrap.config().group() 返回的就是我们在一开始的时候配置的线程模型workerGroup,调workerGroupschedule 方法即可实现定时任务逻辑

客户端启动时的一些其他功能

NettyClient.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Slf4j
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();

bootstrap
// 1.指定线程模型
.group(group)
// 2.指定 IO 类型为 NIO
.channel(NioSocketChannel.class)
// 3.IO 处理逻辑
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {

}
});

bootstrap
// attr() 给客户端的channel即NioServerSocketChannel指定一些自定义属性,通过channel.attr()取出该属性,给NioServerSocketChannel维护一个map
.attr(CommonConfig.CLIENT_NAME_KEY, CommonConfig.CLIENT_NAME_VALUE)
// option() 给连接设置一些 TCP 底层相关的属性
// ChannelOption.CONNECT_TIMEOUT_MILLIS 示连接的超时时间,超过这个时间还是建立不上的话则代表连接失败
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
// ChannelOption.SO_KEEPALIVE 表示是否开启 TCP 底层心跳机制,true 为开启
.option(ChannelOption.SO_KEEPALIVE, true)
// ChannelOption.TCP_NODELAY 表示是否开启Nagle算法,true表示关闭,false表示开启,通俗地说,如果要求高实时性,有数据发送时就马上发送,就关闭,如果需要减少发送次数减少网络交互,就开启.
.option(ChannelOption.TCP_NODELAY, true);


// 4. 建立连接
connect(bootstrap, CommonConfig.NETTY_HOST, CommonConfig.NETTY_PORT, CommonConfig.MAX_RETRY);
}


public static void connect(Bootstrap bootstrap, String host, int port, int retry) {
bootstrap.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
log.info("连接成功");
} else if (retry == 0) {
log.info("重试次数已经用完了,连接失败");
} else {
// 第几次重连
int order = (CommonConfig.MAX_RETRY - retry) + 1;
// 本次重连的间隔
int delay = 1 << order;
log.info("连接失败,进行第{}次重连", order);

bootstrap.config().group()
.schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit.SECONDS);
}
});
}
}

额外的一些方法及其功能都在代码的注释里了.