想使用 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 则包含逻辑层向业务层反馈的参数。

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

Scut:GameWebSocketHost 解析的更多相关文章

  1. Scut:SocketListener 的解析

    大致浏览了一遍,Scut 的网络模型采用的是 SAEA 模型, 它是 .NET Framework 3.5 开始支持的一种支持高性能 Socket 通信的实现. 通过分析 Scut 的套接字监听控制, ...

  2. Scut:账号服务器问题修正

    姑且记录一下,以防未来出现bug回来看看今天改了哪些. 原 Scut 账服是应用于 渠道频道 的账号服务器,每天会发放大量的游客账号,它有一个"自动将已经被注册了一段时间的游客账号再重新推送 ...

  3. Scut:从PackageReader分析客户端协议规则

    看第一个解析API: private void ParseData(byte[] data) { var paramBytes = SplitBuffer(data); RawParam = _enc ...

  4. Scut游戏server引擎Unity3d访问

    Scut提供Unity3d Sdk包.便利的高速发展和Scut游戏server对接: 看Unity3d示为以下的比率: 启动Unity3d项目 打开Scutc.svn\SDK\Unity3d\Asse ...

  5. Scut游戏服务器引擎之Unity3d接入

    Scut提供Unity3d Sdk包,方便开发人员快速与Scut游戏服务器对接: 先看Unity3d示例如下: 启动Unity3d项目 打开Scutc.svn\SDK\Unity3d\Assets目录 ...

  6. 如何部署和运行Scut服务器及游戏:Windows篇

    概述 Scut游戏引擎是一个永久免费的全脚本游戏服务器框架,采用MVC框架设计,简化数据库设计和编码工作:降低对开发人员的开发难度:同时提供了丰富的类库和API接口. 一.    安装环境 必须安装的 ...

  7. JSON介绍及Android最全面解析方法(Gson、AS自带org.son、Jackson解析)

    前言 今天,我们来介绍一下现今主流的数据交换格式-JSON! 相同作为主流为数据交换格式-XML,假设有兴趣能够阅读我写的XML及其DOM.SAX.PULL解析方法和对照 文件夹 定义 JavaScr ...

  8. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  9. .NET Core中的认证管理解析

    .NET Core中的认证管理解析 0x00 问题来源 在新建.NET Core的Web项目时选择“使用个人用户账户”就可以创建一个带有用户和权限管理的项目,已经准备好了用户注册.登录等很多页面,也可 ...

随机推荐

  1. 自动化Cobbler安装

    #install cobbler-server soft #date 2013.08.07 #disabled iptables and selinux /etc/init.d/iptables st ...

  2. javascript 函数 方法

    函数 1.函数的定义 (1)function 函数名(x){ 函数执行体; } (2)var 函数名=function(x){ 函数执行体; }; 这种方法说明,在javascript中,函数就是一种 ...

  3. 在asp.net中使用confirm可以分为两种:

    在asp.net中使用confirm可以分为两种: 1.没有使用ajax,confirm会引起也面刷新 2.使用了ajax,不会刷新 A.没有使用ajax,可以用StringBuilder来完成. ( ...

  4. MyBatis架构图

    MyBatis架构 MyBatis依赖的jar不多,而且代码行数也没多少,其中使用了大量的设计模式,值得好好学习.下图是MyBatis的一张架构图,来自Java框架篇—Mybatis 入门. Myba ...

  5. Dijkstra算法为什么权值不能为负

    Dijkstra算法当中将节点分为已求得最短路径的集合(记为S)和未确定最短路径的个集合(记为U),归入S集合的节点的最短路径及其长度不再变更,如果边上的权值允许为负值,那么有可能出现当与S内某点(记 ...

  6. Mac内建Apache

    打开终端 重启apache:sudo /usr/sbin/apachectl restart 关闭apache:sudo /usr/sbin/apachectl stop 开启apache:sudo ...

  7. winform timespan 两个时间的间隔(差) 分类: WinForm 2014-04-15 10:14 419人阅读 评论(0) 收藏

    TimeSpan 结构  表示一个时间间隔. 先举一个小例子:(计算两个日期相差的天数) 代码如下: DateTime dt = DateTime.Now.ToShortDateString(yyyy ...

  8. iframe页面改动parent页面的隐藏input部件value值,不能触发change事件。

    实现一个依据iframe页面返回充值卡类型不同,安排不同的input部件. 点击选择弹出一个iframe.点击充值卡数据行.返回1.充值卡类型.2.充值卡id(用的UUID).3.充值卡号(字符串). ...

  9. Node.js【2】开发环境搭建(Windows、Linux&amp;Mac)

    安装方式有非常多种,以下仅仅是我喜欢的一种方式,安装的路径不一定要跟我一样. 眼下最新的版本号是v0.10.28 Windows 1.下载(放到E:\node\nodejs): http://node ...

  10. Monkeyrunner入门示例

    准备工作1.安装Android SDK2.熟悉MonkeyRunner的API(http://article.yeeyan.org/view/37503/164523)3.一部Android手机或模拟 ...