java游戏服务器开发之九-Session

相信做过web的小哥应该都知道session的概念,简单来说,session翻译是会话,用来保存用户浏览web页面的一些信息(用户信息)。
我们的框架也需要这么一个session,用于保存用户信息,与客户端连接的管道(Channel)。
有了这个session,我们能做到在系统的各个地方能获取到该用户的信息,及回消息给客户端
增加的类
AttributeUtil
IUser
Session
SessionAttributeKey
SessionManager

TimeUtil


提前写这几个类为session类服务,
1.我们有一个用户类,里面是具体的用户信息,但是我们写的是一个框架,不可能把具体的用户属性写好,所以我们写一个IUser接口,里面放3个肯定会有的方法,getId()/getStatus()/getRole(),用户id、状态、角色。
2.时间管理类TimeUtil,可以获取时间,用于设置session的创建时间
现在就是关键类Session的书写,里面很简单,channel/user/createTime,与客户端的管道/用户信息/创建时间。
里面的几个关键方法,
构造函数--》设置channel和createTime创建时间
registerUser--》玩session里面写入user,一般是在登录之后调用
close--》关闭与客户端的连接

而且我把所有的方法修饰符都改成了private或者默认,使这些仅作用于类里,及包内。为了所有的session操作都通过SessionManager进行。


下面就是SessionManager,说之前先说下AttributeUtil、SessionAttributeKey
因为我们所有的连接最开始有的一个数据都是基于连接(channel),而netty的Channel继承了AttributeMap,可以往里面绑定数据。
调用方式就是channel.attr(attributeKey).set(value)/channel.attr(attributeKey).get()。
这样我们就可以写一个简单的封装AttributeUtil,暴露一个set和get方法。
而attributeKey的话是一个唯一常量,所以我们写在SessionAttributeKey中。
(详细了解可以看 Netty的AttributeMap属性 https://blog.csdn.net/cmqwan/article/details/80775092)


而我们就可以将channel和session进行绑定。就先在SessionAttributeKey写一个Session的key,设置和获取的时候就是用AttributeUtil就好了。
具体说到SessionManager,首先设置SessionManager为单例模式
里面一个uidSessionMap,用于管理已经登录的session。
对应Session里面的方法,创建、注册、关闭。
关键:通过session发送信息sendMessage(Session session, String msg){session.getChannel().writeAndFlush(msg);}


这些类都写完了,就看怎么使用了
在NetworkListener的onConnected中SessionManager.getInstance().create(ctx.channel());
在NetworkListener的onDisconnected中SessionManager.getInstance().close(ctx.channel());


重新运行服务端和客户端,完美

看下具体代码吧


IUser

/**
 * Copyright (C), 2015-2018
 * FileName: IUser
 * Author:   zhao
 * Date:     2018/6/22 15:46
 * Description: 用户的抽象类,主要用于session之中
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.base.session;

/**
 * 〈一句话功能简述〉<br>
 * 〈用户的抽象类,主要用于session之中〉
 *
 * @author zhao
 * @date 2018/6/22 15:46
 * @since 1.0.0
 */
public interface IUser {

  /**
   * 获取用户id
   *
   * @return 用户id
   */
  Integer getId();

  /**
   * 获取用户状态id
   *
   * @return 状态id
   */
  Integer getStatus();

  /**
   * 获取用户角色类型id
   *
   * @return 角色类型id
   */
  Integer getRole();

}

TimeUtil

/**
 * Copyright (C), 2015-2018
 * FileName: TimeUtil
 * Author:   zhao
 * Date:     2018/6/22 17:07
 * Description: TimeUtil时间工具
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.base.util;

import java.util.Calendar;
import java.util.Date;

/**
 * 〈一句话功能简述〉<br>
 * 〈TimeUtil时间工具〉
 *
 * @author zhao
 * @date 2018/6/22 17:07
 * @since 1.0.0
 */
public class TimeUtil {

  private TimeUtil() {
  }

  /**
   * 获取系统距1970年1月1日总毫秒
   *
   * @return 距1970年1月1日总毫秒
   */
  public static long getSysCurTimeMillis() {
    return getCalendar().getTimeInMillis();
  }

  /**
   * 获取系统时间
   *
   * @return 系统时间
   */
  private static Calendar getCalendar() {
    Calendar nowCalendar = Calendar.getInstance();
    nowCalendar.setTime(new Date());
    return nowCalendar;
  }
}

Session

/**
 * Copyright (C), 2015-2018
 * FileName: Session
 * Author:   zhao
 * Date:     2018/6/22 15:44
 * Description: Session会话
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.base.session;

import com.lizhaoblog.base.util.TimeUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.channel.Channel;

/**
 * 〈一句话功能简述〉<br>
 * 〈Session会话,包括管道及用户信息〉
 * 里面的所有信息都需要交由SessionManager操作
 * 外部无法直接访问到这个类的方法
 *
 * @author zhao
 * @date 2018/6/22 15:44
 * @since 1.0.0
 */
public class Session {

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

  /**
   * 与客户端的管道
   */
  private final Channel channel;
  /**
   * 用户信息
   * 用户信息可能为空。
   * 只有在register(登录)之后,里面才会赋值
   */
  private IUser user;
  /**
   * 创建时间
   */
  private final long createTime;

  Session(Channel channel) {
    this.channel = channel;
    this.createTime = TimeUtil.getSysCurTimeMillis();
  }

  /**
   * 玩session里面写入user,一般是在登录之后调用
   * @param user 用户 信息
   */
  void registerUser(IUser user) {
    this.user = user;
  }

  /**
   * 关闭与客户端的连接
   */
  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);
    }

  }

  IUser getUser() {
    return user;
  }

  Channel getChannel() {
    return channel;
  }
}


SessionAttributeKey

/**
 * Copyright (C), 2015-2018
 * FileName: SessionAttributeKey
 * Author:   zhao
 * Date:     2018/6/22 16:12
 * Description: session相关的AttributeKey
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.base.session;

import io.netty.util.AttributeKey;

/**
 * 〈一句话功能简述〉<br>
 * 〈session相关的AttributeKey〉
 *
 * @author zhao
 * @date 2018/6/22 16:12
 * @since 1.0.0
 */
public class SessionAttributeKey {

  /**
   * AttributeKey Session
   */
  public static final AttributeKey<Session> SESSION = AttributeKey.newInstance("SESSION");
}

AttributeUtil

/**
 * Copyright (C), 2015-2018
 * FileName: AttributeUtil
 * Author:   zhao
 * Date:     2018/6/22 16:15
 * Description: Attribute操作的工具类
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.base.session;

import io.netty.channel.Channel;
import io.netty.util.AttributeKey;

/**
 * 〈一句话功能简述〉<br>
 * 〈Attribute操作的工具类〉
 *
 * @author zhao
 * @date 2018/6/22 16:15
 * @since 1.0.0
 */
public class AttributeUtil {

  /**
   * 获取绑定到channel上面的数据
   *
   * @param channel 与客户端连接的管道
   * @param key     {@link SessionAttributeKey}
   * @param <T>     绑定的类型
   * @return  绑定到channel上面的数据
   */
  public static <T> T get(Channel channel, AttributeKey<T> key) {
    return channel.attr(key).get();
  }

  /**
   * 绑定某个数据到channel
   *
   * @param channel 与客户端连接的管道
   * @param key     {@link SessionAttributeKey}
   * @param value   绑定的内容
   * @param <T> 绑定的类型
   */
  public static <T> void set(Channel channel, AttributeKey<T> key, T value) {
    channel.attr(key).set(value);
  }

  private AttributeUtil() {
  }
}

SessionManager


/**
 * Copyright (C), 2015-2018
 * FileName: SessionManager
 * Author:   zhao
 * Date:     2018/6/22 16:40
 * Description: Session管理类
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.base.session;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import io.netty.channel.Channel;

/**
 * 〈一句话功能简述〉<br>
 * 〈Session管理类〉
 *
 * @author zhao
 * @date 2018/6/22 16:40
 * @since 1.0.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");
  }

  /**
   * 已经登录的session管理
   */
  private final ConcurrentMap<Integer, Session> uidSessionMap = new ConcurrentHashMap<>(16);

  public Session getSession(Integer uid) {
    return uidSessionMap.get(uid);
  }

  /**
   * 创建session
   *
   * @param channel 与客户端连接的管道
   * @return session
   */
  public Session create(Channel channel) {
    Session session = getSessionByChannel(channel);
    if (session == null) {
      session = new Session(channel);
      AttributeUtil.set(channel, SessionAttributeKey.SESSION, 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) {
      AttributeUtil.set(session.getChannel(), SessionAttributeKey.SESSION, null);

      IUser user = session.getUser();
      if (user != null) {
        boolean remove = uidSessionMap.remove(user.getId(), session);
        logger.info("Session unregister, userId={}, remove={}", user.getId(), remove);
      }
    }
  }

  public Session getSessionByChannel(Channel channel) {
    return AttributeUtil.get(channel, SessionAttributeKey.SESSION);
  }

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

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

}


AttributeUtil
/**
 * Copyright (C), 2015-2018
 * FileName: AttributeUtil
 * Author:   zhao
 * Date:     2018/6/22 16:15
 * Description: Attribute操作的工具类
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.base.session;

import io.netty.channel.Channel;
import io.netty.util.AttributeKey;

/**
 * 〈一句话功能简述〉<br>
 * 〈Attribute操作的工具类〉
 *
 * @author zhao
 * @date 2018/6/22 16:15
 * @since 1.0.0
 */
public class AttributeUtil {

  /**
   * 获取绑定到channel上面的数据
   *
   * @param channel 与客户端连接的管道
   * @param key     {@link SessionAttributeKey}
   * @param <T>     绑定的类型
   * @return  绑定到channel上面的数据
   */
  public static <T> T get(Channel channel, AttributeKey<T> key) {
    return channel.attr(key).get();
  }

  /**
   * 绑定某个数据到channel
   *
   * @param channel 与客户端连接的管道
   * @param key     {@link SessionAttributeKey}
   * @param value   绑定的内容
   * @param <T> 绑定的类型
   */
  public static <T> void set(Channel channel, AttributeKey<T> key, T value) {
    channel.attr(key).set(value);
  }

  private AttributeUtil() {
  }
}



上面的代码在码云上 https://gitee.com/lizhaoandroid/JgServer
可以加qq群一起探讨Java游戏服务器开发的相关知识 676231524

猜你喜欢

转载自blog.csdn.net/cmqwan/article/details/80779013