Scut:GameWebSocketHost 解析

  想使用 Scut 作的是一個短鏈接項目,因此先直接看 GameWebSocketHost 了。服務器

  先來看下 GameWebSocketHost 的成員:session

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

  由以前的分析可知:SocketListener 搞定了監聽、底層IO,那麼ActionDispatcher 應該負責上層消息的分發了。多線程

  構造函數作了各類參數準備、註冊回調,不贅述。併發

 

  最爲關鍵的是幾個回調函數的具體實現:app

 

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:會話,這是每一個鏈接在應用層的表示:socket

     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 的代碼:async

    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. 接收並處理消息ui

  在 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[0];
                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 > 0)
                    {
                        await session.SendAsync(actionGetter.OpCode, data, 0, 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 = 0,
            /// <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 : 0;
            }
        }
        /// <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 = 0;

        /// <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 = 0;
        /// <summary>
        /// 獲取或設置錯誤信息
        /// </summary>
        public int ErrorCode
     ... ...
  }

  若是是 ActionGetter 是底層向業務層傳遞session與request的通道,GameStruct 則包含邏輯層向業務層反饋的參數。

  等具體跑起來再作細緻分析罷。

相關文章
相關標籤/搜索