using DeepCore;
using DeepCore.IO;
using DeepCore.Log;
using DeepCore.Threading;
using DeepCrystal;
using DeepCrystal.ORM.Generic;
using DeepCrystal.ORM.Query;
using DeepCrystal.RPC;
using DeepMMO.Data;
using DeepMMO.Protocol;
using DeepMMO.Protocol.Client;
using DeepMMO.Server.Area;
using DeepMMO.Server.SystemMessage;
using System;
using System.Threading.Tasks;
namespace DeepMMO.Server.Connect
{
///
/// 单个链接服务
///
public partial class SessionService : IService
{
public readonly Logger log;
public readonly string accountID;
public readonly TypeCodec client_battle_action_codec;
public readonly TypeCodec session_battle_action_codec;
protected ConnectServer.ViewSession session { get; private set; }
protected ClientEnterServerRequest enter { get; private set; }
protected IRemoteService remote_logic_service { get; private set; }
protected IRemoteService remote_area_service { get; private set; }
protected IRemoteService remote_log_service { get; private set; }
protected ClientEnterGameRequest enter_game { get; private set; }
public string Channel { get; private set; }
protected IDisposable heartbeat_timer;
protected DateTime last_heartbeat = DateTime.Now;
protected string sessionToken;
protected MappingReference accountSave;
protected QueryMappingReference queryRoleSnap;
protected MappingReference accountRoleSnapSave;
protected QueryMappingReference queryRoleDataStatusSnap;
protected bool mDisconnected = true;
public override ServiceProperties Properties
{
get
{
var ret = base.Properties;
ret.IgnoreRequestError = true;
ret.IgnoreResponseError = true;
return ret;
}
}
public SessionService(ServiceStartInfo start) : base(start)
{
this.log = LoggerFactory.GetLogger(start.Address.ServiceName);
this.accountID = start.Config["accountID"].ToString();
this.client_battle_action_codec = ConnectServer.ClientCodec.Factory.GetCodec(typeof(ClientBattleAction));
this.session_battle_action_codec = base.ServerCodec.Factory.GetCodec(typeof(SessionBattleAction));
this.Channel = start.Config["channel"]?.ToString();
}
protected override void OnDisposed()
{
this.accountSave.Dispose();
this.accountRoleSnapSave.Dispose();
this.queryRoleSnap.Dispose();
this.queryRoleDataStatusSnap.Dispose();
this.session = null;
this.enter = null;
this.remote_logic_service = null;
this.remote_area_service = null;
this.enter_game = null;
this.heartbeat_timer = null;
this.sessionToken = null;
this.accountSave = null;
this.queryRoleSnap = null;
this.queryRoleDataStatusSnap = null;
this.accountRoleSnapSave = null;
}
protected override async Task OnStartAsync()
{
this.accountSave =
new MappingReference(RPGServerPersistenceManager.TYPE_ACCOUNT_DATA, accountID, this);
this.queryRoleSnap =
new QueryMappingReference(RPGServerPersistenceManager.TYPE_ROLE_SNAP_DATA, this);
this.accountRoleSnapSave =
new MappingReference(RPGServerPersistenceManager.TYPE_ACCOUNT_ROLE_SNAP_DATA,
accountID, this);
this.queryRoleDataStatusSnap =
new QueryMappingReference(
RPGServerPersistenceManager.TYPE_ROLE_DATA_STATUS_SNAP_DATA, this);
this.Provider.AutoDispose(accountSave);
this.Provider.AutoDispose(queryRoleSnap);
this.Provider.AutoDispose(accountRoleSnapSave);
this.Provider.AutoDispose(queryRoleDataStatusSnap);
this.heartbeat_timer = base.Provider.CreateTimer(CheckHeartbeat, this,
TimeSpan.FromSeconds(TimerConfig.timer_sec_SessionKeepTimeout / 2),
TimeSpan.FromSeconds(TimerConfig.timer_sec_SessionKeepTimeout / 2));
var data = await this.accountSave.LoadDataAsync();
var roleSnap = await this.accountRoleSnapSave.LoadDataAsync();
remote_log_service = await this.Provider.GetAsync(ServerNames.LogService);
}
protected override async Task OnStopAsync(ServiceStopInfo reason)
{
this.heartbeat_timer.Dispose();
if (session != null)
{
this.session.socket.Disconnect(reason.Reason);
}
await ShutdownLogicServiceAsync("session destroy");
await this.accountSave.FlushAsync();
}
protected void CheckHeartbeat(object state)
{
if (DateTime.Now - last_heartbeat > TimeSpan.FromSeconds(TimerConfig.timer_sec_SessionKeepTimeout))
{
if (session == null || session.socket.IsConnected == false)
{
this.ShutdownSelf("timeout");
}
}
}
[RpcHandler(typeof(SystemShutdownNotify))]
public virtual void system_rpc_Handle(SystemShutdownNotify shutdown)
{
var logic = remote_logic_service;
if (logic != null)
{
logic.Invoke(new SessionDisconnectNotify() {sessionName = SelfAddress.ServiceName,});
}
this.ShutdownSelf(shutdown.reason);
}
[RpcHandler(typeof(KickPlayerNotify))]
public virtual void rpc_Handle(KickPlayerNotify notify)
{
if (session != null)
{
this.ShutdownSelf(notify.reason);
}
}
//--------------------------------------------------------------------------------------------------------------------------------------------
///
/// 首次连接或者重新连接
///
///
///
[RpcHandler(typeof(LocalBindSessionRequest), typeof(LocalBindSessionResponse), ServerNames.ConnectServerType)]
public virtual async Task connect_rpc_Handle(LocalBindSessionRequest bind)
{
if (!string.IsNullOrEmpty(bind.enter.c2s_session_token) && !string.IsNullOrEmpty(this.sessionToken) &&
bind.enter.c2s_session_token != this.sessionToken)
{
this.sessionToken = null;
return (new LocalBindSessionResponse() {s2c_code = Response.CODE_ERROR});
}
var savedLoginToken = await accountSave.LoadFieldAsync(nameof(AccountData.lastLoginToken));
var savedServerGroup = await accountSave.LoadFieldAsync(nameof(AccountData.lastLoginServerGroupID));
if (savedLoginToken != bind.enter.c2s_login_token)
{
this.sessionToken = null;
return (new LocalBindSessionResponse() {s2c_code = Response.CODE_ERROR});
}
var old_session = this.session;
if (old_session != null)
{
var disconnect = new SessionDisconnectNotify()
{
socketID = old_session.socket.ID,
sessionName = SelfAddress.ServiceName,
};
if (enter_game != null)
{
disconnect.roleID = enter_game.c2s_roleUUID;
}
//老Session暂停发包//
var logic = this.remote_logic_service;
if (logic != null)
{
logic.Invoke(disconnect);
}
//log.Log("Reconnect");
//老Session踢下线//
old_session.socket.Disconnect("New Session Reconnect");
}
else
{
//log.Log("Connect");
}
this.session = bind.session;
this.enter = bind.enter;
//登录成功后,产生新的Token用于断线重连//
this.sessionToken = Guid.NewGuid().ToString();
this.last_heartbeat = DateTime.Now;
return (new LocalBindSessionResponse()
{
session = this,
sessionToken = sessionToken,
serverGroupID = savedServerGroup,
});
}
///
/// 用户断线
///
///
[RpcHandler(typeof(SessionDisconnectNotify), ServerNames.ConnectServerType)]
public virtual void connect_rpc_Handle(SessionDisconnectNotify disconnect)
{
//log.Log("Disconnect");
last_heartbeat = DateTime.Now;
disconnect.sessionName = SelfAddress.ServiceName;
if (enter_game != null)
{
disconnect.roleID = enter_game.c2s_roleUUID;
}
// var area = remote_area_service;
// if (area != null)
// {
// area.Invoke(disconnect);
// }
//排除老Session踢下线导致的Disconnect//
if (this.session == null || disconnect.socketID == this.session.socket.ID)
{
this.session = null;
var logic = this.remote_logic_service;
if (logic != null)
{
mDisconnected = true;
logic.Invoke(disconnect);
}
}
}
///
/// 从网络线程接受协议
///
///
///
///
///
public void connect_OnReceivedBinaryImmediately(TypeCodec route_codec, BinaryMessage binary,
OnRpcBinaryReturn cb = null)
{
last_heartbeat = DateTime.Now;
if (client_battle_action_codec.MessageID == route_codec.MessageID)
{
SendToArea(route_codec, binary);
}
else
{
this.Provider.Execute(new Action(do_async_OnReceivedBinaryImmediately));
void do_async_OnReceivedBinaryImmediately()
{
SendToLogic(route_codec, binary, cb);
}
}
}
///
/// 玩家进入游戏
///
///
///
[RpcHandler(typeof(ClientEnterGameRequest), typeof(ClientEnterGameResponse), ServerNames.ConnectServerType)]
public virtual async Task client_rpc_Handle(ClientEnterGameRequest enter)
{
//验证此角色UID是否在此账号列表中
var roleIDMap = accountRoleSnapSave.Data.roleIDMap;
if (!roleIDMap.ContainsKey(enter.c2s_roleUUID))
{
return new ClientEnterGameResponse()
{
s2c_code = ClientEnterGameResponse.CODE_ROLEID_INVAILD,
};
}
//第三方/一号通验证//
var serverPassportResult = await RPGServerManager.Instance.Passport.VerifyEnterGameAsync(this.enter, enter);
if (!serverPassportResult.Verified)
{
return new ClientEnterGameResponse()
{
s2c_code = ClientEnterGameResponse.CODE_ROLE_SUSPEND,
s2c_msg = serverPassportResult.Message
};
}
//log.Log("ClientEnterGameRequest");
#region 账号封停验证.
var statusSnap = await queryRoleDataStatusSnap.LoadDataAsync(enter.c2s_roleUUID);
if (statusSnap != null)
{
//没过期代表已封停.
if (!((DateTime.UtcNow - statusSnap.SuspendDate).TotalMilliseconds > 0))
{
return (new ClientEnterGameResponse()
{
s2c_code = ClientEnterGameResponse.CODE_ROLE_SUSPEND,
s2c_suspendTime = statusSnap.SuspendDate
});
}
}
#endregion
last_heartbeat = DateTime.Now;
this.enter_game = enter;
bool reconnect = false;
var rec = new SessionReconnectNotify();
rec.sessionName = SelfAddress.ServiceName;
if (enter_game != null)
{
rec.roleID = enter_game.c2s_roleUUID;
}
var logic = remote_logic_service;
if (logic != null)
{
var oldRoleID = logic.Config["roleID"].ToString();
if (oldRoleID != enter.c2s_roleUUID)
{
log.WarnFormat(string.Format("Role Already Login : Acc={0} : Role={1} -> {2}", accountID, oldRoleID,
enter.c2s_roleUUID));
//cb(new ClientEnterGameResponse() { s2c_code = ClientEnterGameResponse.CODE_LOGIC_ALREADY_LOGIN });
await logic.ShutdownAsync("switch role");
logic = await this.CreateLogicServiceAsync(enter);
//return;
}
else
{
//if (DeepCore.Log.Logger.SHOW_LOG)
{
log.InfoFormat(string.Format("Role Reconnect : Acc={0} : Role={1}", accountID, enter.c2s_roleUUID));
}
reconnect = true;
rec.config = await InitConfig();
}
}
else
{
//if (DeepCore.Log.Logger.SHOW_LOG)
{
log.InfoFormat(string.Format("Role Connect : Acc={0} : Role={1}", accountID, enter.c2s_roleUUID));
}
logic = await this.CreateLogicServiceAsync(enter);
}
if (logic != null)
{
accountSave.SetField(nameof(AccountData.lastLoginRoleID), enter.c2s_roleUUID);
await accountSave.FlushAsync();
try
{
mDisconnected = false;
var ret = await logic.CallAsync(enter);
//log.Log("ClientEnterGameResponse: " + ret.IsSuccess);
return ret;
}
finally
{
if (reconnect)
{
logic.Invoke(rec);
}
}
}
else
{
return (new ClientEnterGameResponse() {s2c_code = ClientEnterGameResponse.CODE_LOGIC_NOT_FOUND,});
}
}
///
/// 玩家离开游戏
///
///
///
[RpcHandler(typeof(ClientExitGameRequest), typeof(ClientExitGameResponse), ServerNames.ConnectServerType)]
public virtual async Task client_rpc_Handle(ClientExitGameRequest exit)
{
//log.Log("ClientExitGameRequest");
last_heartbeat = DateTime.Now;
await ShutdownLogicServiceAsync("player exit");
this.remote_area_service = null;
return new ClientExitGameResponse();
//return Task.FromResult(new ClientExitGameResponse());
}
//--------------------------------------------------------------------------------------------------------------------------------------------
[RpcHandler(typeof(SessionBindAreaNotify), ServerNames.AreaServiceType)]
public virtual void area_rpc_Handle(SessionBindAreaNotify bind)
{
this.Provider.GetAsync(new RemoteAddress(bind.areaName, bind.areaNode)).ContinueWith(t =>
{
remote_area_service = t.GetResultAs();
});
}
[RpcHandler(typeof(SessionUnbindAreaNotify), ServerNames.AreaServiceType)]
public virtual void area_rpc_Handle(SessionUnbindAreaNotify msg)
{
remote_area_service = null;
}
// from service: TODO
[RpcHandler(true)]
public virtual void rpc_Handle(BinaryMessage msg)
{
if (mDisconnected) return;
var session = this.session;
if (session != null)
{
session.SocketSend(msg);
}
}
public override void OnWormholeTransported(RemoteAddress from, object message)
{
var session = this.session;
if (session != null)
{
if (message is BinaryMessage bin)
{
session.socket.Send(bin);
}
else if (message is ISerializable ser)
{
session.socket.Send(ser);
}
}
}
//--------------------------------------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------------------------------------
///
/// 战斗协议 ClientBattleAction 直接发往AreaService
///
///
public virtual void SendToArea(TypeCodec route_codec, BinaryMessage action)
{
try
{
var area = remote_area_service;
var enter = enter_game;
if (area != null && enter != null)
{
using (var output = IOStreamObjectPool.AllocOutputAutoRelease(ConnectServer.ClientCodec.Factory))
{
output.PutUTF(enter.c2s_roleUUID);
output.PutBytes(action.Buffer, action.BufferOffset, action.BufferLength);
var to_area = BinaryMessage.FromBuffer(session_battle_action_codec.MessageID, output.Buffer);
area.WormholeTransport(to_area);
}
}
}
catch (Exception err)
{
log.Error(err);
}
}
///
/// 逻辑协议发往LogicService
///
///
///
public virtual void SendToLogic(TypeCodec route_codec, BinaryMessage msg, OnRpcBinaryReturn callback = null)
{
var logic = remote_logic_service;
if (logic != null)
{
if (callback != null)
logic.Call(msg, callback);
else
logic.Invoke(msg);
}
else
{
log.Warn("SendToLogic Error : Logic Service Not Init : " + route_codec);
if (callback != null) callback(BinaryMessage.NULL);
}
}
protected virtual async Task CreateLogicServiceAsync(ClientEnterGameRequest enter_game)
{
var cfg = await InitConfig();
var ret = await this.Provider.CreateAsync(
ServerNames.GetLogicServiceAddress(enter_game.c2s_roleUUID, this.SelfAddress.ServiceNode), cfg);
this.remote_logic_service = ret;
return ret;
}
protected virtual async Task ShutdownLogicServiceAsync(string reason)
{
var logic = remote_logic_service;
remote_logic_service = null;
if (logic != null)
{
try
{
await logic.CallAsync(new SessionBeginLeaveRequest()
{
sessionName = SelfAddress.ServiceName,
roleID = enter_game.c2s_roleUUID,
});
}
catch (Exception err)
{
log.Error(err.Message, err);
}
try
{
var result = await logic.ShutdownAsync(reason);
log.Info("ShutdownAsync Complete : " + result);
}
catch (Exception err)
{
log.Error("ShutdownAsync Error : " + err.Message, err);
}
}
}
private async Task> InitConfig()
{
HashMap cfg = new HashMap();
var serverID = await accountSave.LoadFieldAsync(nameof(AccountData.lastLoginServerID));
var serverGroupID = await accountSave.LoadFieldAsync(nameof(AccountData.lastLoginServerGroupID));
//var privilege = await accountSave.LoadFieldAsync(nameof(AccountData.privilege));
cfg["sessionNode"] = this.SelfAddress.ServiceNode;
cfg["sessionName"] = this.SelfAddress.ServiceName;
cfg["accountID"] = enter.c2s_account;
//cfg["privilege"] = privilege.ToString();
cfg["roleID"] = enter_game.c2s_roleUUID;
cfg["serverID"] = serverID;
cfg["serverGroupID"] = serverGroupID;
cfg["channel"] = enter.c2s_clientInfo.channel;
cfg["passport"] = enter.c2s_clientInfo.sdkName;
cfg["clientVersion"] = enter.c2s_clientInfo.clientVersion;
cfg["deviceId"] = enter.c2s_clientInfo.deviceId;
cfg["deviceModel"] = enter.c2s_clientInfo.deviceModel;
cfg["deviceType"] = enter.c2s_clientInfo.deviceType;
cfg["network"] = enter.c2s_clientInfo.network;
cfg["region"] = enter.c2s_clientInfo.region;
cfg["sdkName"] = enter.c2s_clientInfo.sdkName;
cfg["sdkVersion"] = enter.c2s_clientInfo.sdkVersion;
cfg["subChannel"] = enter.c2s_clientInfo.subChannel;
cfg["userAgent"] = enter.c2s_clientInfo.userAgent;
cfg["userSource1"] = enter.c2s_clientInfo.userSource1;
cfg["userSource2"] = enter.c2s_clientInfo.userSource2;
var ip = (this.session.socket.RemoteAddress as System.Net.IPEndPoint)?.Address?.ToString();
cfg["clientIp"] = ip;
cfg["platformAccount"] = enter.c2s_clientInfo.platformAcount;
return cfg;
}
}
///
/// Connect 进程内,通知SessionService绑定ViewSession
///
public class LocalBindSessionRequest : Request, IRpcNoneSerializable
{
public ConnectServer.ViewSession session;
public ClientEnterServerRequest enter;
}
public class LocalBindSessionResponse : Response, IRpcNoneSerializable
{
public SessionService session;
public string sessionToken;
public string serverGroupID;
}
}