想使用 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 則包含邏輯層向業務層反饋的參數。
等具體跑起來再作細緻分析罷。