4. Netty+SpringBoot实现IM服务 之 用户与channel绑定

系列文章目录

  1. 技术选型、简单实现
  2. 16进制数据及解决半包和粘包
  3. 拆包器与心跳检测
  4. 用户与channel绑定
  5. 通过Redis的订阅机制实现服务集群


定义登录协议

在这里插入图片描述
为了方便演示,我们简单的定义一下登录协议,协议的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系统已经基本搭建起来了。之后就是实现具体的业务逻辑了。
我把代码分享给大家 : 项目地址

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值