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 CLIENT_SESSION_MAP = new ConcurrentSkipListMap<>();
/***
* CHANNEL map
*/
public final static Map 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 {
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