Scut:SocketListener 的解析
大致浏览了一遍,Scut 的网络模型采用的是 SAEA 模型, 它是 .NET Framework 3.5 开始支持的一种支持高性能 Socket 通信的实现。
通过分析 Scut 的套接字监听控制,就能大致明白它是如何使用 SAEA 架构的。
1. 套接字缓冲区内存管理器
先来看下 Scut 对套接字缓冲区的内存管理:
class BufferManager
{
int capacity;
byte[] bufferBlock;
Stack<int> freeIndexPool;
int currentIndex;
int saeaSize; /*
* capacity 表示为所有套接字准备的内存容量
* saeaSzie 表示单个套接字所需的内存量
*/
public BufferManager(int capacity, int saeaSize)
{
this.capacity = capacity;
this.saeaSize = saeaSize;
this.freeIndexPool = new Stack<int>();
} //申请整份的内存空间
internal void InitBuffer()
{
this.bufferBlock = new byte[capacity];
} //为每个 SAEA 向缓存管理器申请缓存
internal bool SetBuffer(SocketAsyncEventArgs args)
{
if (this.freeIndexPool.Count > ) //用一个堆栈记录非顺序释放的内存块,优先使用这些内存块作为缓存
{
args.SetBuffer(this.bufferBlock, this.freeIndexPool.Pop(), this.saeaSize);
}
else
{
if ((capacity - this.saeaSize) < this.currentIndex)
{
return false;
}
args.SetBuffer(this.bufferBlock, this.currentIndex, this.saeaSize);
this.currentIndex += this.saeaSize;
}
return true;
} //为SAEA将缓存还给缓存管理器
internal void FreeBuffer(SocketAsyncEventArgs args)
{
this.freeIndexPool.Push(args.Offset);
args.SetBuffer(null, , );
}
}
使用一个堆栈来管理”碎片大小相同、随时取用与释放”的内存块,这段代码算是十分高效与简介了。
2. SocketListener 的初始化
private void Init()
{
this.bufferManager.InitBuffer(); for (int i = ; i < this.socketSettings.MaxAcceptOps; i++) //创建一个接受连接的SAEA池子
{
this.acceptEventArgsPool.Push(CreateAcceptEventArgs());
private SocketAsyncEventArgs CreateAcceptEventArgs()
{
SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(Accept_Completed); //这部分SAEA绑定的都是“完成连接”事件处理API
return acceptEventArg;
}
}
SocketAsyncEventArgs ioEventArgs;
for (int i = ; i < this.socketSettings.NumOfSaeaForRecSend; i++) //创建一个处理IO的SAEA池子
{
ioEventArgs = new SocketAsyncEventArgs();
this.bufferManager.SetBuffer(ioEventArgs);
ioEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed); //这部分SAEA绑定的都是“IO”事件处理API
DataToken dataToken = new DataToken();
dataToken.bufferOffset = ioEventArgs.Offset; //每个SAEA在缓存管理器中获取的内存块的起始偏移都是唯一的,可以用来做唯一标识
ioEventArgs.UserToken = dataToken;
this.ioEventArgsPool.Push(ioEventArgs);
}
_summaryTimer = new Timer(OnSummaryTrace, null, , );
public class SummaryStatus //日志定时记录连接的状态
{
/// <summary>
///
/// </summary>
public long TotalConnectCount;
/// <summary>
///
/// </summary>
public int CurrentConnectCount;
/// <summary>
///
/// </summary>
public int RejectedConnectCount;
/// <summary>
///
/// </summary>
public int CloseConnectCount;
}
}
3. 监听-连接-数据传输流程
那么,这么多SAEA是如何工作的呢?
public void StartListen()
{
listenSocket = new Socket(this.socketSettings.LocalEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); //建立TCP监听套接字
listenSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); //进一步设置监听套接字参数
listenSocket.Bind(this.socketSettings.LocalEndPoint); //绑定端口
listenSocket.Listen(socketSettings.Backlog); //开始监听,并设置最大排队连接数
_isStart = true;
requestHandler.Bind(this);
PostAccept();
}
看一下 SocketOptionLevel 的作用:
SocketOptionLevel.IP:仅适用于 IP 套接字;
SocketOptionLevel.IPv6:仅适用于 IPv6 套接字;
SocketOptionLevel.Socket:适用于所有套接字;
SocketOptionLevel.Tcp、SocketOptionLevel.Udp:适用于TCP、UDP套接字;
SocketOptionName.ReuseAddress:允许将套接字绑定到已在使用中的地址。
private void PostAccept()
{
try
{
if (!_isStart)
{
return;
}
SocketAsyncEventArgs acceptEventArgs = acceptEventArgsPool.Pop() ?? CreateAcceptEventArgs(); //从accept SAEA池中取出一个SAEA交给监听套接字去获取连接参数
bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArgs);
if (!willRaiseEvent) //直接同步取得连接则顺序执行,异步取得则触发 Accept_Completed 事件处理函数
{
ProcessAccept(acceptEventArgs); //处理连接
}
}
catch (Exception ex)
{
TraceLog.WriteError("Post accept listen error:{0}", ex);
}
}
我们可以看到在 Accept_Completed 中也是同样调用了 ProcessAccept;
private void Accept_Completed(object sender, SocketAsyncEventArgs acceptEventArgs)
{
try
{
ProcessAccept(acceptEventArgs);
}
catch (Exception ex)
{
... ...
}
}
继续看 ProcessAccept 是如何工作的:
private void ProcessAccept(SocketAsyncEventArgs acceptEventArgs)
{
try
{
Interlocked.Increment(ref _summaryStatus.TotalConnectCount); //向监控器提交“总连接数+1”
maxConnectionsEnforcer.WaitOne(); //堵塞一个信号量,由此可知,信号量的总数控制了可并发处理的accept连接数 if (acceptEventArgs.SocketError != SocketError.Success)
{
Interlocked.Increment(ref _summaryStatus.RejectedConnectCount); //向监控器提交“被拒绝连接数+1”
HandleBadAccept(acceptEventArgs);
}
else
{
Interlocked.Increment(ref _summaryStatus.CurrentConnectCount); //向监控器提交“当前连接数+1” SocketAsyncEventArgs ioEventArgs = this.ioEventArgsPool.Pop(); //获取IO SAEA 池中的一个SAEA
ioEventArgs.AcceptSocket = acceptEventArgs.AcceptSocket; //将 accept 建立的 io 套接字交给该 SAEA
var dataToken = (DataToken)ioEventArgs.UserToken;
ioEventArgs.SetBuffer(dataToken.bufferOffset, socketSettings.BufferSize); //为 io SAEA 提供缓存
var exSocket = new ExSocket(ioEventArgs.AcceptSocket); // 将 io 套接字用 ExSocket 管理起来
exSocket.LastAccessTime = DateTime.Now;
dataToken.Socket = exSocket;
acceptEventArgs.AcceptSocket = null; //release connect when socket has be closed.
ReleaseAccept(acceptEventArgs, false); //该 accept SAEA 已经完成任务,释放其资源
try
{
OnConnected(new ConnectionEventArgs { Socket = exSocket }); //OnConnected 是 SocketListener 的“连接事件订阅器”,成功连接时触发该订阅
}
catch (Exception ex)
{
TraceLog.WriteError("OnConnected error:{0}", ex);
}
PostReceive(ioEventArgs);
} }
finally
{
PostAccept(); //处理完毕后又重新开始监听
}
}
可以看到这个api 做的最重要的事情:1. 将建立连接的socket交给ioSAEA;2. ioSAEA去底层获取消息;3. 继续监听;
疑问:如果只有1个监听套接字,为什么要做一个 acceptpool?
再来看下 ioSAEA 的工作流程:
private void PostReceive(SocketAsyncEventArgs ioEventArgs)
{
if (ioEventArgs.AcceptSocket == null) return; bool willRaiseEvent = ioEventArgs.AcceptSocket.ReceiveAsync(ioEventArgs); //异步接收io数据 if (!willRaiseEvent) //如果同步获得直接处理,异步获得则由异步回调处理
{
ProcessReceive(ioEventArgs);
}
}
无论哪种处理方式,都是调用 ProcessReceive,其中比较重要的部分:
bool needPostAnother = requestHandler.TryReceiveMessage(ioEventArgs, out messages, out hasHandshaked);
在 SocketListener 启动的时候我们注意到:
requestHandler.Bind(this);
监听套接字管理器自带 requestHandle,这是个什么东西?
public class RequestHandler
{
public RequestHandler(BaseMessageProcessor messageProcessor)
{
MessageProcessor = messageProcessor;
} internal virtual void Bind(ISocket appServer)
{
AppServer = appServer;
} public ISocket AppServer { get; private set; }
... ...
}
protected GameSocketHost()
: this(new RequestHandler(new MessageHandler()))
{
} protected GameWebSocketHost(bool isSecurity = false)
: this(new WebSocketRequestHandler(isSecurity))
{
}
从 Scut 的以上代码应该可以得知:什么类型的套接字宿主应该绑定相应类型的消息处理API。
进一步观察,websocket 与 socket 的“消息发送API”是一致的,而“消息读取”API则完全不同,这里涉及到更加具体的协议规则,有空再回来研究这块内容。
继续回到 ProcessReceive:
switch (message.OpCode)
{
case OpCode.Close:
var statusCode = requestHandler.MessageProcessor != null
? requestHandler.MessageProcessor.GetCloseStatus(message.Data)
: OpCode.Empty;
if (statusCode != OpCode.Empty)
{
DoClosedStatus(exSocket, statusCode);
}
Closing(ioEventArgs, OpCode.Empty);
needPostAnother = false;
break;
case OpCode.Ping:
DoPing(new ConnectionEventArgs { Socket = exSocket, Meaage = message });
break;
case OpCode.Pong:
DoPong(new ConnectionEventArgs { Socket = exSocket, Meaage = message });
break;
default:
OnDataReceived(new ConnectionEventArgs { Socket = exSocket, Meaage = message });
break;
}
如果是常规数据,则调用 OnDataReceived,这是更上一层注册的逻辑消息处理API,正常来说,到了进入“应用消息分发器”-IActionDispatcher 的节奏了。
继续往上查,果然不出意料。
Scut:SocketListener 的解析的更多相关文章
- Scut:GameWebSocketHost 解析
想使用 Scut 做的是一个短连接项目,所以先直接看 GameWebSocketHost 了. 先来看下 GameWebSocketHost 的成员: protected bool EnableHtt ...
- 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项目时选择“使用个人用户账户”就可以创建一个带有用户和权限管理的项目,已经准备好了用户注册.登录等很多页面,也可 ...
随机推荐
- Git全解析之用起来先
文章目录 1. Git全解析之用起来先 1.1. 先安装Git环境 1.2. 配置 1.3. 简单了解Git 1.3.1. Git对象模型 SHA 1.3.2. Git目录与工作目录 1.4. 可以开 ...
- Jupyter Notebook 对LaTeX 的支持
Jupyter的Markdown模式比我原来想想的更加强大:它支持LaTeX! 支持的特性不多,应该能满足一般的需求了.
- 诡异的TNS-12541:TNS:nolistener
诡异的TNS-12541:TNS:nolistener OS:Microsoft Windows 2003 Ent ...
- OpenCV LDA(Linnear Discriminant analysis)类的使用---OpenCV LDA演示样例
1.OpenCV中LDA类的声明 //contrib.hpp class CV_EXPORTS LDA { public: // Initializes a LDA with num_componen ...
- Cocos2d-x 3.0 实例学习教程 前沿
前一段时间学过cocos2d-x 2.x ,后来去做了一些别的项目.近期又想开发自己的游戏了,但是cocos2d-x 已经升级到3.0 ,好多API都变了.所以决定再把cocos2d-x学一遍,一是 ...
- Linux服务器监控系统 ServMon V1.1---张宴
http://zyan.cc/post/291/ http://blog.zyan.cc/post/276/ http://zyan.cc/post/354/
- mysql在高内存、IO利用率上的几个优化点 (sync+fsync) 猎豹移动技术博客
http://dev.cmcm.com/archives/107 Posted on 2014年10月16日 by liuding | 7条评论 以下优化都是基于CentOS系统下的一些优化整理,有不 ...
- yii2 and short_open_tag
在看yii2的时候, 在main文件里看到了这样一段代码 <?= Yii::$app->language ?> 而我查看了php.ini里的配置, short_open_tag=Of ...
- poj 1811 Pallor Rho +Miller Rabin
/* 题目:给出一个数 如果是prime 输出prime 否则输出他的最小质因子 Miller Rabin +Poller Rho 大素数判定+大数找质因子 后面这个算法嘛 基于Birthday Pa ...
- C#HttpWebResponse请求常见的状态码
成员名称 说明 Continue 等效于 HTTP 状态 100.Continue 指示客户端可能继续其请求. SwitchingProtocols 等效于 HTTP 状态 101.Switching ...