到这里为止,应该是能实现非长链接的Netty服务提供了吧!
但是!Netty是高性能Nio的框架,大多使用到的场景应该是长链接的,那么本篇文章使用Netty来实现一个实时的群聊房间。也就是服务端和客服端都由Netty实现
1.服务架构
一个客户端和业务处理的handler、一个服务端和业务处理的handler
上篇文章已经分析过自定义的编解码器,本文就用Netty自带的String协议了
简单的定一个消息协议 name#msg
,仅供了解,正常肯定会详细点
2.Netty服务端
2.1 main方法启动服务
public static void main(String[] args) {
NioEventLoopGroup b1 = new NioEventLoopGroup();
NioEventLoopGroup b2 = new NioEventLoopGroup();
ServerBootstrap bs = new ServerBootstrap()
.group(b1, b2)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
//使用自带的String编解码器
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
//服务端业务处理的handler
pipeline.addLast(new MyChatServerHandler());
}
});
ChannelFuture f = bs.bind(19900).addListener(future -> {
if (future.isSuccess()) {
System.out.println("已启动netty服务:" + 19900);
}
});
try {
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
b1.shutdownGracefully();
b2.shutdownGracefully();
}
}
2.2 服务端业务处理Handler
public class MyChatServerHandler extends SimpleChannelInboundHandler<String> {
//保存长链接的channel通道
static Map<ChannelId, Channel> channelMap = new ConcurrentHashMap<>();
/**
* 协议 name#msg
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("服务端收到消息:" + msg);
String[] split = msg.split("#");
if (split.length == 1) {
//输了昵称才算新加入的
if (!channelMap.containsKey(ctx.channel().id())) {
channelMap.put(ctx.channel().id(), ctx.channel());
}
}
//消息群发
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = sdf.format(new Date());
for (Map.Entry<ChannelId, Channel> entry : channelMap.entrySet()) {
if (split.length == 1) {
entry.getValue().writeAndFlush(dateStr + ":欢迎" + msg + "加入群聊!");
} else {
entry.getValue().writeAndFlush(dateStr + ":" + msg.replace("#", " : "));
}
}
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("新的客户端连接" + ctx.channel());
}
}
3.Netty客户端
3.1 main方法启动客户端
public static void main(String[] args) {
//客户端只需要一个事件分组
NioEventLoopGroup group = new NioEventLoopGroup();
//构建的类也不同
Bootstrap bs = new Bootstrap()
.group(group)
//选择的channel也不同
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
//handler处理链是一致的
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
pipeline.addLast(new MyChatClientHandler());
}
});
//连接到服务端的指定端口
ChannelFuture future = bs.connect("127.0.0.1", 19900);
try {
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
group.shutdownGracefully();
}
}
3.2 客户端业务处理Handler
public class MyChatClientHandler extends SimpleChannelInboundHandler<String> {
//保存第一次连接成功输入的昵称
static String name;
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
//打印消息
System.out.println(msg);
}
/**
* 协议 name#msg
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("与服务端连接成功:" + ctx.channel() + ",请输入你的昵称:");
//启动一个发送消息线程 只要键盘输入过就发送到服务端
new Thread(() -> {
Scanner sc = new Scanner(System.in);
while (true) {
String next = sc.nextLine();
if (name == null) {
//第一次发送则初始化昵称 协议仅供理解,硬是要输昵称带#的忽略
name = next;
ctx.writeAndFlush(name);
} else {
//发送规定协议的报文
ctx.writeAndFlush(name + "#" + next);
}
}
}).start();
}
}
4.测试
分别启动一个服务端,两个客户端(可多个)。不带时间的为键盘输入日志
客户端1日志:
与服务端连接成功:[id: 0x1b671875, L:/127.0.0.1:53066 - R:/127.0.0.1:19900],请输入你的昵称:
张三
2022-05-09 14:44:43:欢迎张三加入群聊!
2022-05-09 14:44:48:欢迎王五加入群聊!
2022-05-09 14:44:55:王五 : 你好,我是王五
你好,我是张三
2022-05-09 14:45:04:张三 : 你好,我是张三
客户端2日志:
与服务端连接成功:[id: 0x8d57f13c, L:/127.0.0.1:53091 - R:/127.0.0.1:19900],请输入你的昵称:
王五
2022-05-09 14:44:48:欢迎王五加入群聊!
你好,我是王五
2022-05-09 14:44:55:王五 : 你好,我是王五
2022-05-09 14:45:04:张三 : 你好,我是张三
服务端日志:
已启动netty服务:19900
新的客户端连接[id: 0x289276c8, L:/127.0.0.1:19900 - R:/127.0.0.1:53066]
新的客户端连接[id: 0xba54775d, L:/127.0.0.1:19900 - R:/127.0.0.1:53091]
服务端收到消息:张三
服务端收到消息:王五
服务端收到消息:王五#你好,我是王五
服务端收到消息:张三#你好,我是张三
接下来就可以在群聊中任意聊天了
5.总结
上述内容仅供了解使用,正式开发下肯定是要考虑多台机器的,可能channel不在同一台机,这个时候就要使用到分布式中间件了。
预告:
在微服务崛起的时代,服务调用肯定必不可少,RPC通信问题也随之展开,下文将使用Netty来了解什么是RPC
以上就是本章的全部内容了。
上一篇:通信框架之Netty第三话 - Netty的使用以及使用Netty改造Tomcat
下一篇:通信框架之Netty第五话 - 一文了解RPC通信原理并使用Netty实现一个PRC
不见只今汾水上,唯有年年秋雁飞