Scut:GameWebSocketHost 解析

  想使用 Scut 做的是一个短连接项目,所以先直接看 GameWebSocketHost 了。

  先来看下 GameWebSocketHost 的成员:

protected bool EnableHttp;
public IActionDispatcher ActionDispatcher;
private EnvironmentSetting _setting;
private SocketListener socketListener;

  由之前的分析可知:SocketListener 搞定了监听、底层IO,那么ActionDispatcher 应该负责上层消息的分发了。

  构造函数做了各种参数准备、注册回调,不赘述。

  最为关键的是几个回调函数的具体实现:

1. GameSession 建立:

socketListener.Connected += new ConnectionEventHandler(socketLintener_OnConnectCompleted);
        private void socketLintener_OnConnectCompleted(ISocket sender, ConnectionEventArgs e)
{
try
{
var session = GameSession.CreateNew(e.Socket.HashCode, e.Socket, socketListener);  //最重要的是 GameSession 做什么用?
session.HeartbeatTimeoutHandle += OnHeartbeatTimeout;
OnConnectCompleted(sender, e);
}
catch (Exception err)
{
TraceLog.WriteError("ConnectCompleted error:{0}", err);
}
}

  GameSession:会话,这是每个连接在应用层的表示:

     session = new GameSession(keyCode, socket, appServer);

        private GameSession(Guid sid, ExSocket exSocket, ISocket appServer)
: this(sid, null)
{
InitSocket(exSocket, appServer);
} internal void InitSocket(ExSocket exSocket, ISocket appServer)
{
_exSocket = exSocket;  //GameSession 记录了这个连接所使用的 ExSocket
    public class ExSocket
{
     public Guid HashCode;           //哈希唯一标识
private Socket socket;          //管理了io套接字
private IPEndPoint remoteEndPoint; //管理了远程节点信息
private ConcurrentQueue<SocketAsyncResult> sendQueue; //管理通过该io套接字发送的消息队列
private int isInSending;
internal DateTime LastAccessTime; public ExSocket(Socket socket)
{
HashCode = Guid.NewGuid();
sendQueue = new ConcurrentQueue<SocketAsyncResult>();
this.socket = socket;
InitData();
}
... ...
}

if (_exSocket != null) _remoteAddress = _exSocket.RemoteEndPoint.ToNotNullString();
AppServer = appServer; //还记录了所使用的 套接字监听器
if (User != null)
{
//update userid with sid.
_userHash[UserId] = KeyCode;
}
}

  

  回顾一下 SocketListener 的代码:

    public class SocketListener : ISocket
{
public event ConnectionEventHandler Connected;
private void OnConnected(ConnectionEventArgs e)  //当发生“成功连接”时,实际上就是调用了
{
if (Connected != null)
{
Connected(this, e);
}
}
... ...
} public class ConnectionEventArgs : EventArgs
{
public ExSocket Socket { get; set; }
public DataMeaage Meaage { get; set; }
... ...
}

  在 ProcessAccept 中:           

                    //之前已经成功创建了连接            
       SocketAsyncEventArgs ioEventArgs = this.ioEventArgsPool.Pop();
ioEventArgs.AcceptSocket = acceptEventArgs.AcceptSocket;
var dataToken = (DataToken)ioEventArgs.UserToken;
ioEventArgs.SetBuffer(dataToken.bufferOffset, socketSettings.BufferSize);
var exSocket = new ExSocket(ioEventArgs.AcceptSocket);   //将创建后的io套接字交给 ExSocket 管理
exSocket.LastAccessTime = DateTime.Now;
dataToken.Socket = exSocket;
acceptEventArgs.AcceptSocket = null;
ReleaseAccept(acceptEventArgs, false);
try
{
OnConnected(new ConnectionEventArgs { Socket = exSocket });  //在这里事实上调用了 socketLintener_OnConnectCompleted
            }

2. 接收并处理消息

  在 SocketListener 中:

private void ProcessReceive(SocketAsyncEventArgs ioEventArgs)
{
... ...
OnDataReceived(new ConnectionEventArgs { Socket = exSocket, Meaage = message });
... ...
}

  OnDataReceived 就是 GameWebSocketHost 为自己管理的 SocketListener 所注册的 “数据接收” 处理API。

        private void OnDataReceived(ISocket sender, ConnectionEventArgs e)
{
try
{
RequestPackage package;
if (!ActionDispatcher.TryDecodePackage(e, out package))   //在 EnvironmentSetting 的构造中可以看到 ActionDispatcher = new ScutActionDispatcher();  
{                                   //同时将 ConnetctionEvenArgs 的 message 组装成 RequestPackage
//check command
string command = e.Meaage.Message;
if ("ping".Equals(command, StringComparison.OrdinalIgnoreCase))
{
OnPing(sender, e);
return;
}
if ("pong".Equals(command, StringComparison.OrdinalIgnoreCase))
{
OnPong(sender, e);
return;
}
OnError(sender, e);
return;
}
var session = GetSession(e, package);   //首次连接时已经建立了sesseion,此时直接获取即可
if (CheckSpecialPackge(package, session)) //处理业务层的中断请求包、心跳请求包 
{
return;
}
package.Bind(session);            //数据请求包绑定session,后面应该会需要从请求来获取session?
ProcessPackage(package, session).Wait(); //处理具体的请求包 }
catch (Exception ex)
{
TraceLog.WriteError("Received to Host:{0} error:{1}", e.Socket.RemoteEndPoint, ex);
}
}

  

  ProcessPackage 是比较重要的API:

     private async System.Threading.Tasks.Task ProcessPackage(RequestPackage package, GameSession session)  //异步任务-多线程并发处理消息
{
if (package == null) return; try
{
ActionGetter actionGetter;
byte[] data = new byte[];
if (!string.IsNullOrEmpty(package.RouteName))  //客户端通过本游戏对其他游戏进行远程调用
{
actionGetter = ActionDispatcher.GetActionGetter(package, session);
if (CheckRemote(package.RouteName, actionGetter))
{
MessageStructure response = new MessageStructure();
OnCallRemote(package.RouteName, actionGetter, response);
data = response.PopBuffer();
}
else
{
return;
}
}
else
{
SocketGameResponse response = new SocketGameResponse();
response.WriteErrorCallback += ActionDispatcher.ResponseError;
actionGetter = ActionDispatcher.GetActionGetter(package, session);  //将 package 与 session 封装在一起
DoAction(actionGetter, response); //利用本服务器的逻辑脚本处理模块处理消息
        protected void DoAction(ActionGetter actionGetter, BaseGameResponse response)
{
if (GameEnvironment.IsRunning && !ScriptEngines.IsCompiling)
{
OnRequested(actionGetter, response);
ActionFactory.Request(actionGetter, response);    //Request 是如何操作的?
}
else
{
response.WriteError(actionGetter, Language.Instance.MaintainCode, Language.Instance.ServerMaintain);
}
}
                    data = response.ReadByte();
}
try
{
if (session != null && data.Length > )
{
await session.SendAsync(actionGetter.OpCode, data, , data.Length, OnSendCompleted);
}
}
catch (Exception ex)
{
TraceLog.WriteError("PostSend error:{0}", ex);
} }
catch (Exception ex)
{
TraceLog.WriteError("Task error:{0}", ex);
}
finally
{
if (session != null) session.ExitSession();
}
}
        public static void Request(ActionGetter actionGetter, BaseGameResponse response)
{
Request(GameEnvironment.Setting.ActionTypeName, actionGetter, response);
} public static void Request(string typeName, ActionGetter actionGetter, BaseGameResponse response)
{
var actionId = actionGetter.GetActionId().ToInt();
string tempName = string.Format(typeName, actionId);
string errorInfo = "";
try
{
bool isRL = BaseStruct.CheckRunloader(actionGetter);
if (isRL || actionGetter.CheckSign())
{
BaseStruct action = FindRoute(typeName, actionGetter, actionId);  //typeName 用于寻找消息处理模块,并返回一个基类为 BaseStruct 的实例
Process(action, actionGetter, response); //通过该句柄执行消息逻辑处理,并获取返回值,可见 BaseStruct 应该是更偏进业务层次的封装了
if (action != null)
{
return;
}
}
else
{
errorInfo = Language.Instance.SignError;
TraceLog.WriteError("Action request {3} error:{2},rl:{0},param:{1}", isRL, actionGetter.ToString(), errorInfo, tempName);
}
}
catch (Exception ex)
{
errorInfo = Language.Instance.ServerBusy;
TraceLog.WriteError("Action request {0} error:{1}\r\nparam:{2}", tempName, ex, actionGetter.ToString());
}
response.WriteError(actionGetter, Language.Instance.ErrorCode, errorInfo);
}

4. GameStruct 结构:

    public abstract class GameStruct
{ /// <summary>
/// 默认的返回错误信息
/// </summary>
public const string DefaultErrorInfo = "Access fail"; /// <summary>
/// 接口访问处理情况
/// </summary>
public enum LogActionStat
{
/// <summary>
/// 接口访问成功
/// </summary>
Sucess = ,
/// <summary>
/// 访问失败
/// </summary>
Fail
}
/// <summary>
///
/// </summary>
protected bool IsWebSocket = false; /// <summary>
///
/// </summary>
protected Encoding encoding = Encoding.UTF8;
/// <summary>
/// 接口访问开始时间
/// </summary>
protected DateTime iVisitBeginTime;
/// <summary>
/// 接口访问结束时间
/// </summary>
protected DateTime iVisitEndTime;
private string logActionResult = "";
/// <summary>
///
/// </summary>
protected ActionGetter actionGetter; /// <summary>
/// 写日志的对象
/// </summary>
protected BaseLog oBaseLog = null;
/// <summary>
/// 数据类
/// </summary>
protected DataStruct dataStruct = new DataStruct(); /// <summary>
/// 当前游戏会话
/// </summary>
public GameSession Current { get; internal set; } /// <summary>
///
/// </summary>
public int UserId
{
get
{
return Current != null ? Current.UserId : ;
}
}
/// <summary>
/// ActionID,接口编号
/// </summary>
protected int actionId; /// <summary>
/// 本次登录SessionID句柄
/// </summary>
protected string Sid;
/// <summary>
/// 是否是错误的URL请求串
/// </summary>
private bool IsError = false; /// <summary>
/// 是否是主动推送
/// </summary>
protected bool IsPush = false; /// <summary>
/// 是否影响输出, True:不响应
/// </summary>
protected bool IsNotRespond; /// <summary>
/// 请求上来的消息编号,主动下发编号为0
/// </summary>
protected int MsgId = ; /// <summary>
/// 时间缀
/// </summary>
protected string St = "st"; /// <summary>
/// 返回Action是否为ErrorAction
/// </summary>
/// <returns></returns>
public bool GetError()
{
return IsError;
}
private string errorInfo = string.Empty;
/// <summary>
/// 获取或设置错误信息
/// </summary>
public String ErrorInfo
{
get
{
return errorInfo;
}
set
{
errorInfo = value;
}
} private int errorCode = ;
/// <summary>
/// 获取或设置错误信息
/// </summary>
public int ErrorCode
     ... ...
  }

  如果是 ActionGetter 是底层向业务层传递session与request的通道,GameStruct 则包含逻辑层向业务层反馈的参数。

  等具体跑起来再做细致分析罢。

上一篇:Android Studio在创建/导入项目的时候,一直处于building “XXX”gradle project info的解决办法


下一篇:InfluxDB基础操作