想使用 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. Postman 安装 & 资料

    安装 下载地址: http://chromecj.com/web-development/2014-09/60/download.html 怎么在谷歌浏览器中安装.crx扩展名的离线Chrome插件? ...

  2. Dolls - 4160(简单二分图匹配)

    题意:有一些箱子,大箱子可以套小箱子,但是必须h>h,w>w,l>l,求出来最外面能剩下几个箱子无法被嵌套.   分析:思考每个箱子都只会被别的箱子套一次,所以构成一二分匹配模型,只 ...

  3. HTML embed标签使用方法和属性详解

    一.基本语法   代码如下:   embed src=url   说明:embed可以用来插入各种多媒体,格式可以是 Midi.Wav.AIFF.AU.MP3等等,Netscape及新版的IE 都支持 ...

  4. 布隆过滤器的java实现

    package com.kaikeba.data.jobspider.util; import java.util.BitSet; public class Bloomfilter { private ...

  5. CSDN Markdown简明教程5-高速上手

    0.文件夹 文件夹 前言 CSDN Markdown特点 CSDN Markdown高速上手 1 使用快捷键 粗体斜体 引用 链接 高亮代码块 图片 标题 列表 切割线 撤销反复 2 使用离线写作 3 ...

  6. Linux C 语言 获取系统时间信息

    比如获取当前年份:        /* 获取当前系统时间 暂时不使用        int iyear = 0;        int sysyear = 0;        time_t now;  ...

  7. C++继承:公有,私有,保护

    前言 无论是在平时学习中还是还做项目之时,主要用到的继承都是 public 公有继承,因此,对protected private两者继承都不大了解! 今天,在看<Effective C++ 3e ...

  8. Linux开发工具之Makefile(上)

    二.makefile(上) 01.make工具   利用make工具可以自动完成编译工作.这些工作包括:如果修改了某几 个源文件,则只重装新编译这几个源文件:如果某个头文件被修改了,则 重新编译所有包 ...

  9. 模板-->扩展欧几里得

    如果有相应的OJ题目,欢迎同学们提供相应的链接 相关链接 所有模板的快速链接 单变元模线性方程模板 poj_2115_C Looooops,my_ac_code 简单的测试 None 代码模板 /* ...

  10. Redis的AOF功能

    引言:  Redis是基于内存的数据库,同时也提供了若干持久化的方案,允许用户把内存中的数据,写入本地文件系统,以备下次重启或者当机之后继续使用.本文将描述如何基于Redis来设置AOF功能 什么是R ...