一、群聊消息的收发实现
1.其中的和核心实现部分是服务端处理群聊消息:GroupMessageRequestHandler
public class GroupMessageRequestHandler extends SimpleChannelInboun
dHandler
<GroupMessageRequestPacket> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, GroupMessag
eRequestPacket
requestPacket) {
// 1.拿到 groupId 构造群聊消息的响应
String groupId = requestPacket.getToGroupId();
GroupMessageResponsePacket responsePacket = new GroupMessageR
esponsePacket();
responsePacket.setFromGroupId(groupId);
responsePacket.setMessage(requestPacket.getMessage());
responsePacket.setFromUser(SessionUtil.getSession(ctx.channel()));
// 2.拿到群聊对应的ChannelGroup,写到每个客户端
ChannelGroup channelGroup = SessionUtil.getChannelGroup(groupId)
;
channelGroup.writeAndFlush(responsePacket);
}
}
通过groupId构造群聊消息的响应,将发送群聊的用户信息填入
拿到群聊对应的ChannelGroup,通过writeAndFlush()方法写到客户端
2.共享Handler:
使用netty完成一个即时聊天系统的核心功能的服务端代码:
serverBootstrap
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new Spliter());
ch.pipeline().addLast(new PacketDecoder());
ch.pipeline().addLast(new LoginRequestHandler());
ch.pipeline().addLast(new AuthHandler());
ch.pipeline().addLast(new MessageRequestHandler());
ch.pipeline().addLast(new CreateGroupRequestHandler());
ch.pipeline().addLast(new JoinGroupRequestHandler());
ch.pipeline().addLast(new QuitGroupRequestHandler());
ch.pipeline().addLast(new ListGroupMembersRequestHandle
r());
ch.pipeline().addLast(new GroupMessageRequestHandler())
;
ch.pipeline().addLast(new LogoutRequestHandler());
ch.pipeline().addLast(new PacketEncoder());
}
});
netty的逻辑是,每次有新连接到来的时候都会调用ChannelInitializer的initChannel()方法,与指令相关的Handler每次都会被创建一次
由上述代码可以看到,每一个指令Handler它们内部都是没有成员变量即无状态,则我们可以使用单例模式(在调用pipeline.addLast()方法的时候直接使用单例,不需要每次都创建)
以LoginRequestHandler为例:
@ChannelHandler.Sharable
public class LoginRequestHandler extends SimpleChannelInboundHandler
<LoginRequestPacket> {
// 2. 构造单例
public static final LoginRequestHandler INSTANCE = new LoginReques
tHandler();
protected LoginRequestHandler() {
}
}
其中注解@ChannelHandler.Sharable显示的告诉Netty,这个Handler支持多个Channel共享
例如在服务端使用单例模式:
serverBootstrap
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
// 单例模式,多个Channel共享同一个Handler
ch.pipeline().addLast(LoginRequestHandler.INSTANCE);
// ...
}
});
这样每次来一个新连接,添加Handler的时候就不需要每次都创建
3.压缩Handler(合并编解码器)
serverBootstrap
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new Spliter());
ch.pipeline().addLast(new PacketDecoder());
ch.pipeline().addLast(LoginRequestHandler.INSTANCE);
ch.pipeline().addLast(AuthHandler.INSTANCE);
ch.pipeline().addLast(MessageRequestHandler.INSTANCE);
ch.pipeline().addLast(CreateGroupRequestHandler.INSTANCE);
ch.pipeline().addLast(JoinGroupRequestHandler.INSTANCE);
ch.pipeline().addLast(QuitGroupRequestHandler.INSTANCE);
ch.pipeline().addLast(ListGroupMembersRequestHandler.INSTA
NCE);
ch.pipeline().addLast(GroupMessageRequestHandler.INSTANCE)
;
ch.pipeline().addLast(LogoutRequestHandler.INSTANCE);
ch.pipeline().addLast(new PacketEncoder());
}
});
每个Spliter都需要维持每个Channel当前读到的数据,但是其中的PacketDecoder和PacketEncoder可以用Netty中提供的MessageToMessageCodec,使用它可以将编解码操作放在同一个类中去实现,示例代码:
PacketCodecHandler.java
@ChannelHandler.Sharable
public class PacketCodecHandler extends MessageToMessageCodec<Byte
Buf, Packet> {
public static final PacketCodecHandler INSTANCE = new PacketCodec
Handler();
private PacketCodecHandler() {
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, Lis
t<Object> out) {
out.add(PacketCodec.INSTANCE.decode(byteBuf));
}
@Override
protected void encode(ChannelHandlerContext ctx, Packet packet, List<
Object> out) {
ByteBuf byteBuf = ctx.channel().alloc().ioBuffer();
PacketCodec.INSTANCE.encode(byteBuf, packet);
out.add(byteBuf);
}
}
PacketCodecHandler是一个无状态的Handler,可以同样用单例模式来实现
最终优化后的代码为:
serverBootstrap
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new Spliter());
ch.pipeline().addLast(PacketCodecHandler.INSTANCE);
ch.pipeline().addLast(LoginRequestHandler.INSTANCE);
ch.pipeline().addLast(AuthHandler.INSTANCE);
ch.pipeline().addLast(MessageRequestHandler.INSTANCE);
ch.pipeline().addLast(CreateGroupRequestHandler.INSTANCE);
ch.pipeline().addLast(JoinGroupRequestHandler.INSTANCE);
ch.pipeline().addLast(QuitGroupRequestHandler.INSTANCE);
ch.pipeline().addLast(ListGroupMembersRequestHandler.INSTAN
CE);
ch.pipeline().addLast(GroupMessageRequestHandler.INSTANCE)
;
ch.pipeline().addLast(LogoutRequestHandler.INSTANCE);
}
});
二、缩短事件传播路径
1.压缩Handler-----合并平行Handler
因为上述服务端的代码,在Pipeline中,绝大部分都是与指令相关的handler,Handler越多其链越长,事件传播过程性能损耗会逐渐被放大,因为解码器decode出来的每个Packet对象都要在每隔Handler上经过一遍,因此我i们需要缩短这个事件传播路径
示例代码:
@ChannelHandler.Sharable
public class IMHandler extends SimpleChannelInboundHandler<Packet>
{
public static final IMHandler INSTANCE = new IMHandler();
private Map<Byte, SimpleChannelInboundHandler<? extends Packet>>
handlerMap;
private IMHandler() {
handlerMap = new HashMap<>();
handlerMap.put(MESSAGE_REQUEST, MessageRequestHandler.INS
TANCE);
handlerMap.put(CREATE_GROUP_REQUEST, CreateGroupRequest
Handler.INSTANCE);
handlerMap.put(JOIN_GROUP_REQUEST, JoinGroupRequestHandler
.INSTANCE);
handlerMap.put(QUIT_GROUP_REQUEST, QuitGroupRequestHandle
r.INSTANCE);
handlerMap.put(LIST_GROUP_MEMBERS_REQUEST,
ListGroupMembersRequestHandler.INSTANCE);
handlerMap.put(GROUP_MESSAGE_REQUEST, GroupMessageRequ
estHandler.INSTANCE);
handlerMap.put(LOGOUT_REQUEST, LogoutRequestHandler.INSTA
NCE);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Packet
packet) throws
Exception {
handlerMap.get(packet.getCommand()).channelRead(ctx, packet);
}
}
压缩之后的代码:
serverBootstrap
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new Spliter());
ch.pipeline().addLast(PacketCodecHandler.INSTANCE);
ch.pipeline().addLast(LoginRequestHandler.INSTANCE);
ch.pipeline().addLast(AuthHandler.INSTANCE);
ch.pipeline().addLast(IMHandler.INSTANCE);
}
});