系列文章目录
授权登录
定义登录协议
为了方便演示,我们简单的定义一下登录协议,协议的body就是用户的ID。
用户与channel绑定
存储用户ID和channel的对应关系
ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
定义一个map实现用户ID和channel的绑定关系。这样通过用户的ID可以直接找到channel
存储channel和用户ID的对应关系
AttributeKey<String> key = AttributeKey.valueOf("user");
channel.attr(key).set(userId);
使用netty提供的Attribute实现channel和用户ID的绑定。
关键代码展示
X.java
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Mr.Guo
* @date 2021/3/9 下午4:15
*/
public class X {
public static final ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
/**
* 判断一个通道是否有用户在使用
* 可做信息转发时判断该通道是否合法
*
* @param channel
* @return
*/
public static boolean hasUser(Channel channel) {
AttributeKey<String> key = AttributeKey.valueOf("user");
//netty移除了这个map的remove方法,这里的判断谨慎一点
return (channel.hasAttr(key) || channel.attr(key).get() != null);
}
/**
* 上线一个用户
*
* @param channel
* @param userId
*/
public static void online(Channel channel, String userId) {
//先判断用户是否在web系统中登录?
//这部分代码个人实现
channelMap.put(userId, channel);
AttributeKey<String> key = AttributeKey.valueOf("user");
channel.attr(key).set(userId);
}
/**
* 根据用户id获取该用户的通道
*
* @param userId
* @return
*/
public static Channel getChannelByUserId(String userId) {
return channelMap.get(userId);
}
/**
* 判断一个用户是否在线
*
* @param userId
* @return
*/
public static Boolean online(String userId) {
return channelMap.containsKey(userId) && channelMap.get(userId) != null;
}
}
SocketHandlerAdapter
import cn.xa87.im.demo.X;
import cn.xa87.im.demo.packet.Xa87Packet;
import com.alibaba.fastjson.JSON;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author Mr.Guo
* @date 2021/3/4 下午5:03
*/
@Slf4j
@Component
@ChannelHandler.Sharable
public class SocketHandlerAdapter extends ChannelHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
Xa87Packet packet = (Xa87Packet) msg;
String body = new String(packet.getBody());
log.info("收到信息:{}", JSON.toJSONString(msg));
log.info("body==>{}", body);
if (packet.getCommand() == 1) {
X.online(ctx.channel(), body);
}
ctx.writeAndFlush(msg);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent) evt).state();
if (state == IdleState.READER_IDLE) {
// 在规定时间内没有收到客户端的上行数据, 主动断开连接
log.info("{}超时,断开连接", ctx.channel().id());
ctx.close();
}
} else {
super.userEventTriggered(ctx, evt);
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.info("channelInactive");
Attribute<String> attr = ctx.attr(AttributeKey.valueOf("user"));
if (null != attr && null != attr.get()) {
X.channelMap.remove(attr.get());
attr.remove();
}
super.channelInactive(ctx);
}
}
总结
到此一个简单的IM系统已经基本搭建起来了。之后就是实现具体的业务逻辑了。
我把代码分享给大家 : 项目地址