想使用 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. SRM 405(1-250pt, 1-500pt)

    DIV1 250pt 题意:以linux系统中文件系统的路径表示方法为背景,告诉你某文件的绝对路径和当前位置,求相对路径.具体看样例. 解法:模拟题,不多说.每次碰到STL的题自己的代码都会显得很sb ...

  2. AOJ 0525 穷举

    题意:有一个烤饼器可以烤r行c列的煎饼,煎饼可以正面朝上(用1表示)也可以背面朝上(用0表示).一次可将同一行或同一列的煎饼全部翻转.现在需要把尽可能多的煎饼翻成正面朝上,问最多能使多少煎饼正面朝上? ...

  3. Hibernate五 HQL查询

    HQL查询一 介绍1.HQL:Hibernate Query Language,是一种完全面向对象的查询语言.使用Hibernate有多重查询方式可供选择:hibernate的HQL查询,也可以使用条 ...

  4. tcp dump 截取http

    监听命令 sudo tcpdump -w mm.txt -s 0 -A -v  tcp dst port 8080 -w mm.txt :把记录下来的数据已二进制格式存储在mm.txt文件内  -w ...

  5. linux 发邮件

      一. centos yum 安装 1. yum install mailx vim  /etc/nail.rc 添加网易163邮箱开放的需要认证的smtp服务器: set from=USER@16 ...

  6. 如何理解oracle 11g scan ip

    如何理解oracle 11g scan ip   在11.2之前,client链接数据库的时候要用vip,假如你的cluster有4个节点,那么客户端的tnsnames.ora中就对应有四个主机vip ...

  7. 关于 Head First SQL 中文版

    我想谈谈 我对于Head  First  SQL  中文版的一些看法 事实上关于我翻译的这个Head  First  SQL 中文版..我自觉得:的确翻译得非常烂.. 和翻译Head  First  ...

  8. 让你的WizFi250适应各种气候

    这篇文章会具体描写叙述如何马上得到指定城市的天气状况(比方首尔).由OpenWeatherMap提供. 用JSON(由OpenWeatherMap提供),XML和一个以太网模块.使WIZnet-Wiz ...

  9. C#泛型类的简单创建与使用

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Cons ...

  10. 模仿ios下的coverflow

    Android高级图片滚动控件,编写3D版的图片轮播器 http://blog.csdn.net/guolin_blog/article/details/17482089 A cool Open So ...