Scut:GameWebSocketHost 解析
想使用 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 解析的更多相关文章
- Scut:SocketListener 的解析
大致浏览了一遍,Scut 的网络模型采用的是 SAEA 模型, 它是 .NET Framework 3.5 开始支持的一种支持高性能 Socket 通信的实现. 通过分析 Scut 的套接字监听控制, ...
- Scut:账号服务器问题修正
姑且记录一下,以防未来出现bug回来看看今天改了哪些. 原 Scut 账服是应用于 渠道频道 的账号服务器,每天会发放大量的游客账号,它有一个"自动将已经被注册了一段时间的游客账号再重新推送 ...
- Scut:从PackageReader分析客户端协议规则
看第一个解析API: private void ParseData(byte[] data) { var paramBytes = SplitBuffer(data); RawParam = _enc ...
- Scut游戏server引擎Unity3d访问
Scut提供Unity3d Sdk包.便利的高速发展和Scut游戏server对接: 看Unity3d示为以下的比率: 启动Unity3d项目 打开Scutc.svn\SDK\Unity3d\Asse ...
- Scut游戏服务器引擎之Unity3d接入
Scut提供Unity3d Sdk包,方便开发人员快速与Scut游戏服务器对接: 先看Unity3d示例如下: 启动Unity3d项目 打开Scutc.svn\SDK\Unity3d\Assets目录 ...
- 如何部署和运行Scut服务器及游戏:Windows篇
概述 Scut游戏引擎是一个永久免费的全脚本游戏服务器框架,采用MVC框架设计,简化数据库设计和编码工作:降低对开发人员的开发难度:同时提供了丰富的类库和API接口. 一. 安装环境 必须安装的 ...
- JSON介绍及Android最全面解析方法(Gson、AS自带org.son、Jackson解析)
前言 今天,我们来介绍一下现今主流的数据交换格式-JSON! 相同作为主流为数据交换格式-XML,假设有兴趣能够阅读我写的XML及其DOM.SAX.PULL解析方法和对照 文件夹 定义 JavaScr ...
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- .NET Core中的认证管理解析
.NET Core中的认证管理解析 0x00 问题来源 在新建.NET Core的Web项目时选择“使用个人用户账户”就可以创建一个带有用户和权限管理的项目,已经准备好了用户注册.登录等很多页面,也可 ...
随机推荐
- ASP.NET MVC3 ModelState.IsValid为false的问题
模型验证通常在submit后调用Action之前进行验证,eg: public class ZhengXing { [Key] public int ZhengXin ...
- Android网络:开发浏览器(一)——基本的浏览网页功能开发
我们定义这个版本为1.0版本. 首先,因为要制作一个浏览器,那么就不能通过调用内置浏览器来实现网页的浏览功能,但是可以使用WebView组件来进行. 在此之前,我们可以来看看两种网页显示方式: ...
- [转载]Web前端和后端之区分,以及面临的挑战
原文地址:Web前端和后端之区分,以及面临的挑战[转]作者:joyostyle 在我们实际的开发过程中,我们当前这样定位前端.后端开发人员. 1)前端开发人员:精通JS,能熟练应用JQuery,懂CS ...
- log4jdbc与logback集合打印日志过多的解决
在项目中使用了log4jdbc,可以很方便的把sql的参数也打印出来,便于问题调试.比如原始sql: select * from t_order where order_id = ? : 经过log4 ...
- jiaocheng https://github.com/CarpenterLee/JCFInternals
https://github.com/CarpenterLee/JCFInternals
- DirectX 11游戏编程学习笔记之8: 第6章Drawing in Direct3D(在Direct3D中绘制)(习题解答)
本文由哈利_蜘蛛侠原创,转载请注明出处.有问题欢迎联系2024958085@qq.com 注:我给的电子版是700多页,而实体书是800多页,所以我在提到相关概念的时候 ...
- android ContentProvider学习
1.ContentProvider提供位存储或获取数据提供了统一的接口. 2.使用ContentProvider可以在不同的应用程序之间共享数据. 3.Android为常见的一些数据提供了Conten ...
- 利用NIO建立Socket服务器
传统的Java 的IO,利用Socket建立服务器,接收客户端连接,一般都是为每一个连接建立一个线程,如果连接数巨大,那么服务器开销也将巨大..NIO的原理,可以参照图:http://new.51ct ...
- 关于C++构造函数的FAQ
[1] 构造函数是用来干什么的? 构造函数构建类的对象,初始化类变量,分配资源(内存.文件.信号量.套接口等等) [2] List x; 和 List x();有什么不同? 前一个是定义List的一个 ...
- 重装系统时,将MBR分区转为GPT 分区
摘要 很多同学在重装系统的时候,或多或少都遇到过这样的问题:镜像文件没有问题,软碟通刻录也没有问题,但偏偏就在选择安装系统盘盘符的时候,跳出对话框,提示:Windows无法安装到这个磁盘,选中的磁盘具 ...