具体原理:
登录之后服务端把channel和用户名绑定放到集合中,
之后聊天等请求都根据用户名发送到对应的chaael
以下仅展示部分源码,详情请下载以上文件
/**
* 用户管理接口
*/
public interface UserService {
/**
* 登录
* @param username 用户名
* @param password 密码
* @return 登录成功返回 true, 否则返回 false
*/
boolean login(String username, String password);
}
/**
* 会话管理接口
*/
public interface Session {
/**
* 绑定会话
* @param channel 哪个 channel 要绑定会话
* @param username 会话绑定用户
*/
void bind(Channel channel, String username);
/**
* 解绑会话
* @param channel 哪个 channel 要解绑会话
*/
void unbind(Channel channel);
/**
* 获取属性
* @param channel 哪个 channel
* @param name 属性名
* @return 属性值
*/
Object getAttribute(Channel channel, String name);
/**
* 设置属性
* @param channel 哪个 channel
* @param name 属性名
* @param value 属性值
*/
void setAttribute(Channel channel, String name, Object value);
/**
* 根据用户名获取 channel
* @param username 用户名
* @return channel
*/
Channel getChannel(String username);
}
/**
* 聊天组会话管理接口
*/
public interface GroupSession {
/**
* 创建一个聊天组, 如果不存在才能创建成功, 否则返回 null
* @param name 组名
* @param members 成员
* @return 成功时返回组对象, 失败返回 null
*/
Group createGroup(String name, Set<String> members);
/**
* 加入聊天组
* @param name 组名
* @param member 成员名
* @return 如果组不存在返回 null, 否则返回组对象
*/
Group joinMember(String name, String member);
/**
* 移除组成员
* @param name 组名
* @param member 成员名
* @return 如果组不存在返回 null, 否则返回组对象
*/
Group removeMember(String name, String member);
/**
* 移除聊天组
* @param name 组名
* @return 如果组不存在返回 null, 否则返回组对象
*/
Group removeGroup(String name);
/**
* 获取组成员
* @param name 组名
* @return 成员集合, 没有成员会返回 empty set
*/
Set<String> getMembers(String name);
/**
* 获取组成员的 channel 集合, 只有在线的 channel 才会返回
* @param name 组名
* @return 成员 channel 集合
*/
List<Channel> getMembersChannel(String name);
}
@Slf4j
public class ChatServer {
public static void main(String[] args) {
//利用boss、worker两个线程组来处理网络事件
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
LoginRequestMessageHandler LOGIN_HANDLER = new LoginRequestMessageHandler();
ChatRequestMessageHandler CHAT_HANDLER = new ChatRequestMessageHandler();
GroupChatRequestMessageHandler GROUPCHAT_HANDLER = new GroupChatRequestMessageHandler();
GroupCreateRequestMessageHandler GROUPCREATE_HANDLER = new GroupCreateRequestMessageHandler();
GroupJoinRequestMessageHandler GROUPJOIN_HANDLER = new GroupJoinRequestMessageHandler();
GroupMembersRequestMessageHandler GROUPMEMBERS_HANDLER = new GroupMembersRequestMessageHandler();
GroupQuitRequestMessageHandler GROUPQUIT_HANDLER = new GroupQuitRequestMessageHandler();
QuitHandler QUIT_HANDLER = new QuitHandler();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap()
.channel(NioServerSocketChannel.class)
.group(boss, worker)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new ProcotolFrameDecoder())
.addLast(LOGGING_HANDLER)
.addLast(MESSAGE_CODEC)
.addLast(LOGIN_HANDLER)
.addLast(CHAT_HANDLER)
.addLast(GROUPCHAT_HANDLER)
.addLast(GROUPCREATE_HANDLER)
.addLast(GROUPJOIN_HANDLER)
.addLast(GROUPMEMBERS_HANDLER)
.addLast(GROUPQUIT_HANDLER)
.addLast(QUIT_HANDLER);
}
});
Channel channel = serverBootstrap.bind(8888).sync().channel();
channel.closeFuture().sync(); //主线程阻塞在这里,如果不阻塞,则服务器主线程执行完毕会直接关闭服务器
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
@Slf4j
public class ChatClient {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
CountDownLatch WAIT_FOR_LOGIN = new CountDownLatch(1);
AtomicBoolean LOGIN = new AtomicBoolean(false);
try {
Bootstrap bootstrap = new Bootstrap()
.channel(NioSocketChannel.class)
.group(group)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new ProcotolFrameDecoder())
.addLast(LOGGING_HANDLER)
.addLast(MESSAGE_CODEC)
.addLast("client handler", new ChannelInboundHandlerAdapter() {
// 在连接建立后触发 active 事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
// 构造消息对象
LoginRequestMessage message = new LoginRequestMessage(username, password);
ctx.writeAndFlush(message);
System.out.println("等待后续操作...");
try {
WAIT_FOR_LOGIN.await();//阻塞等待后续操作
} catch (InterruptedException e) {
e.printStackTrace();
}
// 如果登录失败
if (!LOGIN.get()) {
ctx.channel().close();
return;
}
//登录后进行操作
while (true) {
System.out.println("==================================");
System.out.println("send [username] [content]");
System.out.println("gsend [group name] [content]");
System.out.println("gcreate [group name] [m1,m2,m3...]");
System.out.println("gmembers [group name]");
System.out.println("gjoin [group name]");
System.out.println("gquit [group name]");
System.out.println("quit");
System.out.println("==================================");
String command = scanner.nextLine();
String[] s = command.split(" ");
switch (s[0]) {
case "send":
ctx.writeAndFlush(new ChatRequestMessage(username, s[1], s[2]));
break;
case "gsend":
ctx.writeAndFlush(new GroupChatRequestMessage(username, s[1], s[2]));
break;
case "gcreate":
Set<String> set = new HashSet<>(Arrays.asList(s[2].split(",")));
set.add(username); // 加入自己
ctx.writeAndFlush(new GroupCreateRequestMessage(s[1], set));
break;
case "gmembers":
ctx.writeAndFlush(new GroupMembersRequestMessage(s[1]));
break;
case "gjoin":
ctx.writeAndFlush(new GroupJoinRequestMessage(username, s[1]));
break;
case "gquit":
ctx.writeAndFlush(new GroupQuitRequestMessage(username, s[1]));
break;
case "quit":
ctx.channel().close();
return;
}
}
}, "system in").start();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// log.debug("msg: {}", msg);
System.out.println("msg:"+msg);
if ((msg instanceof LoginResponseMessage)) {
LoginResponseMessage response = (LoginResponseMessage) msg;
if (response.isSuccess()) {
// 如果登录成功
LOGIN.set(true);
}
// CountDownLatch减为0,唤醒阻塞的线程
WAIT_FOR_LOGIN.countDown();
}
}
});
}
});
Channel channel = bootstrap.connect("127.0.0.1", 8888)
.sync().channel();
channel.closeFuture().sync(); //主线程阻塞在这里,如果不阻塞,则服务器主线程执行完毕会直接关闭服务器
} catch (Exception e) {
log.error("client error", e);
} finally {
group.shutdownGracefully();
}
}
}
服务器端将 handler 独立出来
@ChannelHandler.Sharable
public class LoginRequestMessageHandler extends SimpleChannelInboundHandler<LoginRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, LoginRequestMessage loginRequestMessage) throws Exception {
String username = loginRequestMessage.getUsername();
String password = loginRequestMessage.getPassword();
boolean login = UserServiceFactory.getUserService().login(username, password);
LoginResponseMessage message;
if(login){
SessionFactory.getSession().bind(channelHandlerContext.channel(),username);
message = new LoginResponseMessage(true,"登录成功");
}else{
message = new LoginResponseMessage(false,"用户名或密码不正确");
}
channelHandlerContext.writeAndFlush(message);
}
}
@ChannelHandler.Sharable
public class ChatRequestMessageHandler extends SimpleChannelInboundHandler<ChatRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ChatRequestMessage chatRequestMessage) throws Exception {
String chatRequestMessageTo = chatRequestMessage.getTo();
Channel channel = SessionFactory.getSession().getChannel(chatRequestMessageTo);
ChatResponseMessage chatResponseMessage;
if(channel == null){
chatResponseMessage = new ChatResponseMessage(false,"对方用户不存在或者不在线");
channelHandlerContext.writeAndFlush(chatResponseMessage);
}else{
chatResponseMessage = new ChatResponseMessage(true,chatRequestMessage.getFrom(),chatRequestMessage.getContent());
channel.writeAndFlush(chatResponseMessage);
}
}
}
@ChannelHandler.Sharable
public class GroupCreateRequestMessageHandler extends SimpleChannelInboundHandler<GroupCreateRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, GroupCreateRequestMessage groupCreateRequestMessage) throws Exception {
String groupName = groupCreateRequestMessage.getGroupName();
Set<String> members = groupCreateRequestMessage.getMembers();
GroupSession groupSession = GroupSessionFactory.getGroupSession();
Group group = groupSession.createGroup(groupName, members);
if(group == null) {
channelHandlerContext.writeAndFlush(new GroupCreateResponseMessage(false,"群组已存在,创建失败"));
}else{
channelHandlerContext.writeAndFlush(new GroupCreateResponseMessage(false,groupName+"群组创建成功"));
members.forEach(member -> {
Channel channel = SessionFactory.getSession().getChannel(member);
channel.writeAndFlush(new GroupCreateResponseMessage(true,"您已被拉入" + groupName+"群组"));
});
}
}
}
@ChannelHandler.Sharable
public class GroupChatRequestMessageHandler extends SimpleChannelInboundHandler<GroupChatRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, GroupChatRequestMessage groupChatRequestMessage) throws Exception {
List<Channel> membersChannel = GroupSessionFactory.getGroupSession().getMembersChannel(groupChatRequestMessage.getGroupName());
membersChannel.forEach(channel -> {
channel.writeAndFlush(new GroupChatResponseMessage(groupChatRequestMessage.getFrom(),groupChatRequestMessage.getContent()));
});
}
}
@ChannelHandler.Sharable
public class GroupJoinRequestMessageHandler extends SimpleChannelInboundHandler<GroupJoinRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, GroupJoinRequestMessage groupJoinRequestMessage) throws Exception {
Group group = GroupSessionFactory.getGroupSession().joinMember(groupJoinRequestMessage.getGroupName(), groupJoinRequestMessage.getUsername());
if(group!=null){
channelHandlerContext.writeAndFlush(new GroupJoinResponseMessage(true,groupJoinRequestMessage.getGroupName() + "群加入成功"));
}else{
channelHandlerContext.writeAndFlush(new GroupJoinResponseMessage(false,groupJoinRequestMessage.getGroupName()+ "群不存在"));
}
}
}
@ChannelHandler.Sharable
public class GroupQuitRequestMessageHandler extends SimpleChannelInboundHandler<GroupQuitRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, GroupQuitRequestMessage groupQuitRequestMessage) throws Exception {
Group group = GroupSessionFactory.getGroupSession().removeMember(groupQuitRequestMessage.getGroupName(), groupQuitRequestMessage.getUsername());
if (group != null) {
channelHandlerContext.writeAndFlush(new GroupJoinResponseMessage(true, "已退出群" + groupQuitRequestMessage.getGroupName()));
}else{
channelHandlerContext.writeAndFlush(new GroupJoinResponseMessage(false, groupQuitRequestMessage.getGroupName() + "群不存在"));
}
}
}
@ChannelHandler.Sharable
public class GroupMembersRequestMessageHandler extends SimpleChannelInboundHandler<GroupMembersRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, GroupMembersRequestMessage groupMembersRequestMessage) throws Exception {
Set<String> members = GroupSessionFactory.getGroupSession().getMembers(groupMembersRequestMessage.getGroupName());
channelHandlerContext.writeAndFlush(new GroupMembersResponseMessage(members));
}
}
@Slf4j
@ChannelHandler.Sharable
public class QuitHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
SessionFactory.getSession().unbind(ctx.channel());
log.debug("{} 已经断开", ctx.channel());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
SessionFactory.getSession().unbind(ctx.channel());
log.debug("{} 已经异常断开 异常是{}", ctx.channel(), cause.getMessage());
}
}
原因
问题
// 用来判断是不是 读空闲时间过长,或 写空闲时间过长
// 5s 内如果没有收到 channel 的数据,会触发一个 IdleState#READER_IDLE 事件
ch.pipeline().addLast(new IdleStateHandler(5, 0, 0));
// ChannelDuplexHandler 可以同时作为入站和出站处理器
ch.pipeline().addLast(new ChannelDuplexHandler() {
// 用来触发特殊事件
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception{
IdleStateEvent event = (IdleStateEvent) evt;
// 触发了读空闲事件
if (event.state() == IdleState.READER_IDLE) {
log.debug("已经 5s 没有读到数据了");
ctx.channel().close();
}
}
});
// 用来判断是不是 读空闲时间过长,或 写空闲时间过长
// 3s 内如果没有向服务器写数据,会触发一个 IdleState#WRITER_IDLE 事件
ch.pipeline().addLast(new IdleStateHandler(0, 3, 0));
// ChannelDuplexHandler 可以同时作为入站和出站处理器
ch.pipeline().addLast(new ChannelDuplexHandler() {
// 用来触发特殊事件
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception{
IdleStateEvent event = (IdleStateEvent) evt;
// 触发了写空闲事件
if (event.state() == IdleState.WRITER_IDLE) {
// log.debug("3s 没有写数据了,发送一个心跳包");
ctx.writeAndFlush(new PingMessage());
}
}
});