============================System.exit()方法=============================
原型:System.exit(int status)
其功能主要是调用Runtime.getRuntime().exit(status);
作用是终止当前正在运行的Java虚拟机,这个status表示退出的状态码,非零表示异常终止。(可以返回给其他进程的调用者一个调用的返回码,以根据返回码采取不同的策略。)
注意:不管status为何值程序都会退出,和return 相比有不同的是:return是回到上一层,而System.exit(status)是回到最上层。
System.exit(0):
- 正常退出,程序正常执行结束退出,Java GC进行垃圾回收,直接退出。
- 在Swing开发中,一般用于Swing窗体关闭按钮。(重写windowClosing方法时调用System.exit(0)来终止程序,Window类的dispose()方法只是关闭窗口,并不会让程序退出)。
System.exit(1):
- 是非正常退出,就是说无论程序正在执行与否,都退出.
- 如果为非0的话,如果这个方法被调用后,虚拟机已开始关闭序列如果关闭钩子正在运行,此方法将无限期阻塞。如果关将钩子运行完成,并且未调用的finalizers,在finalization-on-exit允许的情况下启动回收完成,虚拟机停止。
- 一般在catch块中会使用(例如使用Apache的FTPClient类时,源码中推荐使用System.exit(1)告知连接失败),当程序会被脚本调用、父进程调用发生异常时需要通过System.exit(1)来告知操作失败,默认程序最终返回的值返是0,即然发生异常默认还是返回0,因此在这种情况下需要手工指定返回非零。
===============================Nettty代码===================================
Netty客户端:
package org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateFormatUtils;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler.FirstClientSendMsgHandler;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatcommond.ChatRetryCommand;
import org.jy.sso.websocket.stomp.push.netty.chat.system.constant.ChatClientConstant;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* IM聊天系统客户端
* <p>
* 工具主要是封装了java.util.Random对象,提供了一些产生随机数或者随机字符串的方法。随机工具类是RandomUtil,里面的方法都是静态方法。
*/
@Slf4j
public class ChatCenterClient {
public static final ConcurrentHashMap<Byte,Bootstrap> bootStrapMap = new ConcurrentHashMap();
@SneakyThrows
public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) {
// 在客户端添加一遍逻辑处理器,在客户建立连接成功后,向服务器端写数据
channel.pipeline().addLast(new FirstClientSendMsgHandler());
}
});
connect(bootstrap, ChatClientConstant.CHAT_SERVER_HOST_IP, ChatClientConstant.CHAT_SERVER_PORT, ChatClientConstant.CHAT_MAX_RETRY_NUM);
// 重连指令与对应客户端启动的实例映射关系
bootStrapMap.put(ChatRetryCommand.CHAT_RETRY_CONNECTION, bootstrap);
}
/**
* @param bootstrap 客户端启动器
* @param host 主机
* @param port 端口
* @param retryNum 指数退避重新连接的次数
* @author yh19166
* @deprecated 连接和重连机制,实现了指数退避重连
*/
private static void connect(Bootstrap bootstrap, String host, int port, int retryNum) {
bootstrap.connect(host, port).addListener(future -> {
// 通过future.isSuccess() 判断是否连接成功
if (future.isSuccess()) {
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " -->> 连接成功....");
} else if (retryNum == ChatClientConstant.CHAT_INI_RETRY_NUM) {
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " -->> 重连次数已用完,放弃连接....");
/**
* 原型:System.exit(int status)
*
* 其功能主要是调用Runtime.getRuntime().exit(status);
*
* 作用是终止当前正在运行的Java虚拟机,这个status表示退出的状态码,非零表示异常终止。(可以返回给其他进程的调用者一个调用的返回码,以根据返回码采取不同的策略。)
*
* 注意:不管status为何值程序都会退出,和return 相比有不同的是:return是回到上一层,而System.exit(status)是回到最上层。
*=======================================================================================================================================================
* System.exit(0):
*
* 正常退出,程序正常执行结束退出,Java GC进行垃圾回收,直接退出。
* 在Swing开发中,一般用于Swing窗体关闭按钮。(重写windowClosing方法时调用System.exit(0)来终止程序,Window类的dispose()方法只是关闭窗口,并不会让程序退出)。
* System.exit(1):
*
* 是非正常退出,就是说无论程序正在执行与否,都退出.
* 如果为非0的话,如果这个方法被调用后,虚拟机已开始关闭序列如果关闭钩子正在运行,此方法将无限期阻塞。如果关将钩子运行完成,并且未调用的finalizers,在finalization-on-exit允许的情况下启动回收完成,虚拟机停止。
* 一般在catch块中会使用(例如使用Apache的FTPClient类时,源码中推荐使用System.exit(1)告知连接失败),当程序会被脚本调用、父进程调用发生异常时需要通过System.exit(1)来告知操作失败,默认程序最终返回的值返是0,即然发生异常默认还是返回0,因此在这种情况下需要手工指定返回非零。
*/
System.exit(0);
} else {
// 第几次重连
int order = (ChatClientConstant.CHAT_MAX_RETRY_NUM - retryNum) + ChatClientConstant.CHAT_STEP_RETRY_LENGTH;
// 客户考虑重新连接的逻辑,分梯度连接......
System.out.println("第 " + order + " 连接失败......");
// 本次重连的间隔: 通过左移操作快速计算出重连时间间隔
int delay = ChatClientConstant.CHAT_STEP_RETRY_LENGTH << order;
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " -->> 连接失败,第" + order + "次重新连接....");
// 实现定时任务逻辑
bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retryNum - ChatClientConstant.CHAT_STEP_RETRY_LENGTH), delay, TimeUnit.SECONDS);
}
});
}
/**
* @param bootstrap 客户端启动类
* @param host 主机
* @param port 端口
* @param retryNum 指数退避重新连接的次数
* @author yh19166
* @deprecated 连接和重连机制,实现了指数退避重连
*/
@Deprecated
public static void retryConnect(Bootstrap bootstrap, String host, int port, int retryNum) {
bootstrap.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
log.info("连接服务器成功!");
} else if (retryNum == ChatClientConstant.CHAT_INI_RETRY_NUM) {
log.error("重连次数已用完,放弃连接!");
/**
* 原型:System.exit(int status)
*
* 其功能主要是调用Runtime.getRuntime().exit(status);
*
* 作用是终止当前正在运行的Java虚拟机,这个status表示退出的状态码,非零表示异常终止。(可以返回给其他进程的调用者一个调用的返回码,以根据返回码采取不同的策略。)
*
* 注意:不管status为何值程序都会退出,和return 相比有不同的是:return是回到上一层,而System.exit(status)是回到最上层。
*=======================================================================================================================================================
* System.exit(0):
*
* 正常退出,程序正常执行结束退出,Java GC进行垃圾回收,直接退出。
* 在Swing开发中,一般用于Swing窗体关闭按钮。(重写windowClosing方法时调用System.exit(0)来终止程序,Window类的dispose()方法只是关闭窗口,并不会让程序退出)。
* System.exit(1):
*
* 是非正常退出,就是说无论程序正在执行与否,都退出.
* 如果为非0的话,如果这个方法被调用后,虚拟机已开始关闭序列如果关闭钩子正在运行,此方法将无限期阻塞。如果关将钩子运行完成,并且未调用的finalizers,在finalization-on-exit允许的情况下启动回收完成,虚拟机停止。
* 一般在catch块中会使用(例如使用Apache的FTPClient类时,源码中推荐使用System.exit(1)告知连接失败),当程序会被脚本调用、父进程调用发生异常时需要通过System.exit(1)来告知操作失败,默认程序最终返回的值返是0,即然发生异常默认还是返回0,因此在这种情况下需要手工指定返回非零。
*/
System.exit(0);
} else {
// 第几次重连
int order = (ChatClientConstant.CHAT_MAX_RETRY_NUM - retryNum) + ChatClientConstant.CHAT_STEP_RETRY_LENGTH;
// 本次重连的间隔
int delay = ChatClientConstant.CHAT_STEP_RETRY_LENGTH << order;
log.error(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + ": 连接失败,第" + order + "次重新连接....");
bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retryNum - ChatClientConstant.CHAT_STEP_RETRY_LENGTH), delay, TimeUnit.SECONDS);
}
});
}
}
代码中用到的常量类:
package org.jy.sso.websocket.stomp.push.netty.chat.system.constant;
public class ChatClientConstant {
// 主机IP
public static final String CHAT_SERVER_HOST_IP = "127.0.0.1";
// 连接的端口
public static final int CHAT_SERVER_PORT = 8000;
// 最大重试次数
public static final int CHAT_MAX_RETRY_NUM = 4;
// 重试初始值
public static final int CHAT_INI_RETRY_NUM = 0;
// 步长
public static final int CHAT_STEP_RETRY_LENGTH = 1;
}
客户端处理器:
package org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateFormatUtils;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter.ChatCenterClient;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatcommond.ChatRetryCommand;
import org.jy.sso.websocket.stomp.push.netty.chat.system.constant.ChatClientConstant;
import java.nio.charset.StandardCharsets;
import java.util.Date;
/**
* 客户端向服务器端发送消息第一个处理器
*/
@Slf4j
public class FirstClientSendMsgHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("=========================向服务器端写数据============================");
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + ": 客户端写出数据");
// 1.获取数据
ByteBuf buffer = getByteBuf(ctx);
// 2.写数据
ctx.channel().writeAndFlush(buffer);
}
private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
// 1.获取二进制抽象 ByteBuf
ByteBuf buffer = ctx.alloc().buffer();
// 2.准备传递给服务器端的数据,并指定字符编码为UTF-8
byte[] bytes = "你好,这是Netty客户端传递的数据".getBytes(StandardCharsets.UTF_8);
// 3.填充数据到ByteBuf
buffer.writeBytes(bytes);
return buffer;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("=========================读取到服务器端的数据============================");
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " : 服务器端读到的客户端传递过来的数据 -> "
+ byteBuf.toString(StandardCharsets.UTF_8)); // 去掉这个编码转换结果为:
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
/**
* 程序的健壮性考虑
*
* @param ctx 通道处理器上下文
* @param cause 抛出的错误信息
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 客户端没有正常退出导致的异常: 远程主机强迫关闭了一个现有的连接
// 服务器端突然关闭,抛出该异常: 远程主机强迫关闭了一个现有的连接
ctx.channel().flush();
ctx.channel().closeFuture();
log.info("FirstClientSendMsgHandler.exceptionCaught : [{}]", cause.getMessage());
Bootstrap Bootstrap = ChatCenterClient.bootStrapMap.get(ChatRetryCommand.CHAT_RETRY_CONNECTION);
ChatCenterClient.retryConnect(Bootstrap, ChatClientConstant.CHAT_SERVER_HOST_IP, ChatClientConstant.CHAT_SERVER_PORT, ChatClientConstant.CHAT_MAX_RETRY_NUM);
}
}
==================================Netty服务器端==============================
package org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.SneakyThrows;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler.FirstServerReceiveDataHandler;
/**
* IM服务器端
*/
public class ChatCenterServer {
@SneakyThrows
public static void main(String[] args) {
// 服务器端启动类
ServerBootstrap serverBootstrap = new ServerBootstrap();
// boss主线程(父线程)
NioEventLoopGroup bossLoopGroup = new NioEventLoopGroup();
// 工作线程(子线程)
NioEventLoopGroup workerLoopGroup = new NioEventLoopGroup();
serverBootstrap // 父子线程建立组连接
.group(bossLoopGroup, workerLoopGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new FirstServerReceiveDataHandler());
}
}).bind(8000).sync();
}
}
服务器端Pipeline对应的通道处理器handler:
package org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.nio.charset.StandardCharsets;
import java.util.Date;
/**
* 服务器端接收客户端数据的处理器:
* 服务器端接收客户端数据
* 响应客户端请求,并向客户端写数据
*/
@Slf4j
public class FirstServerReceiveDataHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object requestContent) {
//接收客户端的数据逻辑
System.out.println("============================接收客户端的数据=============================");
ByteBuf byteBuf = (ByteBuf) requestContent;
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " : 服务器端读到的客户端传递过来的数据 -> "
+ byteBuf.toString(StandardCharsets.UTF_8)); // 去掉这个编码转换结果为:
System.out.println("============================向客户端写数据=============================");
ByteBuf response = getByteBuf(ctx);
ctx.channel().writeAndFlush(response);
}
/**
* 向客户端写响应数据
*
* @param ctx 通道处理器上下文
* @return {@link io.netty.buffer.ByteBuf}
*/
private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
byte[] responseBytes = "你好,欢迎关注杨哥哥的微信公众号,<<财务自由耕耘者>>!".getBytes(StandardCharsets.UTF_8);
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeBytes(responseBytes);
return buffer;
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 客户端未正常的关闭,抛出异常: 主机强迫关闭了一个现有的连接
ctx.channel().flush();
ctx.channel().closeFuture();
log.info("exception: [{}]",cause.getMessage());
// 可以考虑重启服务端,重新启动|重连区别
}
}
测试效果日下:
====================== 先运行客户端代码============不运行服务器端代码=====================
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.tinyCacheSize: 512
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.smallCacheSize: 256
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
20:04:33.448 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
20:04:33.448 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 65536
20:04:33.448 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
第 1 连接失败......
2023-05-24 20:04:34 -->> 连接失败,第1次重新连接....
第 2 连接失败......
2023-05-24 20:04:37 -->> 连接失败,第2次重新连接....
第 3 连接失败......
2023-05-24 20:04:42 -->> 连接失败,第3次重新连接....
第 4 连接失败......
2023-05-24 20:04:51 -->> 连接失败,第4次重新连接....
2023-05-24 20:05:08 -->> 重连次数已用完,放弃连接....
Process finished with exit code 0
=============================客户端输出的日志=======================================
======先运行服务器端代码,在运行客户端代码,待客户端连接上服务端后,再关闭服务器端==============
20:27:31.730 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
20:27:31.730 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
20:27:31.730 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
20:27:31.748 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
20:27:31.748 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 65536
20:27:31.748 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
2023-05-24 20:27:31 -->> 连接成功....
=========================向服务器端写数据============================
2023-05-24 20:27:31: 客户端写出数据
20:27:31.801 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 32768
20:27:31.801 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
20:27:31.801 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
20:27:31.809 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
20:27:31.841 [nioEventLoopGroup-2-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true
20:27:31.841 [nioEventLoopGroup-2-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@5f1d79
=========================读取到服务器端的数据============================
2023-05-24 20:27:31 : 服务器端读到的客户端传递过来的数据 -> 你好,欢迎关注杨哥哥的微信公众号,<<财务自由耕耘者>>!
20:27:37.561 [nioEventLoopGroup-2-1] INFO org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler.FirstClientSendMsgHandler - FirstClientSendMsgHandler.exceptionCaught : [远程主机强迫关闭了一个现有的连接。]
20:27:38.569 [nioEventLoopGroup-2-2] ERROR org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter.ChatCenterClient - 2023-05-24 20:27:38: 连接失败,第1次重新连接....
第 2 连接失败......
2023-05-24 20:27:41 -->> 连接失败,第2次重新连接....
第 3 连接失败......
2023-05-24 20:27:46 -->> 连接失败,第3次重新连接....
第 4 连接失败......
2023-05-24 20:27:55 -->> 连接失败,第4次重新连接....
2023-05-24 20:28:12 -->> 重连次数已用完,放弃连接....
Process finished with exit code 0
===============================客户端输出的日志==============================