github项目推荐:Chat_Room
Netty 入门级项目
码云:https://gitee.com/baldness_and_coldness/Chat_Room
Github:https://github.com/fff-bald/Chat_Room
Netty入门相关知识:https://blog.csdn.net/qq_35751014/category_9722459.html
一、简单介绍
Chat_Room是一个基于Netty开发的命令行聊天室程序,使用了C/S架构,用户使用客户端向服务器发送短信,由服务器转发至所有连接中的客户端。
二、工作流程
客户端:
1、客户端连接服务器:客户端通过服务器的ip地址和端口号连接上服务器,加入到聊天室中。
(实质上是channel和EventLoopGroup建立和连接)
2、客户端发送短信:通过SocketChannel向服务器发送数据。
服务器:
1、服务器初始化:定义EventLoopGroup,通过ServerBootstrap对服务器初始化,选择对外暴露的端口,初始化channel并绑定handler。
3、用户记录:
上线,当有客户端第一次连接服务器,该channel 处于活动状态,将channel存于数组中,channel与用户一一绑定。
下线,当channel 断开连接时,从channel数组中删除对应的channel,之后有短信传入也不会在发送到该客户端。
2、短信转发:当有客户端向服务器发送短信时,服务器遍历channel数组,通过channel向每个客户端发送这条短信。
三、核心实现
项目的核心功能是通过Netty实现的,Netty的相关基础知识需要大家系统性地学习。
Netty是由JBOSS提供的一个java开源框架,现为
Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
使用Netty实现功能,最主要的是handler的编写,需要实现客户端发送消息到服务器,服务器将消息转发到所有客户端中。
Clint 中 handler的实现很简单,只要展示由服务器发来的短信即可。
自定义一个handler继承SimpleChannelInboundHandler< String>,重写channelRead0方法就行了,当服务器给客户端发送短信时,这个方法就会触发。
public class MyClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String meg) throws Exception {
WriteAndPrint.println(meg.trim());
}
}
Server中的handle要实现的功能就相对多一些,需要记录所有注册过的客户端,并且从客户端中收到信息后,需要完成对信息的转发。
同样自定义一个handler继承SimpleChannelInboundHandler< String>。
handlerAdded(ChannelHandlerContext ctx) | 此方法表示连接建立,一旦建立连接,就第一个被执行 |
---|---|
channelActive(ChannelHandlerContext ctx) | 见名知义,表示 channel 处于活动状态 |
channelInactive(ChannelHandlerContext ctx) | 与上相似,表示 channel 处于不活动状态 |
handlerRemoved(ChannelHandlerContext ctx) throws Exception | 表示 channel 断开连接 |
channelRead0(ChannelHandlerContext ctx, String msg) | 与client同 |
四、Netty进阶
如果你看到了这里,或者在github上看过了这个项目的源代码,你可能会觉得这个项目太简单了,想要拓展更多的功能,却发现无从入手。
事件驱动
这是很正常的,因为这个项目没有对可拓展性进行考虑,服务器粗暴地将所有来自客户端的数据都当成了需要转发的短信,这不利于我们之后对功能的拓展。
问题:
举个栗子,我希望实现这么一个功能:
用户可以自定义自己在聊天室中的昵称昵称。
想要实现这个功能,用户自定义的昵称就必须要从客户端传递到服务器。
但对于服务器来说,所有从客户端发来的数据地位都是相同的,它无法识别出哪一条是对用户昵称的定义。
自然而然的我们就会想到要在数据中加标志位。
例如在这个chatroom程序中,我们可以在客户端每条发送的String信息前拼上一位的标志位,”1”代表是要发送的信息,”2“代表要用户设置的昵称。
这种方法虽然简单,但是不推荐。
万一后面实现的功能越来越多,拼接字符串不好管理。
解决办法:
我们可以将发送的消息从一条String变成一个Message类,类中包括消息头和消息体,消息头为标志位,消息体为实际短信。
不过要对类进行序列化编码,Netty自带的编码器就不太给力了,目前很多公司都是使用http+json或tcp+protobuf。
适配器模式
还是回到原本的问题,如果实现的功能越来越多,简单的if-else式判断就会变得不易于拓展,然后我们就能想到使用适配器设计模式。
如果你们对 适配器模式 + 事件驱动的Netty开发 感兴趣,
我推荐你们下一个Netty的进阶项目:nico / ratel
nico / ratel
简单介绍:https://blog.csdn.net/awsl_6699/article/details/115604307
码云地址:https://gitee.com/ainilili/ratel