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项目时选择“使用个人用户账户”就可以创建一个带有用户和权限管理的项目,已经准备好了用户注册.登录等很多页面,也可 ...
随机推荐
- selenium grid 搭建
hub端 Step1: 下载 selenium-server-standalone-x.xx.x.jar我下载的是:selenium-server-standalone-2.44.0.jar下载地址: ...
- JDBC 之 事务
1.概念:事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功. 2.数据库开启事务的命令dtl: Start transaction开启事务 Rollback回滚事务(撤销) ...
- sql server常用查询
最近在做一些练习,觉得数据的查询是一个很有意思的,在做的过程中一些好的查询方法也使自己感觉到数据库的强大,于是乎就会想到要把这些方法记下来,以后就懒得再去想了 1.查询是整百的倍数 SELECT 实缴 ...
- Android(java)学习笔记233: 远程服务的应用场景(移动支付案例)
一. 移动支付: 用户需要在移动终端提交账号.密码以及金额等数据 到 远端服务器.然后远端服务器匹配这些信息,进行逻辑判断,进而完成交易,返回交易成功或失败的信息给移动终端.用户提交账号. ...
- Upgrade to Python 2.7.9 on CentOS5.5
1. Install python2.7 #cd /tmp #wget https://www.python.org/ftp/python/2.7.9/Python-2.7.9.tgz --no-ch ...
- codevs1044四子连棋(Dfs)
/* 数据范围太小 暴力暴力 Dfs直接 终止条件嘛 就是4中目标棋局 挨着枚举一遍就好了 搜索的起点一定是空格 当然 空格周围有黑有白 黑先走或者白先走答案可能不一样 所以 维护一个b 表示这一步走 ...
- 2015 Multi-University Training Contest 1 题解 BY FZUw
题目链接:5288-5299 HDU5288 题解原文链接:我是链接
- Android Studio中关于Project与Module
在Android Studio中一个Project和Eclipse中的WorkSpace是相似的,而一个Module与Eclipse中的Project是相似的(大致可以这么的认为) 若在Android ...
- 关于asp.net中cookie在调试过程中读写正常发布后乱码问题
最近在做的项目发布后出现了乱码的问题,既然出现了乱码很大的可能性是跟编码有关系,所以首先的解决方案就是重新对cookie进行编码, 在写入的cookie的时候编码,在读取的时候解码 在写入cookie ...
- 关于mtk Android打开串口权限问题
最近在做一个测试串口读写回路的APK,jni代码部分遇到一个小小问题: fd = open(path_utf, O_RDWR);返回值是-1,要么就是权限问题,要么就是文件不存在所以需要打印错误信息, ...