目录
1. 功能设计
- 实现客户端的登录功能。登录成功后,记录会话信息,主要是登录用户和channel的绑定关系,我们后续通信时,需要向对应的channel中发送消息。
- 实现两个客户端之间的通信。通过对方名称,查询到对应的channel,向其channel中发送消息。
- 实现聊天群的创建、群成员的查看、退群、加群功能。通过群session来记录群成员的channel信息。
- 客户端的退出,包括直接关闭客户端以及异常关闭。采用空闲检测+心跳机制实现。
2. 相关代码
代码网盘下载链接
链接:https://pan.baidu.com/s/11iySpSqv6D_lZ4gyYSvBEw?pwd=d79n
提取码:d79n
2.1 message消息
2.1.1 Message 消息父类
@Data
public abstract class Message implements Serializable {
public static Class<?> getMessageClass(int messageType) {
return messageClasses.get(messageType);
}
private int sequenceId;
private int messageType;
public abstract int getMessageType();
public static final int LoginRequestMessage = 0;
public static final int LoginResponseMessage = 1;
public static final int ChatRequestMessage = 2;
public static final int ChatResponseMessage = 3;
public static final int GroupCreateRequestMessage = 4;
public static final int GroupCreateResponseMessage = 5;
public static final int GroupJoinRequestMessage = 6;
public static final int GroupJoinResponseMessage = 7;
public static final int GroupQuitRequestMessage = 8;
public static final int GroupQuitResponseMessage = 9;
public static final int GroupChatRequestMessage = 10;
public static final int GroupChatResponseMessage = 11;
public static final int GroupMembersRequestMessage = 12;
public static final int GroupMembersResponseMessage = 13;
public static final int PingMessage = 14;
public static final int PongMessage = 15;
private static final Map<Integer, Class<?>> messageClasses = new HashMap<>();
static {
messageClasses.put(LoginRequestMessage, LoginRequestMessage.class);
messageClasses.put(LoginResponseMessage, LoginResponseMessage.class);
messageClasses.put(ChatRequestMessage, ChatRequestMessage.class);
messageClasses.put(ChatResponseMessage, ChatResponseMessage.class);
messageClasses.put(GroupCreateRequestMessage, GroupCreateRequestMessage.class);
messageClasses.put(GroupCreateResponseMessage, GroupCreateResponseMessage.class);
messageClasses.put(GroupJoinRequestMessage, GroupJoinRequestMessage.class);
messageClasses.put(GroupJoinResponseMessage, GroupJoinResponseMessage.class);
messageClasses.put(GroupQuitRequestMessage, GroupQuitRequestMessage.class);
messageClasses.put(GroupQuitResponseMessage, GroupQuitResponseMessage.class);
messageClasses.put(GroupChatRequestMessage, GroupChatRequestMessage.class);
messageClasses.put(GroupChatResponseMessage, GroupChatResponseMessage.class);
messageClasses.put(GroupMembersRequestMessage, GroupMembersRequestMessage.class);
messageClasses.put(GroupMembersResponseMessage, GroupMembersResponseMessage.class);
messageClasses.put(PingMessage, GroupMembersResponseMessage.class);
messageClasses.put(PongMessage, GroupMembersResponseMessage.class);
}
}
2.1.2 AbstractResponseMessage
@Data
@ToString(callSuper = true)
public abstract class AbstractResponseMessage extends Message {
private boolean success;
private String reason;
public AbstractResponseMessage() {
}
public AbstractResponseMessage(boolean success, String reason) {
this.success = success;
this.reason = reason;
}
}
2.1.3 登录请求和响应
@Data
@ToString(callSuper = true)
public class LoginRequestMessage extends Message {
private String username;
private String password;
public LoginRequestMessage() {
}
public LoginRequestMessage(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public int getMessageType() {
return LoginRequestMessage;
}
}
@Data
@ToString(callSuper = true)
public class LoginResponseMessage extends AbstractResponseMessage {
public LoginResponseMessage(boolean success, String reason) {
super(success, reason);
}
@Override
public int getMessageType() {
return LoginResponseMessage;
}
}
2.1.4 聊天请求和响应
@Data
@ToString(callSuper = true)
public class ChatRequestMessage extends Message {
private String content;
private String to;
private String from;
public ChatRequestMessage() {
}
public ChatRequestMessage(String from, String to, String content) {
this.from = from;
this.to = to;
this.content = content;
}
@Override
public int getMessageType() {
return ChatRequestMessage;
}
}
@Data
@ToString(callSuper = true)
public class ChatResponseMessage extends AbstractResponseMessage {
private String from;
private String content;
public ChatResponseMessage(boolean success, String reason) {
super(success, reason);
}
public ChatResponseMessage(String from, String content) {
this.from = from;
this.content = content;
}
@Override
public int getMessageType() {
return ChatResponseMessage;
}
}
2.1.5 群组聊天请求和响应
@Data
@ToString(callSuper = true)
public class GroupChatRequestMessage extends Message {
private String content;
private String groupName;
private String from;
public GroupChatRequestMessage(String from, String groupName, String content) {
this.content = content;
this.groupName = groupName;
this.from = from;
}
@Override
public int getMessageType() {
return GroupChatRequestMessage;
}
}
@Data
@ToString(callSuper = true)
public class GroupChatResponseMessage extends AbstractResponseMessage {
private String from;
private String content;
public GroupChatResponseMessage(boolean success, String reason) {
super(success, reason);
}
public GroupChatResponseMessage(String from, String content) {
this.from = from;
this.content = content;
}
@Override
public int getMessageType() {
return GroupChatResponseMessage;
}
}
2.1.6 群组创建请求和响应
@Data
@ToString(callSuper = true)
public class GroupCreateRequestMessage extends Message {
private String groupName;
private Set<String> members;
public GroupCreateRequestMessage(String groupName, Set<String> members) {
this.groupName = groupName;
this.members = members;
}
@Override
public int getMessageType() {
return GroupCreateRequestMessage;
}
}
@Data
@ToString(callSuper = true)
public class GroupCreateResponseMessage extends AbstractResponseMessage {
public GroupCreateResponseMessage(boolean success, String reason) {
super(success, reason);
}
@Override
public int getMessageType() {
return GroupCreateResponseMessage;
}
}
2.1.7 群组成员请求和响应
@Data
@ToString(callSuper = true)
public class GroupMembersRequestMessage extends Message {
private String groupName;
public GroupMembersRequestMessage(String groupName) {
this.groupName = groupName;
}
@Override
public int getMessageType() {
return GroupMembersRequestMessage;
}
}
@Data
@ToString(callSuper = true)
public class GroupMembersResponseMessage extends Message {
private Set<String> members;
public GroupMembersResponseMessage(Set<String> members) {
this.members = members;
}
@Override
public int getMessageType() {
return GroupMembersResponseMessage;
}
}
2.1.8 加群请求和响应
@Data
@ToString(callSuper = true)
public class GroupJoinRequestMessage extends Message {
private String groupName;
private String username;
public GroupJoinRequestMessage(String username, String groupName) {
this.groupName = groupName;
this.username = username;
}
@Override
public int getMessageType() {
return GroupJoinRequestMessage;
}
}
@Data
@ToString(callSuper = true)
public class GroupJoinResponseMessage extends AbstractResponseMessage {
public GroupJoinResponseMessage(boolean success, String reason) {
super(success, reason);
}
@Override
public int getMessageType() {
return GroupJoinResponseMessage;
}
}
2.1.9 退群请求和响应
@Data
@ToString(callSuper = true)
public class GroupQuitRequestMessage extends Message {
private String groupName;
private String username;
public GroupQuitRequestMessage(String username, String groupName) {
this.groupName = groupName;
this.username = username;
}
@Override
public int getMessageType() {
return GroupQuitRequestMessage;
}
}
@Data
@ToString(callSuper = true)
public class GroupQuitResponseMessage extends AbstractResponseMessage {
public GroupQuitResponseMessage(boolean success, String reason) {
super(success, reason);
}
@Override
public int getMessageType() {
return GroupQuitResponseMessage;
}
}
2.1.10 ping/pong心跳消息
public class PingMessage extends Message{
@Override
public int getMessageType() {
return PingMessage;
}
}
public class PongMessage extends Message{
@Override
public int getMessageType() {
return PongMessage;
}
}
2.2 codec编解码器
2.2.1 帧编解码器,二次封装
public class ProtocolFrameDecoder extends LengthFieldBasedFrameDecoder {
public ProtocolFrameDecoder() {
this(1024, 12, 4, 0, 0);
}
public ProtocolFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
int initialBytesToStrip) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
}
}
2.2.2 自定义的消息编解码器
@Slf4j
@ChannelHandler.Sharable
public class MessageSharableCodec extends MessageToMessageCodec<ByteBuf,Message> {
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, List<Object> outList) throws Exception {
ByteBuf out = ctx.alloc().buffer();
// 1. 4个字节的魔数
out.writeBytes(new byte[] {1, 2, 3, 4});
// 2. 1字节的版本
out.writeByte(1);
// 3. 1字节的序列化方式,0-jdk 1-json
out.writeByte(0);
// 4. 1字节表示消息类型,比如登录、退出..
out.writeByte(msg.getMessageType());
// 5. 4字节表示消息请求序号
out.writeInt(msg.getSequenceId());
// 由于4+1+1+1+4+4=15,我们再加一字节用于补齐,无意义
out.writeByte(0XFF);
// 6. 4字节表示消息的长度
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(msg);
byte[] bytes = os.toByteArray();
out.writeInt(bytes.length);
// 7.消息正文
out.writeBytes(bytes);
outList.add(out);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int magicNum = in.readInt();
byte version = in.readByte();
byte serializerType = in.readByte();
byte type = in.readByte();
int sequenceId = in.readInt();
in.readByte();
int length = in.readInt();
byte[] bytes = new byte[length];
in.readBytes(bytes, 0, length);
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
Message message = (Message) objectInputStream.readObject();
out.add(message);
}
}
2.3 handler处理器
2.3.1 登录处理器
@ChannelHandler.Sharable
public class LoginRequestHandler extends SimpleChannelInboundHandler<LoginRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, LoginRequestMessage msg) throws Exception {
boolean login = UserServiceFactory.getUserService().login(msg.getUsername(), msg.getPassword());
LoginResponseMessage loginResponseMessage;
if (login) {
loginResponseMessage = new LoginResponseMessage(true, "登录成功");
// 登录成功,记录session,即账号与channel的绑定关系
SessionFactory.getSession().bind(ctx.channel(), msg.getUsername());
} else {
loginResponseMessage = new LoginResponseMessage(false, "账号或密码不正确");
}
ctx.writeAndFlush(loginResponseMessage);
}
}
2.3.2 聊天处理器
@ChannelHandler.Sharable
public class ChatRequestHandler extends SimpleChannelInboundHandler<ChatRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ChatRequestMessage msg) throws Exception {
// 找到对方所在的channel
String to = msg.getTo();
Channel channel = SessionFactory.getSession().getChannel(to);
if (channel != null){
// 在线 向对方channel写入消息
channel.writeAndFlush(new ChatResponseMessage(msg.getFrom(),msg.getContent()));
} else {
// 对方channel不存在,向自己的chanel写入失败的原因
ctx.writeAndFlush(new ChatResponseMessage(false,"对方不存在或不在线"));
}
}
}
2.3.3 群聊处理器
@ChannelHandler.Sharable
public class GroupChatRequestHandler extends SimpleChannelInboundHandler<GroupChatRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, GroupChatRequestMessage msg) throws Exception {
// 获取群成员,向每个成员的channel发送消息
List<Channel> membersChannel = GroupSessionFactory.getGroupSession().getMembersChannel(msg.getGroupName());
if (membersChannel == null) {
// 不存在
ctx.channel().writeAndFlush(new GroupChatResponseMessage(false, "群聊不存在"));
return;
}
for (Channel channel : membersChannel) {
if (channel.equals(ctx.channel())) {
continue;
}
channel.writeAndFlush(new GroupChatResponseMessage(msg.getFrom(), msg.getContent()));
}
}
}
2.3.4 创建群处理器
@ChannelHandler.Sharable
public class GroupCreateRequestHandler extends SimpleChannelInboundHandler<GroupCreateRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, GroupCreateRequestMessage msg) throws Exception {
// 根据名称创建群聊,如果已存在则返回已存在的群聊信息
// 需要将创建人也拉入群聊,这里没处理,需要在client端输入members时加上自己
Group group = GroupSessionFactory.getGroupSession().createGroup(msg.getGroupName(), msg.getMembers());
if (group == null) {
// 创建成功, 向每一个人回复进群消息,向创建人回复创建成功消息
List<Channel> membersChannel = GroupSessionFactory.getGroupSession().getMembersChannel(msg.getGroupName());
for (Channel channel : membersChannel) {
if (!channel.equals(ctx.channel())) {
channel.writeAndFlush(new GroupCreateResponseMessage(true, "你已进入" + msg.getGroupName() + "群聊"));
}
}
ctx.writeAndFlush(new GroupCreateResponseMessage(true, "群聊创建成功"));
} else {
ctx.writeAndFlush(new GroupCreateResponseMessage(false, "群聊已存在"));
}
}
}
2.3.5 查看群成员处理器
@ChannelHandler.Sharable
public class GroupMemberRequestHandler extends SimpleChannelInboundHandler<GroupMembersRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, GroupMembersRequestMessage msg) throws Exception {
Set<String> members = GroupSessionFactory.getGroupSession().getMembers(msg.getGroupName());
ctx.channel().writeAndFlush(new GroupMembersResponseMessage(members));
}
}
2.3.6 加群处理器
@ChannelHandler.Sharable
public class GroupJoinRequestHandler extends SimpleChannelInboundHandler<GroupJoinRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, GroupJoinRequestMessage msg) throws Exception {
// 查询群聊是否存在,如果存在,将自己添加到组成员中,向每个成员发送新成员进群消息
Group group = GroupSessionFactory.getGroupSession().joinMember(msg.getGroupName(), msg.getUsername());
if (group == null) {
ctx.writeAndFlush(new GroupJoinResponseMessage(false, "群聊不存在"));
return;
}
List<Channel> membersChannel = GroupSessionFactory.getGroupSession().getMembersChannel(msg.getGroupName());
for (Channel channel : membersChannel) {
if (channel.equals(ctx.channel())) {
continue;
}
channel.writeAndFlush(new GroupJoinResponseMessage(true, msg.getUsername() + "进入群聊"));
}
ctx.writeAndFlush(new GroupJoinResponseMessage(true, "加入群聊成功"));
}
}
2.3.7 退群处理器
@ChannelHandler.Sharable
public class GroupQuitRequestHandler extends SimpleChannelInboundHandler<GroupQuitRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, GroupQuitRequestMessage msg) throws Exception {
// 移除本身,并向群成员发消息
Group group = GroupSessionFactory.getGroupSession().removeMember(msg.getGroupName(), msg.getUsername());
if (group == null) {
ctx.writeAndFlush(new GroupQuitResponseMessage(false, "群聊不存在"));
return;
}
List<Channel> membersChannel = GroupSessionFactory.getGroupSession().getMembersChannel(msg.getGroupName());
for (Channel channel : membersChannel) {
if (channel.equals(ctx.channel())) {
continue;
}
channel.writeAndFlush(new GroupQuitResponseMessage(true, msg.getUsername() + "退出群聊"));
}
ctx.writeAndFlush(new GroupQuitResponseMessage(true, "退出群聊成功"));
}
}
2.4 用户管理
2.4.1 UserService
public interface UserService {
/**
* 登录
* @param username 用户名
* @param password 密码
* @return 登录成功返回 true, 否则返回 false
*/
boolean login(String username, String password);
}
2.4.2 UserServiceFactory
public abstract class UserServiceFactory {
private static UserService userService = new UserServiceMemoryImpl();
public static UserService getUserService() {
return userService;
}
}
2.4.3 UserServiceMemoryImp
public class UserServiceMemoryImpl implements UserService {
private Map<String, String> allUserMap = new ConcurrentHashMap<>();
{
allUserMap.put("zhangsan", "123");
allUserMap.put("lisi", "123");
allUserMap.put("wangwu", "123");
allUserMap.put("zhaoliu", "123");
allUserMap.put("qianqi", "123");
}
@Override
public boolean login(String username, String password) {
String pass = allUserMap.get(username);
if (pass == null) {
return false;
}
return pass.equals(password);
}
}
2.5 会话管理
2.5.1 用户会话管理
/**
* 会话管理接口
*/
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 abstract class SessionFactory {
private static Session session = new SessionMemoryImpl();
public static Session getSession() {
return session;
}
}
public class SessionMemoryImpl implements Session {
private final Map<String, Channel> usernameChannelMap = new ConcurrentHashMap<>();
private final Map<Channel, String> channelUsernameMap = new ConcurrentHashMap<>();
private final Map<Channel,Map<String,Object>> channelAttributesMap = new ConcurrentHashMap<>();
@Override
public void bind(Channel channel, String username) {
usernameChannelMap.put(username, channel);
channelUsernameMap.put(channel, username);
channelAttributesMap.put(channel, new ConcurrentHashMap<>());
}
@Override
public void unbind(Channel channel) {
String username = channelUsernameMap.remove(channel);
usernameChannelMap.remove(username);
channelAttributesMap.remove(channel);
}
@Override
public Object getAttribute(Channel channel, String name) {
return channelAttributesMap.get(channel).get(name);
}
@Override
public void setAttribute(Channel channel, String name, Object value) {
channelAttributesMap.get(channel).put(name, value);
}
@Override
public Channel getChannel(String username) {
return usernameChannelMap.get(username);
}
@Override
public String toString() {
return usernameChannelMap.toString();
}
}
2.5.2 群会话管理
@Data
/**
* 聊天组,即聊天室
*/
public class Group {
// 聊天室名称
private String name;
// 聊天室成员
private Set<String> members;
public static final Group EMPTY_GROUP = new Group("empty", Collections.emptySet());
public Group(String name, Set<String> members) {
this.name = name;
this.members = members;
}
}
/**
* 聊天组会话管理接口
*/
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);
/**
* 获取组成员的名字
* @param name 组名
*/
List<String> getUsers(String name);
}
public abstract class GroupSessionFactory {
private static GroupSession session = new GroupSessionMemoryImpl();
public static GroupSession getGroupSession() {
return session;
}
}
public class GroupSessionMemoryImpl implements GroupSession {
private final Map<String, Group> groupMap = new ConcurrentHashMap<>();
@Override
public Group createGroup(String name, Set<String> members) {
Group group = new Group(name, members);
return groupMap.putIfAbsent(name, group);
}
@Override
public Group joinMember(String name, String member) {
return groupMap.computeIfPresent(name, (key, value) -> {
value.getMembers().add(member);
return value;
});
}
@Override
public Group removeMember(String name, String member) {
return groupMap.computeIfPresent(name, (key, value) -> {
value.getMembers().remove(member);
return value;
});
}
@Override
public Group removeGroup(String name) {
return groupMap.remove(name);
}
@Override
public Set<String> getMembers(String name) {
return groupMap.getOrDefault(name, Group.EMPTY_GROUP).getMembers();
}
@Override
public List<Channel> getMembersChannel(String name) {
//判断群聊存不存在
if(groupMap.get(name) == null){
return null;
}
return getMembers(name).stream()
.map(member -> SessionFactory.getSession().getChannel(member))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@Override
public List<String> getUsers(String name) {
if(groupMap.get(name) == null){
return null;
}
return new ArrayList<>(groupMap.get(name).getMembers());
}
}
2.6 客户端和服务端
server
public class ChatServer {
public static void main(String[] args) {
LoggingHandler loggingHandler = new LoggingHandler(LogLevel.INFO);
MessageSharableCodec messageSharableCodec = new MessageSharableCodec();
LoginRequestHandler loginRequestHandler = new LoginRequestHandler();
ChatRequestHandler chatRequestHandler = new ChatRequestHandler();
GroupCreateRequestHandler groupCreateRequestHandler = new GroupCreateRequestHandler();
GroupChatRequestHandler groupChatRequestHandler = new GroupChatRequestHandler();
GroupJoinRequestHandler groupJoinRequestHandler = new GroupJoinRequestHandler();
GroupQuitRequestHandler groupQuitRequestHandler = new GroupQuitRequestHandler();
GroupMemberRequestHandler groupMemberRequestHandler = new GroupMemberRequestHandler();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup(4))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtocolFrameDecoder());
pipeline.addLast(loggingHandler);
pipeline.addLast(messageSharableCodec);
pipeline.addLast(loginRequestHandler);
pipeline.addLast(chatRequestHandler);
pipeline.addLast(groupCreateRequestHandler);
pipeline.addLast(groupChatRequestHandler);
pipeline.addLast(groupJoinRequestHandler);
pipeline.addLast(groupQuitRequestHandler);
pipeline.addLast(groupMemberRequestHandler);
}
})
.bind(8080);
}
}
client
@Slf4j
public class ChatClient {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
AtomicBoolean login = new AtomicBoolean();
ChannelFuture channelFuture = new Bootstrap().group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
ChannelPipeline pipeline = nioSocketChannel.pipeline();
// pipeline.addLast(new LoggingHandler(LogLevel.INFO));
pipeline.addLast(new ProtocolFrameDecoder());
pipeline.addLast(new MessageSharableCodec());
pipeline.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("msg:{}", msg);
if (msg instanceof LoginResponseMessage) {
LoginResponseMessage loginResponseMessage = (LoginResponseMessage) msg;
if (loginResponseMessage.isSuccess()) {
login.set(true);
}
countDownLatch.countDown();
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入账号:");
String name = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
// 将账号密码发给服务端,校验账号是否正确
LoginRequestMessage message = new LoginRequestMessage(name, password);
ctx.writeAndFlush(message);
System.out.println("等待后续操作....");
try {
// 阻塞当前线程,直到收到登录响应,再放通
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!login.get()) {
// 登录失败 直接关闭channel
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(name, s[1], s[2]));
break;
case "gsend":
ctx.writeAndFlush(new GroupChatRequestMessage(name, s[1], s[2]));
break;
case "gcreate":
Set<String> numbers = new HashSet<>(Arrays.asList(s[2].split(",")));
ctx.writeAndFlush(new GroupCreateRequestMessage(s[1], numbers));
break;
case "gmembers":
ctx.writeAndFlush(new GroupMembersRequestMessage(s[1]));
break;
case "gjoin":
ctx.writeAndFlush(new GroupJoinRequestMessage(name, s[1]));
break;
case "gquit":
ctx.writeAndFlush(new GroupQuitRequestMessage(name, s[1]));
break;
case "quit":
ctx.channel().close();
break;
default:
System.out.println("指令不正确");
}
}
}).start();
}
});
}
})
.connect(new InetSocketAddress("localhost", 8080));
Channel channel = channelFuture.sync().channel();
channel.closeFuture().sync();
}
}
3.空闲检测+心跳机制
IdleStateHandler:空闲状态处理器
我们可以通过构造参数,设置读、写的超时时间,如果时间超时,则会触发对应的事件。
- readerIdleTimeSeconds -> IdleStateEvent -> IdleState#READER_IDLE
- writerIdleTimeSeconds -> IdleStateEvent -> IdleState#WRITER_IDLE
- allIdleTimeSeconds -> IdleStateEvent -> IdleState#ALL_IDLE
server
服务端需要添加对读超时的检测。
pipeline.addLast(messageSharableCodec);
// 设置空闲检测,读超时设置为5秒,即server端如果5秒没收到数据,则会触发READER_IDLE
pipeline.addLast(new IdleStateHandler(5, 0, 0));
// 空闲检测可能涉及到入站和出站,可以实现ChannelDuplexHandler子类实现
pipeline.addLast(new ChannelDuplexHandler() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (IdleState.READER_IDLE.equals(event.state())) {
// 触发读空闲超时事件
ctx.channel().close();
}
}
}
});
pipeline.addLast(loginRequestHandler);
client
客户端向服务端发送心跳,心跳时长要比服务端空闲检测时间短。
pipeline.addLast(new MessageSharableCodec());
// 写超时设置为3秒,即客户端如果3秒没写出数据,则触发WRITER_IDLE,此刻我们需要补一条心跳数据
pipeline.addLast(new IdleStateHandler(0, 3, 0));
pipeline.addLast(new ChannelDuplexHandler() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (IdleState.WRITER_IDLE.equals(event.state())) {
// 触发写空闲超时事件, 向服务端发送ping,服务端可以回复pong(这块没加)
ctx.channel().writeAndFlush(new PingMessage());
}
}
}
});
pipeline.addLast(new ChannelInboundHandlerAdapter() {