============================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();@SneakyThrowspublic static void main(String[] args) {Bootstrap bootstrap = new Bootstrap();NioEventLoopGroup group = new NioEventLoopGroup();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<Channel>() {@Overrideprotected 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 连接和重连机制,实现了指数退避重连*/@Deprecatedpublic 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 {// 主机IPpublic 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 {@Overridepublic 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.获取二进制抽象 ByteBufByteBuf buffer = ctx.alloc().buffer();// 2.准备传递给服务器端的数据,并指定字符编码为UTF-8byte[] bytes = "你好,这是Netty客户端传递的数据".getBytes(StandardCharsets.UTF_8);// 3.填充数据到ByteBufbuffer.writeBytes(bytes);return buffer;}@Overridepublic 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)); // 去掉这个编码转换结果为:}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();}/*** 程序的健壮性考虑** @param ctx 通道处理器上下文* @param cause 抛出的错误信息*/@Overridepublic 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 {@SneakyThrowspublic 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>() {@Overrideprotected 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 {@Overridepublic 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;}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();}@Overridepublic 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
===============================客户端输出的日志==============================