网页制作与网站建设期末考试永久免费自助建站软件
目录
- 1.目的
- 2.Netty是什么
- 2.1.Netty和Tomcat的区别
- 2.2.Netty为什么流行
- 2.2.1并发高
- 2.2.2.传输快
- 2.2.3.封装好
- 3.基于Netty实现WebSocket聊天室
- 3.1创建simple_webchat项目
- 3.2编写代码
- 4.结果
1.目的
- 学习和了解Netty的应用场景和使用方式
2.Netty是什么
- Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。
2.1.Netty和Tomcat的区别
- Netty和Tomcat最大的区别就在于通信协议,Tomcat是基于Http协议的。Netty能通过编程自定义各种协议,因为netty能够通过codec自己来编码/解码字节流,完成类似redis访问的功能。
2.2.Netty为什么流行
2.2.1并发高
- 对比于BIO(Blocking I/O,阻塞IO),Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架。当一个连接建立之后,NIO有两个步骤要做(接收完客户端发过来的全部数据、处理完请求业务之后返回response给客户端),NIO和BIO的区别主要是在第一步。在BIO中,等待客户端发数据这个过程是阻塞的,一个线程只能处理一个请求而最大线程数是有限的,BIO不能支持高并发。
- 而NIO中,当一个Socket建立好之后,Thread并不会阻塞去接收这个Socket,而是将这个请求交给Selector,Selector会不断的去遍历所有的Socket,一旦有一个Socket建立完成,他会通知Thread,然后Thread处理完数据再返回给客户端——这个过程是不阻塞的。
2.2.2.传输快
- 依赖NIO的一个特性——零拷贝。Java的内存有堆内存、栈内存和字符串常量池等等,其中堆内存是Java对象存放的地方,一般我们的数据如果需要从IO读取到堆内存,中间需要经过Socket缓冲区,也就是说一个数据会被拷贝两次才能到达他的的终点。针对这种情况,当Netty需要接收数据的时候,他会在堆内存之外开辟一块内存,数据就直接从IO读到了那块内存中去,在netty里面通过ByteBuf可以直接对这些数据进行直接操作,从而加快了传输速度。
2.2.3.封装好
3.基于Netty实现WebSocket聊天室
3.1创建simple_webchat项目
3.2编写代码
- pom.xml
<dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.23</version></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.6.Final</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.1.7</version></dependency>
</dependencies>
- Server
public interface Server {void start();void shutdown();
}
- BaseServer
public abstract class BaseServer implements Server{protected Logger logger = LoggerFactory.getLogger(getClass());protected String host = "localhost";protected int port = 8099;/*** 1. NioEventLoopGroup是用来处理I/O操作的多线程事件循环器,* 在这个例子中我们实现了一个服务端的应用,因此会有2个 NioEventLoopGroup 会被使用。* 第一个经常被叫做‘boss’,用来接收进来的连接。第二个经常被叫做‘worker’,* 用来处理已经被接收的连接,一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上*/protected DefaultEventLoopGroup defLoopGroup;protected NioEventLoopGroup bossGroup;protected NioEventLoopGroup workGroup;protected NioServerSocketChannel ssch;protected ChannelFuture cf;/*** 2.ServerBootstrap是一个启动 NIO 服务的辅助启动类。* 你可以在这个服务中直接使用 Channel*/protected ServerBootstrap b;public void init(){defLoopGroup = new DefaultEventLoopGroup(8, new ThreadFactory() {private AtomicInteger index = new AtomicInteger(0);@Overridepublic Thread newThread(Runnable r) {return new Thread(r, "DEFAULTEVENTLOOPGROUP_" + index.incrementAndGet());}});bossGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {private AtomicInteger index = new AtomicInteger(0);@Overridepublic Thread newThread(Runnable r) {return new Thread(r, "BOSS_" + index.incrementAndGet());}});workGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 10, new ThreadFactory() {private AtomicInteger index = new AtomicInteger(0);@Overridepublic Thread newThread(Runnable r) {return new Thread(r, "WORK_" + index.incrementAndGet());}});b = new ServerBootstrap();}@Overridepublic void shutdown() {if (defLoopGroup != null) {defLoopGroup.shutdownGracefully();}bossGroup.shutdownGracefully();workGroup.shutdownGracefully();}
}
- UserInfo
public class UserInfo {private static AtomicInteger uidGener = new AtomicInteger(1000);private boolean isAuth = false; // 是否认证private long time = 0; // 登录时间private int userId; // UIDprivate String nick; // 昵称private String addr; // 地址private Channel channel;// 通道//其他get和set方法直接生成就行public void setUserId() {this.userId = uidGener.incrementAndGet();}
- MessageHandler
/*** 1.SimpleChannelInboundHandler实现了ChannelInboundHandler接口,* 提供了许多事件处理的接口方法,然后你可以覆盖这些方法*/
public class MessageHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {private static final Logger logger = LoggerFactory.getLogger(MessageHandler.class);@Overrideprotected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame)throws Exception {UserInfo userInfo = UserInfoManager.getUserInfo(ctx.channel());if (userInfo != null && userInfo.isAuth()) {JSONObject json = JSONObject.parseObject(frame.text());// 广播返回用户发送的消息文本UserInfoManager.broadcastMess(userInfo.getUserId(), userInfo.getNick(), json.getString("mess"));}}@Overridepublic void channelUnregistered(ChannelHandlerContext ctx) throws Exception {UserInfoManager.removeChannel(ctx.channel());UserInfoManager.broadCastInfo(ChatCode.SYS_USER_COUNT,UserInfoManager.getAuthUserCount());super.channelUnregistered(ctx);}
}
- UserAuthHandler
/*** 1.SimpleChannelInboundHandler实现了ChannelInboundHandler接口,* 提供了许多事件处理的接口方法,然后你可以覆盖这些方法*/
public class UserAuthHandler extends SimpleChannelInboundHandler<Object> {private static final Logger logger = LoggerFactory.getLogger(UserAuthHandler.class);private WebSocketServerHandshaker handshaker;@Overrideprotected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {if (msg instanceof FullHttpRequest) {handleHttpRequest(ctx, (FullHttpRequest) msg);} else if (msg instanceof WebSocketFrame) {handleWebSocket(ctx, (WebSocketFrame) msg);}}@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof IdleStateEvent) {IdleStateEvent evnet = (IdleStateEvent) evt;// 判断Channel是否读空闲, 读空闲时移除Channelif (evnet.state().equals(IdleState.READER_IDLE)) {final String remoteAddress = NettyUtil.parseChannelRemoteAddr(ctx.channel());logger.warn("NETTY SERVER PIPELINE: IDLE exception [{}]", remoteAddress);UserInfoManager.removeChannel(ctx.channel());UserInfoManager.broadCastInfo(ChatCode.SYS_USER_COUNT,UserInfoManager.getAuthUserCount());}}ctx.fireUserEventTriggered(evt);}private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {if (!request.decoderResult().isSuccess() || !"websocket".equals(request.headers().get("Upgrade"))) {logger.warn("protobuf don't support websocket");ctx.channel().close();return;}WebSocketServerHandshakerFactory handshakerFactory = new WebSocketServerHandshakerFactory(Constants.WEBSOCKET_URL, null, true);handshaker = handshakerFactory.newHandshaker(request);if (handshaker == null) {WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());} else {// 动态加入websocket的编解码处理handshaker.handshake(ctx.channel(), request);UserInfo userInfo = new UserInfo();userInfo.setAddr(NettyUtil.parseChannelRemoteAddr(ctx.channel()));// 存储已经连接的ChannelUserInfoManager.addChannel(ctx.channel());}}private void handleWebSocket(ChannelHandlerContext ctx, WebSocketFrame frame) {// 判断是否关闭链路命令if (frame instanceof CloseWebSocketFrame) {handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());UserInfoManager.removeChannel(ctx.channel());return;}// 判断是否Ping消息if (frame instanceof PingWebSocketFrame) {logger.info("ping message:{}", frame.content().retain());ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain()));return;}// 判断是否Pong消息if (frame instanceof PongWebSocketFrame) {logger.info("pong message:{}", frame.content().retain());ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain()));return;}// 本程序目前只支持文本消息if (!(frame instanceof TextWebSocketFrame)) {throw new UnsupportedOperationException(frame.getClass().getName() + " frame type not supported");}String message = ((TextWebSocketFrame) frame).text();JSONObject json = JSONObject.parseObject(message);int code = json.getInteger("code");Channel channel = ctx.channel();switch (code) {case ChatCode.PING_CODE:case ChatCode.PONG_CODE:UserInfoManager.updateUserTime(channel);logger.info("receive pong message, address: {}", NettyUtil.parseChannelRemoteAddr(channel));return;case ChatCode.AUTH_CODE:boolean isSuccess = UserInfoManager.saveUser(channel, json.getString("nick"));UserInfoManager.sendInfo(channel,ChatCode.SYS_AUTH_STATE,isSuccess);if (isSuccess) {UserInfoManager.broadCastInfo(ChatCode.SYS_USER_COUNT,UserInfoManager.getAuthUserCount());}return;case ChatCode.MESS_CODE: //普通的消息留给MessageHandler处理break;default:logger.warn("The code [{}] can't be auth!!!", code);return;}//后续消息交给MessageHandler处理ctx.fireChannelRead(frame.retain());}
}
- UserInfoManager
/*** Channel的管理器*/
public class UserInfoManager {private static final Logger logger = LoggerFactory.getLogger(UserInfoManager.class);private static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true);private static ConcurrentMap<Channel, UserInfo> userInfos = new ConcurrentHashMap<>();private static AtomicInteger userCount = new AtomicInteger(0);public static void addChannel(Channel channel) {String remoteAddr = NettyUtil.parseChannelRemoteAddr(channel);if (!channel.isActive()) {logger.error("channel is not active, address: {}", remoteAddr);}UserInfo userInfo = new UserInfo();userInfo.setAddr(remoteAddr);userInfo.setChannel(channel);userInfo.setTime(System.currentTimeMillis());userInfos.put(channel, userInfo);}public static boolean saveUser(Channel channel, String nick) {UserInfo userInfo = userInfos.get(channel);if (userInfo == null) {return false;}if (!channel.isActive()) {logger.error("channel is not active, address: {}, nick: {}", userInfo.getAddr(), nick);return false;}// 增加一个认证用户userCount.incrementAndGet();userInfo.setNick(nick);userInfo.setAuth(true);userInfo.setUserId();userInfo.setTime(System.currentTimeMillis());return true;}/*** 从缓存中移除Channel,并且关闭Channel* @param channel*/public static void removeChannel(Channel channel) {try {logger.warn("channel will be remove, address is :{}", NettyUtil.parseChannelRemoteAddr(channel));rwLock.writeLock().lock();channel.close();UserInfo userInfo = userInfos.get(channel);if (userInfo != null) {UserInfo tmp = userInfos.remove(channel);if (tmp != null && tmp.isAuth()) {// 减去一个认证用户userCount.decrementAndGet();}}} finally {rwLock.writeLock().unlock();}}/*** 广播普通消息** @param message*/public static void broadcastMess(int uid, String nick, String message) {if (!BlankUtil.isBlank(message)) {try {rwLock.readLock().lock();Set<Channel> keySet = userInfos.keySet();for (Channel ch : keySet) {UserInfo userInfo = userInfos.get(ch);if (userInfo == null || !userInfo.isAuth()) continue;ch.writeAndFlush(new TextWebSocketFrame(ChatProto.buildMessProto(uid, nick, message)));}} finally {rwLock.readLock().unlock();}}}/*** 广播系统消息*/public static void broadCastInfo(int code, Object mess) {try {rwLock.readLock().lock();Set<Channel> keySet = userInfos.keySet();for (Channel ch : keySet) {UserInfo userInfo = userInfos.get(ch);if (userInfo == null || !userInfo.isAuth()) continue;ch.writeAndFlush(new TextWebSocketFrame(ChatProto.buildSystProto(code, mess)));}} finally {rwLock.readLock().unlock();}}public static void broadCastPing() {try {rwLock.readLock().lock();logger.info("broadCastPing userCount: {}", userCount.intValue());Set<Channel> keySet = userInfos.keySet();for (Channel ch : keySet) {UserInfo userInfo = userInfos.get(ch);if (userInfo == null || !userInfo.isAuth()) continue;ch.writeAndFlush(new TextWebSocketFrame(ChatProto.buildPingProto()));}} finally {rwLock.readLock().unlock();}}/*** 发送系统消息* @param code* @param mess*/public static void sendInfo(Channel channel, int code, Object mess) {channel.writeAndFlush(new TextWebSocketFrame(ChatProto.buildSystProto(code, mess)));}public static void sendPong(Channel channel) {channel.writeAndFlush(new TextWebSocketFrame(ChatProto.buildPongProto()));}/*** 扫描并关闭失效的Channel*/public static void scanNotActiveChannel() {Set<Channel> keySet = userInfos.keySet();for (Channel ch : keySet) {UserInfo userInfo = userInfos.get(ch);if (userInfo == null) continue;if (!ch.isOpen() || !ch.isActive() || (!userInfo.isAuth() &&(System.currentTimeMillis() - userInfo.getTime()) > 10000)) {removeChannel(ch);}}}public static UserInfo getUserInfo(Channel channel) {return userInfos.get(channel);}public static ConcurrentMap<Channel, UserInfo> getUserInfos() {return userInfos;}public static int getAuthUserCount() {return userCount.get();}public static void updateUserTime(Channel channel) {UserInfo userInfo = getUserInfo(channel);if (userInfo != null) {userInfo.setTime(System.currentTimeMillis());}}
}
- ChatCode
public class ChatCode {public static final int PING_CODE = 10015;public static final int PONG_CODE = 10016;public static final int AUTH_CODE = 10000;public static final int MESS_CODE = 10086;/*** 系统消息类型*/public static final int SYS_USER_COUNT = 20001; // 在线用户数public static final int SYS_AUTH_STATE = 20002; // 认证结果public static final int SYS_OTHER_INFO = 20003; // 系统消息
}
- ChatProto
public class ChatProto {public static final int PING_PROTO = 1 << 8 | 220; //ping消息public static final int PONG_PROTO = 2 << 8 | 220; //pong消息public static final int SYST_PROTO = 3 << 8 | 220; //系统消息public static final int EROR_PROTO = 4 << 8 | 220; //错误消息public static final int AUTH_PROTO = 5 << 8 | 220; //认证消息public static final int MESS_PROTO = 6 << 8 | 220; //普通消息private int version = 1;private int uri;private String body;private Map<String,Object> extend = new HashMap<>();public ChatProto(int head, String body) {this.uri = head;this.body = body;}public static String buildPingProto() {return buildProto(PING_PROTO, null);}public static String buildPongProto() {return buildProto(PONG_PROTO, null);}public static String buildSystProto(int code, Object mess) {ChatProto chatProto = new ChatProto(SYST_PROTO, null);chatProto.extend.put("code", code);chatProto.extend.put("mess", mess);return JSONObject.toJSONString(chatProto);}public static String buildAuthProto(boolean isSuccess) {ChatProto chatProto = new ChatProto(AUTH_PROTO, null);chatProto.extend.put("isSuccess", isSuccess);return JSONObject.toJSONString(chatProto);}public static String buildErorProto(int code,String mess) {ChatProto chatProto = new ChatProto(EROR_PROTO, null);chatProto.extend.put("code", code);chatProto.extend.put("mess", mess);return JSONObject.toJSONString(chatProto);}public static String buildMessProto(int uid,String nick, String mess) {ChatProto chatProto = new ChatProto(MESS_PROTO, mess);chatProto.extend.put("uid", uid);chatProto.extend.put("nick", nick);chatProto.extend.put("time", DateTimeUtil.getCurrentTime());return JSONObject.toJSONString(chatProto);}public static String buildProto(int head, String body) {ChatProto chatProto = new ChatProto(head, body);return JSONObject.toJSONString(chatProto);}public int getUri() {return uri;}public void setUri(int uri) {this.uri = uri;}public String getBody() {return body;}public void setBody(String body) {this.body = body;}public int getVersion() {return version;}public void setVersion(int version) {this.version = version;}public Map<String, Object> getExtend() {return extend;}public void setExtend(Map<String, Object> extend) {this.extend = extend;}
}
- SimpleWebChatServer
public class SimpleWebChatServer extends BaseServer {private ScheduledExecutorService executorService;public SimpleWebChatServer(int port) {this.port = port;// 创建一个定长线程池executorService = Executors.newScheduledThreadPool(2);}@Overridepublic void start() {b.group(bossGroup, workGroup);// 设置初始化的主从"线程池"b.channel(NioServerSocketChannel.class);//3.接收进来的连接,由于是服务端,故而是NioServerSocketChannelb.option(ChannelOption.SO_KEEPALIVE, true);//4提供给NioServerSocketChannel用来接收进来的连接b.option(ChannelOption.TCP_NODELAY, true);b.option(ChannelOption.SO_BACKLOG, 1024);//5设置Channel实现的配置参数b.localAddress(new InetSocketAddress(port));b.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {//6帮助使用者配置一个新的 Channelch.pipeline().addLast(defLoopGroup,new HttpServerCodec(), //请求解码器new HttpObjectAggregator(65536),//将多个消息转换成单一的消息对象new ChunkedWriteHandler(), //支持异步发送大的码流,一般用于发送文件流new IdleStateHandler(60, 0, 0), //检测链路是否读空闲new UserAuthHandler(), //处理握手和认证new MessageHandler() //处理消息的发送);}});try {// 绑定端口,开始接收进来的连接cf = b.bind().sync();InetSocketAddress addr = (InetSocketAddress) cf.channel().localAddress();logger.info("WebSocketServer start success, port is:{}", addr.getPort());// 定时扫描所有的Channel,关闭失效的ChannelexecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {logger.info("scanNotActiveChannel --------");UserInfoManager.scanNotActiveChannel();}}, 3, 60, TimeUnit.SECONDS);// 定时向所有客户端发送Ping消息executorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {UserInfoManager.broadCastPing();}}, 3, 50, TimeUnit.SECONDS);} catch (InterruptedException e) {logger.error("WebSocketServer start fail,", e);}}@Overridepublic void shutdown() {if (executorService != null) {executorService.shutdown();}super.shutdown();}
}
- BlankUtil
public class BlankUtil {/*** 判断字符串是否为空*/public static boolean isBlank(final String str) {return (str == null) || (str.trim().length() <= 0);}/*** 判断字符是否为空* @param cha* @return*/public static boolean isBlank(final Character cha) {return (cha == null) || cha.equals(' ');}/*** 判断对象是否为空*/public static boolean isBlank(final Object obj) {return (obj == null);}/*** 判断数组是否为空* @param objs* @return*/public static boolean isBlank(final Object[] objs) {return (objs == null) || (objs.length <= 0);}/*** 判断Collectionj是否为空* @param obj* @return*/public static boolean isBlank(final Collection<?> obj) {return (obj == null) || (obj.size() <= 0);}/*** 判断Set是否为空* @param obj* @return*/public static boolean isBlank(final Set<?> obj) {return (obj == null) || (obj.size() <= 0);}public static boolean isBlank(Integer i) {return i == null || i < 1;}/*** 判断Serializable是否为空* @param obj* @return*/public static boolean isBlank(final Serializable obj) {return obj == null;}/*** 判断Map是否为空* @param obj* @return*/public static boolean isBlank(final Map<?, ?> obj) {return (obj == null) || (obj.size() <= 0);}
}
- Constants
public class Constants {public static String DEFAULT_HOST = "localhost";public static int DEFAULT_PORT = 9688;public static String WEBSOCKET_URL = "ws://localhost:8099/websocket";
}
- DateTimeUtil
public class DateTimeUtil {private static final Logger logger = LoggerFactory.getLogger(DateTimeUtil.class);private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";private static final String DEFAULT_TIME_PATTERN = "HH:mm:ss";/*** 获取当天的字符串* @return*/public static String getTodayStr(){SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");return sdf.format(new Date());}public static String getTodayStr2(){SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd");return sdf.format(new Date());}public static String getCurrentTime(){return getCurrentTime(DEFAULT_TIME_PATTERN);}/*** 获取当前时间的字符串* @return*/public static String getCurrentDateTime(){return getCurrentTime(DEFAULT_DATE_PATTERN);}/*** 获取当前时间的字符串* @param format 字符串格式,如:yy-MM-dd HH:mm:ss* @return*/public static String getCurrentTime(String format){SimpleDateFormat sdf = new SimpleDateFormat(format);Timestamp timestamp = new Timestamp(System.currentTimeMillis());return sdf.format(timestamp);}/*** 获取当前的月份* @return*/public static String getCurrentMonth(){SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");return sdf.format(new Date());}/*** 比较两个时间,如果返回大于0,time1大于time2,如果返回-1,time1小于time2,返回0则相等* @param time1* @param time2* @return* @throws ParseException*/public static int compareTime(String time1,String time2) throws ParseException{SimpleDateFormat sdf = new SimpleDateFormat(DEFAULT_DATE_PATTERN);Date date1 = sdf.parse(time1);Date date2 = sdf.parse(time2);long result = date1.getTime() - date2.getTime();if(result > 0){return 1;}else if(result==0){return 0;}else{return -1;}}/*** 转换字符串成日期对象* @param dateStr 日期字符串* @param format 格式,如:yy-MM-dd HH:mm:ss* @return*/public static Date convertStrToDate(String dateStr,String format){if(!BlankUtil.isBlank(dateStr)&&!BlankUtil.isBlank(format)){try{SimpleDateFormat sdf = new SimpleDateFormat(format);return sdf.parse(dateStr);}catch (Exception e) {logger.warn("convertDate fail, date is "+ dateStr, e);}}return null;}/*** 把字符串日期转换成另一种格式* @param dateStr 字符串日期* @param format 转换日期格式* @param otherFormat 转换日期格式* @return*/public static String convertDate(String dateStr,String format,String otherFormat){try{Date date = convertStrToDate(dateStr, format);SimpleDateFormat sdf = new SimpleDateFormat(otherFormat);return sdf.format(date);}catch (Exception e) {logger.warn("convertDate fail, date is "+ dateStr, e);}return null;}/*** 把字符串日期转换成另一种格式* @param dateStr 字符串日期* @param format 转换格式* @return*/public static String convertDate(String dateStr,String format){return convertDate(dateStr, DEFAULT_DATE_PATTERN,format);}
- NettyUtil
public class NettyUtil {/*** 获取Channel的远程IP地址* @param channel* @return*/public static String parseChannelRemoteAddr(final Channel channel) {if (null == channel) {return "";}SocketAddress remote = channel.remoteAddress();final String addr = remote != null ? remote.toString() : "";if (addr.length() > 0) {int index = addr.lastIndexOf("/");if (index >= 0) {return addr.substring(index + 1);}return addr;}return "";}
}
- SimpleWebChatApplication
public class SimpleWebChatApplication {private static final Logger logger = LoggerFactory.getLogger(SimpleWebChatApplication.class);public static void main(String[] args) {final SimpleWebChatServer server = new SimpleWebChatServer(Constants.DEFAULT_PORT);server.init();server.start();//注册进程钩子,在JVM进程关闭前释放资源Runtime.getRuntime().addShutdownHook(new Thread(){@Overridepublic void run(){server.shutdown();logger.warn(">>>>>>>>>> jvm shutdown");System.exit(0);}});}
}
- resources下载地址。提取码:ymk2