从零开始手写mmo游戏从框架到爆炸(四)— session session

      session是灵魂,没有session谈什么框架。其实session就是用户的数据绑定和channel的绑定,有了session我们就可以做很多事情。

      所以我们首先要创建一个用户对象,这里我们先说明一下,本框架目前暂时不涉及数据库的操作,后续数据的读写都基于内存和文件,当然既然是从框架到爆炸,谁知道是不是很快就接入springboot和mybatis了呢。

      创建一个Session类,用于存放用户信息和channel。同时创建一个SessionManger用于封装各类操作。具体如下:

Session.java

package com.loveprogrammer.base.bean.session;

import io.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName Session
 * @Description 用户session
 * @Author admin
 * @Date 2024/2/4 17:25
 * @Version 1.0
 */
public class Session {

    private static final Logger logger = LoggerFactory.getLogger(Session.class);

    /**
     * sessionId
     */
    private int id;

    /**
     * 房间id,PVP 或者团队副本的时候使用
     */
    private int roomId;

    /**
     * 用户id
     */
    private String userId;

    /**
     * 用户昵称
     */
    private String nickname;

    /***
     * 绑定的channel
     */
    private Channel channel;

    public Session(int id,Channel channel) {
        this.id = id;
        this.channel = channel;
    }

    /**
     * 关闭与客户端的连接
     */
    public void close() {
        if (channel == null) {
            return;
        }
        try {
            if (channel.isActive() || channel.isOpen()) {
                channel.close().sync();
            }
        } catch (InterruptedException e) {
            logger.error("channel.close find error ", e);
        }

    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getRoomId() {
        return roomId;
    }

    public void setRoomId(int roomId) {
        this.roomId = roomId;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public Channel getChannel() {
        return channel;
    }

    public void setChannel(Channel channel) {
        this.channel = channel;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }
}

SessionManager.java

package com.loveprogrammer.base.network.support;

import com.loveprogrammer.base.bean.session.Session;
import io.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ClassName SessionManager
 * @Description 会话管理类
 * @Author admin
 * @Date 2024/2/4 17:44
 * @Version 1.0
 */
public class SessionManager {
    private static final Logger logger = LoggerFactory.getLogger(SessionManager.class);
    private static final SessionManager instance = new SessionManager();

    public static SessionManager getInstance() {
        return instance;
    }

    private SessionManager() {
        logger.info("SessionManager init success");
    }

    /***
     * 会话 map
     */
    public final static Map<Integer, Session> CLIENT_SESSION_MAP = new ConcurrentSkipListMap<>();

    /***
     * CHANNEL map
     */
    public final static Map<String, Integer> CHANNEL_ID_MAP = new ConcurrentHashMap<>();

    private final static AtomicInteger CLIENT_ATOMIC_ID = new AtomicInteger(1);

    public final static int getClientId() {
        return CLIENT_ATOMIC_ID.getAndIncrement();
    }

    public Session getSession(Integer id) {
        return CLIENT_SESSION_MAP.get(id);
    }

    public Session getById(Integer id) {
        return CLIENT_SESSION_MAP.get(id);
    }

    public int getId(Channel channel) {
        String longId = channel.id().asLongText();
        Integer clientId = CHANNEL_ID_MAP.get(longId);
        if (null == clientId) {
            clientId = getClientId();
            CHANNEL_ID_MAP.put(longId, clientId);
        }
        return clientId;
    }

    /**
     * 创建session
     *
     * @param channel 与客户端连接的管道
     * @return session
     */
    public Session create(Channel channel) {
        Session session = getSessionByChannel(channel);
        if (session == null) {
            int id = getId(channel);
            session = new Session(id,channel);
            CLIENT_SESSION_MAP.put(id,session);
            logger.info("session 创建成功");
        } else {
            logger.error("新连接建立时已存在Session,注意排查原因 " + channel.toString());
        }
        return session;
    }

//    /**
//     * 注册sesson
//     *
//     * @param session session
//     * @param user    用户
//     */
//    public void register(Session session, IUser user) {
//        session.registerUser(user);
//        uidSessionMap.put(session.getUser().getId(), session);
//    }

    /**
     * 通过channel关闭session
     *
     * @param channel 与客户端连接的管道
     */
    public void close(Channel channel) {
        Session session = getSessionByChannel(channel);
        if (session != null) {
            close(session);
        }
    }

    /**
     * 关闭session
     *
     * @param session 要关闭的session
     */
    private void close(Session session) {
        unregister(session);
        session.close();
        logger.info("session close success");
    }

    /**
     * 将之前注册好的session从map中移除
     *
     * @param session 将之前注册好的session从map中移除
     */
    private void unregister(Session session) {
        if (session != null) {
            int sessionId = session.getId();
            CLIENT_SESSION_MAP.remove(sessionId);
            CLIENT_SESSION_MAP.remove(sessionId);
            logger.info("Session unregister, userId={}, sessionId={}", session.getUserId(), sessionId);
        }
    }

    public Session getSessionByChannel(Channel channel) {
        int id = getId(channel);
        return getById(id);
    }

    public void sendMessage(Channel channel, String msg) {
        channel.writeAndFlush(msg);
    }

    public void sendMessage(Session session, String msg) {
        session.getChannel().writeAndFlush(msg);
    }
}

 修改NetworkListener.java

package com.loveprogrammer.base.network.support;

import com.loveprogrammer.base.bean.session.Session;
import com.loveprogrammer.base.network.listener.INetworkEventListener;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NetworkListener implements INetworkEventListener {

    protected static final Logger logger = LoggerFactory.getLogger(NetworkListener.class);

    @Override
    public void onConnected(ChannelHandlerContext ctx) {
        logger.info("建立连接");
        SessionManager.getInstance().create(ctx.channel());
    }

    @Override
    public void onDisconnected(ChannelHandlerContext ctx) {
        logger.info("建立断开");
        SessionManager.getInstance().close(ctx.channel());
    }

    @Override
    public void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
        logger.warn("异常发生", throwable);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, String msg) {
        logger.info("数据内容:data=" + msg);
        String result = "我是服务器,我收到了你的信息:" + msg;

        Session session = SessionManager.getInstance().getSessionByChannel(ctx.channel());
        result += ",sessionId = " + session.getId();

        result += "\r\n";
        SessionManager.getInstance().sendMessage(ctx.channel(),result);
    }
}

修改 TcpMessageStringHandler.java

package com.loveprogrammer.base.network.channel.tcp.str;

import com.loveprogrammer.base.network.listener.INetworkEventListener;
import com.loveprogrammer.base.network.support.SessionManager;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName TcpMessageStringHandler
 * @Description tcp消息处理类
 * @Author admin
 * @Date 2024/2/4 15:16
 * @Version 1.0
 */
public class TcpMessageStringHandler extends SimpleChannelInboundHandler<String> {
    private static final Logger logger = LoggerFactory.getLogger(TcpMessageStringHandler.class);

    private final INetworkEventListener listener;

    public TcpMessageStringHandler(INetworkEventListener listener) {
        this.listener = listener;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
        listener.onExceptionCaught(ctx,throwable);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        super.channelRead(ctx, msg);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) {

        listener.channelRead(ctx,msg);
        // ctx.writeAndFlush(result);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        listener.onConnected(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        listener.onDisconnected(ctx);
    }
}

 运行起来效果如下:

12:08:40.527 [nioEventLoopGroup-2-1] [INFO ] com.loveprogrammer.netty.simple.SocketClientHandler:30 --- 数据内容:data=我是服务器,我收到了你的信息:大哥你好,我是客户端,我又来了,sessionId = 2
12:08:40.527 [nioEventLoopGroup-2-1] [INFO ] com.loveprogrammer.netty.simple.SocketClientHandler:34 --- 向服务器发送消息 大哥你好,我是客户端,我又来了

12:08:40.527 [nioEventLoopGroup-2-1] [INFO ] com.loveprogrammer.netty.simple.SocketClientHandler:30 --- 数据内容:data=我是服务器,我收到了你的信息:大哥你好,我是客户端,我又来了,sessionId = 2
12:08:40.527 [nioEventLoopGroup-2-1] [INFO ] com.loveprogrammer.netty.simple.SocketClientHandler:34 --- 向服务器发送消息 大哥你好,我是客户端,我又来了

12:08:40.527 [nioEventLoopGroup-2-1] [INFO ] com.loveprogrammer.netty.simple.SocketClientHandler:30 --- 数据内容:data=我是服务器,我收到了你的信息:大哥你好,我是客户端,我又来了,sessionId = 2
12:08:40.527 [nioEventLoopGroup-2-1] [INFO ] com.loveprogrammer.netty.simple.SocketClientHandler:34 --- 向服务器发送消息 大哥你好,我是客户端,我又来了

上一章:从零开始手写mmo游戏从框架到爆炸(三)— 服务启动接口与网络事件监听器-CSDN博客

下一章:

从零开始手写mmo游戏从框架到爆炸(五)— 集成springboot-CSDN博客

 全部源码详见:

gitee : eternity-online: 多人在线mmo游戏 - Gitee.com

分支:step-04

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值