Netty进阶 -- 非阻塞网络编程 实现群聊+私聊+心跳检测系统

import java.util.concurrent.TimeUnit;

public class GroupChatServer {

// 监听端口

private int port;

public GroupChatServer(int port) {

this.port = port;

}

//编写run方法,处理客户端的请求

public void run() {

//创建两个线程组

EventLoopGroup bossGroup = new NioEventLoopGroup(1);

//Nio核数 * 2

EventLoopGroup workerGroup = new NioEventLoopGroup();

ServerBootstrap bootstrap = new ServerBootstrap();

try {

bootstrap.group(bossGroup, workerGroup)

.channel(NioServerSocketChannel.class)

.option(ChannelOption.SO_BACKLOG, 128)

.childOption(ChannelOption.SO_KEEPALIVE, true)

.childHandler(new ChannelInitializer() {

@Override

protected void initChannel(SocketChannel socketChannel) throws Exception {

//获取pipeline

ChannelPipeline pipeline = socketChannel.pipeline();

//向pipeline加入解码器

pipeline.addLast(“decoder”, new StringDecoder());

//向pipeline加入编码器

pipeline.addLast(“encoder”, new StringEncoder());

//加入自己的业务处理handler

pipeline.addLast(new GroupChatServerHandler());

//加入心跳检测机制

pipeline.addLast(new IdleStateHandler(3, 5, 7, TimeUnit.SECONDS));

pipeline.addLast(new HeartbeatServerHandler());

}

});

System.out.println(“netty 服务器启动”);

ChannelFuture future = bootstrap.bind(port).sync();

//监听关闭事件

future.channel().closeFuture().sync();

} catch (Exception e) {

e.printStackTrace();

} finally {

bossGroup.shutdownGracefully();

workerGroup.shutdownGracefully();

}

}

public static void main(String[] args) {

GroupChatServer groupChatServer = new GroupChatServer(7000);

groupChatServer.run();

}

}

GroupChatServerHandler 服务器自定义handler

package com.wanshi.netty.groupchat;

import io.netty.channel.Channel;

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.SimpleChannelInboundHandler;

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.HashMap;

import java.util.Map;

public class GroupChatServerHandler extends SimpleChannelInboundHandler {

//所有的channel存入map集合中,目的是为了私聊好获取用户

private static Map<String,Channel> allChannels = new HashMap<String,Channel>();

//格式化所有日期时间

private SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);

//转化日期

private String currentDate = sdf.format(new Date());

/**

  • handlerAdded 表示连接建立,一旦连接建立,第一个被执行

  • 将当前channel加入到map集合

*/

@Override

public void handlerAdded(ChannelHandlerContext ctx) throws Exception {

//获取当前channel

Channel channel = ctx.channel();

//推送客户加入聊天的信息推送给其它在线的客户端

//该方法会将channelGroup中所有的channel遍历并发送消息

allChannels.forEach((k, ch) ->{

ch.writeAndFlush(currentDate+" \n [客户端]" + channel.remoteAddress() + “加入聊天\n”);

});

//获取端口号

String key = channel.remoteAddress().toString().split(“:”)[1];

allChannels.put(key, channel);

}

/**

  • 表示断开连接了,将xx客户离开信息推送给当前在线的客户

  • @param ctx

  • @throws Exception

*/

@Override

public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {

//获取当前channel

Channel channel = ctx.channel();

//推送客户加入聊天的信息推送给其它在线的客户端

//该方法会将map中所有的channel遍历并发送消息

allChannels.forEach((k, ch) ->{

ch.writeAndFlush(currentDate+" \n [客户端]" + channel.remoteAddress() + “离线\n”);

});

System.out.println(“当前在线人数:” + allChannels.size());

}

/**

  • 读取数据并将数据转发给在线的客户端

  • @param channelHandlerContext

  • @param s

  • @throws Exception

*/

@Override

protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {

//获取到当前channel

Channel channel = channelHandlerContext.channel();

//私聊用户发送消息

if(s.contains(“#”)){

String id = s.split(“#”)[1];

String body = s.split(“#”)[2];

Channel userChannel = allChannels.get(id);

String key = channel.remoteAddress().toString().split(“:”)[1];

userChannel.writeAndFlush(currentDate+"\n “+key+”【私聊】 [用户] “+id+” 说 : "+body);

return;

}

//循环遍历hashmap集合进行转发消息

allChannels.forEach((k, ch) -> {

if (channel != ch) {

ch.writeAndFlush(currentDate + " \n [客户端]" + channel.remoteAddress() + “:” + s + “\n”);

} else { // 发送消息给自己,回显自己发送的消息

channel.writeAndFlush(currentDate + " \n [我]:" + s + “\n”);

}

});

}

/**

  • 表示channel处于活动状态

  • @param ctx

  • @throws Exception

*/

@Override

public void channelActive(ChannelHandlerContext ctx) throws Exception {

System.out.println(currentDate + " – " + ctx.channel().remoteAddress() + “上线~”);

}

/**

  • 失去连接时会触发此方法

  • @param ctx 全局上下文对象

  • @throws Exception

*/

@Override

public void channelInactive(ChannelHandlerContext ctx) throws Exception {

Channel channel = ctx.channel();

String key = channel.remoteAddress().toString().split(“:”)[1];

allChannels.remove(key);

System.out.println(currentDate + " – " + ctx.channel().remoteAddress() + “离线”);

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

//关闭

ctx.close();

}

}

自定义心跳处理器 – HeartbeatServerHandler

package com.wanshi.netty.heartbeat;

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.ChannelInboundHandlerAdapter;

import io.netty.handler.timeout.IdleStateEvent;

public class HeartbeatServerHandler extends ChannelInboundHandlerAdapter {

/**

  • 客户端在指定时间内未触发相应操作执行此方法,即认为与客户端断开连接

  • @param ctx 全局上下文对象

  • @param evt 事件

  • @throws Exception 发生异常时抛出

*/

@Override

public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

//判断当前事件是否为IdleStateEvent

if (evt instanceof IdleStateEvent) {

//将evt强转为IdleStateEvent

IdleStateEvent event = (IdleStateEvent) evt;

//判断到底发生的事件是什么

String eventType = null;

//由于IdleStateEvent底层判断事件是根据枚举类型来的,所以直接判断即可

switch (event.state()) {

case READER_IDLE:

eventType = “读空闲”;

break;

case WRITER_IDLE:

eventType = “写空闲”;

break;

case ALL_IDLE:

eventType = “读写空闲”;

break;

}

System.out.println(ctx.channel().remoteAddress() + “发生超时事件,事件类型为:” + eventType);

System.out.println(“服务器做相应处理”);

}

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

System.out.println(“发生异常!”);

}

}

GroupChatClient 客户端

package com.wanshi.netty.groupchat;

import io.netty.bootstrap.Bootstrap;

import io.netty.channel.*;

import io.netty.channel.nio.NioEventLoopGroup;

import io.netty.channel.socket.SocketChannel;

import io.netty.channel.socket.nio.NioSocketChannel;

import io.netty.handler.codec.string.StringDecoder;

import io.netty.handler.codec.string.StringEncoder;

import java.util.Scanner;

public class GroupChatClient {

//定义属性

private final String host;

public final int port;

public GroupChatClient(String host, int port) {

this.host = host;

this.port = port;

}

public void run () {

EventLoopGroup eventExecutors = new NioEventLoopGroup();

Bootstrap bootstrap = new Bootstrap();

try {

bootstrap.group(eventExecutors)

.channel(NioSocketChannel.class)

.handler(new ChannelInitializer() {

@Override

protected void initChannel(SocketChannel socketChannel) throws Exception {

//得到pipeline

ChannelPipeline pipeline = socketChannel.pipeline();

//加入相关的handler

pipeline.addLast(“decoder”, new StringDecoder());

pipeline.addLast(“encoder”, new StringEncoder());

//加入自定义handler

pipeline.addLast(new GroupChatClientHandler());

}

});

ChannelFuture channelFuture = bootstrap.connect(host, port).sync();

//得到channel

Channel channel = channelFuture.channel();

System.out.println(“-----” + channel.localAddress() + “----”);

//客户端需要输入信息,创建一个扫描器

Scanner scanner = new Scanner(System.in);

while (scanner.hasNext()) {

String msg = scanner.nextLine();

//通过channel发送到服务器端

channel.writeAndFlush(msg+“\r\n”);

}

channelFuture.channel().closeFuture().sync();

} catch (Exception e) {

} finally {

eventExecutors.shutdownGracefully();

}

}

public static void main(String[] args) {

GroupChatClient groupChatClient = new GroupChatClient(“127.0.0.1”, 7000);

groupChatClient.run();

}

}

GroupChatClientHandler 客户端自定义处理器Handler

package com.wanshi.netty.groupchat;

import io.netty.channel.Channel;

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.SimpleChannelInboundHandler;

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.HashMap;

import java.util.Map;

public class GroupChatServerHandler extends SimpleChannelInboundHandler {

//所有的channel存入map集合中,目的是为了私聊好获取用户

private static Map<String,Channel> allChannels = new HashMap<String,Channel>();

//格式化所有日期时间

private SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);

//转化日期

private String currentDate = sdf.format(new Date());

/**

  • handlerAdded 表示连接建立,一旦连接建立,第一个被执行

  • 将当前channel加入到map集合

*/

@Override

public void handlerAdded(ChannelHandlerContext ctx) throws Exception {

//获取当前channel

Channel channel = ctx.channel();

//推送客户加入聊天的信息推送给其它在线的客户端

//该方法会将channelGroup中所有的channel遍历并发送消息

allChannels.forEach((k, ch) ->{

ch.writeAndFlush(currentDate+" \n [客户端]" + channel.remoteAddress() + “加入聊天\n”);

});

//获取端口号

String key = channel.remoteAddress().toString().split(“:”)[1];

allChannels.put(key, channel);

}

/**

  • 表示断开连接了,将xx客户离开信息推送给当前在线的客户

  • @param ctx

  • @throws Exception

*/

@Override

public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {

//获取当前channel

Channel channel = ctx.channel();

//推送客户加入聊天的信息推送给其它在线的客户端

//该方法会将map中所有的channel遍历并发送消息

allChannels.forEach((k, ch) ->{

ch.writeAndFlush(currentDate+" \n [客户端]" + channel.remoteAddress() + “离线\n”);

});

System.out.println(“当前在线人数:” + allChannels.size());

}

/**

  • 读取数据并将数据转发给在线的客户端

  • @param channelHandlerContext

  • @param s

  • @throws Exception

*/

@Override

protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {

//获取到当前channel

Channel channel = channelHandlerContext.channel();

//私聊用户发送消息

if(s.contains(“#”)){

String id = s.split(“#”)[1];

String body = s.split(“#”)[2];

Channel userChannel = allChannels.get(id);

String key = channel.remoteAddress().toString().split(“:”)[1];

userChannel.writeAndFlush(currentDate+"\n “+key+”【私聊】 [用户] “+id+” 说 : "+body);

return;

}

//循环遍历hashmap集合进行转发消息

allChannels.forEach((k, ch) -> {

if (channel != ch) {

ch.writeAndFlush(currentDate + " \n [客户端]" + channel.remoteAddress() + “:” + s + “\n”);

} else { // 发送消息给自己,回显自己发送的消息

channel.writeAndFlush(currentDate + " \n [我]:" + s + “\n”);

}

});

}

/**

  • 表示channel处于活动状态

  • @param ctx

  • @throws Exception

*/

@Override

public void channelActive(ChannelHandlerContext ctx) throws Exception {

System.out.println(currentDate + " – " + ctx.channel().remoteAddress() + “上线~”);

}

/**

  • 失去连接时会触发此方法

  • @param ctx 全局上下文对象

  • @throws Exception

*/

@Override

public void channelInactive(ChannelHandlerContext ctx) throws Exception {

Channel channel = ctx.channel();

String key = channel.remoteAddress().toString().split(“:”)[1];

allChannels.remove(key);

System.out.println(currentDate + " – " + ctx.channel().remoteAddress() + “离线”);

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

//关闭

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
ss().toString().split(“:”)[1];

allChannels.remove(key);

System.out.println(currentDate + " – " + ctx.channel().remoteAddress() + “离线”);

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

//关闭

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-I22iiQg4-1715854986231)]

[外链图片转存中…(img-paQuwIfn-1715854986232)]

[外链图片转存中…(img-RGwpniss-1715854986232)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值