大致浏览了一遍,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 的解析的更多相关文章

  1. Scut:GameWebSocketHost 解析

    想使用 Scut 做的是一个短连接项目,所以先直接看 GameWebSocketHost 了. 先来看下 GameWebSocketHost 的成员: protected bool EnableHtt ...

  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 502(2-1000pt)

    题意:在0~(n-1)中选择k个数,使得他们的和为n的倍数的选择方案有多少种.(n <= 1000, k <= 47) 解法:裸dp.d[i][j][k’]表示在前i个数中(0~i-1), ...

  2. tessilstrona

    Untitled Document

  3. MyBatis(6):MyBatis集成Spring事务管理(下)

    前一篇文章复习了MyBatis的基本使用以及使用Spring管理MyBatis的事务的做法,本文的目的是在这个的基础上稍微做一点点的进阶:多数据的事务处理.文章内容主要包含两方面: 1.单表多数据的事 ...

  4. linux Tomcat restart脚本简单版

    linux系统下重启tomcat的shell脚本: tomcat_home=/opt/apache-tomcat-6.0.32  #找到tomcat进程的id并kill掉 ps -ef |grep t ...

  5. Python中http请求方法库汇总

    最近在使用python做接口测试,发现python中http请求方法有许多种,今天抽点时间把相关内容整理,分享给大家,具体内容如下所示: 一.python自带库----urllib2 python自带 ...

  6. Thrift初用小结

    thrift --gen  csharp  search.thrift thrift --gen java search.thrift Thrift是facebook的一个技术核心框架,07年四月开放 ...

  7. Android 省市县 三级联动(android-wheel的使用)[转]

    转载:http://blog.csdn.net/lmj623565791/article/details/23382805 今天没事跟群里面侃大山,有个哥们说道Android Wheel这个控件,以为 ...

  8. iOS图片拉伸

    常用的图片拉伸场景有:聊天页面的气泡,需要根据内容拉伸,但圆角拉伸后会变形,为避免圆角拉伸,可以指定拉伸区域.UIImage实体调用以下方法即可指定拉伸区域. - (UIImage *)stretch ...

  9. uva 1391 Astronauts(2-SAT)

    /*翻译好题意 n个变量 不超过m*2句话*/ #include<iostream> #include<cstdio> #include<cstring> #inc ...

  10. Django runserver show client ip

    get path of basehttp.py $ python >>> import site >>> site.getsitepackages() ['/usr ...