引言:

之前写过一个 demo 案例大致讲解了 Socket 通信的过程,并和自建的服务器完成连接和简单的数据通信,详细的内容可以查看 Unity3D —— Socket通信(C#)。但是在实际项目应用的过程中,这个 demo 的实现方式显得异常简陋,而且对应多个业务同时发起 Socket 通信请求的处理能力也是有限,总不能每个请求都创建一个线程去监听返回结果,所以有必要进一步优化一番,例如加入线程池管理已经用一个队列来管理同时发起的请求,让 Socket 请求和接收异步执行,基本的思路就是引入 多线程异步 这两个概念。

设计思路:

正如上面提及的,我们优化的思路主要两点:多线程异步 Socket

1.多线程:

考虑到并发处理能力,假如是服务端,我们需要对每个连接上来的 Socket 客户端创建管理线程,通常会用线程池来管理。而在客户端,我们可以选择创建三个核心的线程,根据功能分为:

  • 发送数据线程 SenderThread:负责将数据发送给服务端;

  • 接收数据线程 ReceiverThread:负责接收服务端的数据;

  • 其他业务线程 MainThread:负责收发数据之外的操作,例如:压缩数据、加密解密和数据打包解包等。

由于在 .NET 中已经提供了 System.Threading.Thread 这个线程的基础类,所以我们可以通过使用这个类来创建我们自己的线程管理类,添加一些附加属性。

大致步骤:

  • 先创建一个 Socket 对象,然后通过对应的 API 去连接服务器 IP 端口,可以有两种方式:阻塞式Connect 接口; 非阻塞式BeginConnect 接口;
  • 连接完成后创建 发送线程接收线程 ,并将套接字 Socket 对象传入它们的操作方法中;
  • 发送线程的发送方法是被调用才触发的,接收线程则需要一个轮休等待网络数据的方法。

2.异步 Socket:

在这一点上,主要做法就是使用队列的概念,即不管要发送消息还是收到消息,都先把消息放到队列(发送队列、接收队列)中,然后依次从队列中中取出消息来执行。

队列:在 .NET 中,提供了一个 Queue 的结构实现队列的功能,队列里面可以存放任何可以转化为 object 的对象或数据,所以自然也能存放我们的消息。

  • 发送队列:

    当我们要发送一条网络协议的时候,不是直接将消息丢给丢给 Socket 通道进行传输,而是先通过 Queue.Enqueue 接口压入队列,然后在一个循环执行的逻辑(轮询)中不断地从队列中通过 Queue.Dequeue 接口取出队列中的消息然后再传递给 Socket 进行真实的网络传输。
  • 接收队列:

    接收网络数据也是相似的过程,从缓冲区取到字节流数据,解析成功后,先存在接收队列中,然后再循环遍历操作中从队列里取出消息进行处理。

当然,为了解决队列在多线程中的并发问题,还需要了解一下 lock 方法,这是解决问题的方法之一,但不是唯一办法,这里我的原则是在不严重影响效率的情况下怎么简单怎么来,使用大致如下:

//线程安全的访问队列的方式
lock (mQueue)
{
    ...
}

网上还有另一种方案 Unity 异步网络方案 IOCP Socket + ThreadSafe Queue

3.内存流缓存池:

在做消息数据的读写和格式转换操作时,都会用到 MemoryStream ,这是一个流结构,其后备存储是内存,也就是每次创建一个这样的对象都会占用一定的内存空间,假如每一次操作都创建一个 MemoryStream 对象,用完又不做主动回收,显然效率较低。

  • 内存流池化思想:我们可以在池中使用一个 List<MemoryStream> 列表来保存已创建的流对象,再使用 Dictionary<MemoryStream,bool> 字典来存储每一个对象是否可用的状态,每次需要创建流对象的步骤:

    • 通过判断列表中 MemoryStream 个数是否大于0,假如大于0则取出第一个,并删除列表中第一个 item (防止重复引用),在字典中记录此对象为不可用状态;
    • 假如列表中已经没有可用的 MemoryStream,则创建新的 MemoryStream 对象并在字典中记录状态,省去了删除列表 item 的步骤;
    • 使用完毕后,将 MemoryStream 对象的属性重置,并放回管理列表,字典中该对象的状态置为可用状态。
  • 多个内存池:为了避免多线程的并发问题,所以一般一个内存池只允许一个线程来访问,根据上述我们创建三个核心线程的思想,所以这里我们也要对应地创建三个池,分别为:发送线程使用 MemoryStream 池接收线程使用 MemoryStream 池业务线程线程使用 MemoryStream 池

    所以最终每个池需要提供至少两个接口:

    • New 用于从池中获取一个可用的 MemoryStream 对象;
    • Delete 用于将一个使用完后的 MemoryStream 对象回收到池中。

4.收发包线程管理类:

这个类主要完成连接的建立和断开的管理,以及在连接成功后创建读写包的线程,并将网络读写操作的对象传递给收发包的线程以便后续网络数据包的接收和发送操作:

  • 建立连接:

    在 .Net 中提供了两种创建 Socket 连接的方式(用于客户端):

    • 使用 System.Net.Sockets.TcpClient 创建一个 TcpClient 对象,然后使用 TcpClient.BeginConnect 接口去连接指定 IP 端口,使用这种方式创建连接后;
    • 使用 System.Net.Sockets.Socket 创建一个 Socket 对象,然后通过 Socket.Connect 去连接指定 IP 端口。
  • 数据收发:

    • TcpClient 通过 TcpClient.GetStream() 接口得到一个 NetworkStream 对象,然后分别使用 NetworkStream.WriteNetworkStream.Read 完成网络数据的发送和接收;
    • Socket 连接完成后,直接使用最开始创建的 Socket 套接字对象调用 Socket.SendScoket.Receive 来完成网络数据的发送和接收。
  • 断开连接:

    • TcpClient 需要执行两步操作:一是关闭 NetworkStream,二是关闭 TcpClient ,都是调用对应的关闭方法: TcpClient.GetStream().Close() / TcpClient.Close()
    • Socket 直接关闭即可 Socket.Close()

不了解 TcpClient 的可以查看一下 《C# Tcp协议收发数据(TCPClient发,Socket收)

不了解 Socket 的可以查看一下 《.net网络编程之一:Socket编程

5.调用异步的 API:

出于灵活性的考虑,我还是选择使用 Socket 来收发数据而不使用封装好的 TcpClient 中获取的 NetworkStream 对象来实现,主要考虑两点:

  • 其一,我打算使用2个字节的 ushort 类型数据来表示数据包体的大小,将每次的数据包控制在 65535 byte 之内;
  • 其二,传输数据类型没有限制。

6.NetworkStream.Write 和 Socket.Send 对比:

这两个都是 TCP 通信中发送数据的接口,但是,NetworkStream 在使用 Write(byte[] buffer, int offset, int size) 传输数据时,会在数据的头部加上一个 int 类型的数据,即传入的 size 参数,用于表示 buffer 的大小,但假如定制网络协议的时候规定只允许在头部使用2个字节表示数据的长度,即一个 short 类型,那此时 NetworkStream 就无法满足需求了,只能使用 Socket 来完成。

参考 官方介绍,C# 中的 ushort 取值范围是 0 ~ 65535,即2个字节表示的包头,一次最大传输数据大小是 65535 byte 大概看为是 64kb。

7.异步收发接口:

  • 发送:

    使用 Socket.BeginSendSocket.EnSend 这对 API 来实现异步的数据接收操作;
  • 接收:

    使用 Socket.BeginReceive``Socket.EnReceive 这对 API 来实现异步的数据接收操作。

案例:

网上也有使用类似的思路对 C# 的 Socket 进行封装的,这里简单列举几个:

参考资料:

《Unity 3D游戏客户端基础框架》多线程异步 Socket 框架构建的更多相关文章

  1. 《Unity 3D游戏客户端基础框架》概述

    框架概述: 做了那么久的业务开发,也做了一年多的核心战斗开发,最近想着自己倒腾一套游戏框架,当然暂不涉及核心玩法类型和战斗框架,核心战斗的设计要根据具体的游戏类型而定制,这里只是一些通用的基础系统的框 ...

  2. 《Unity 3D游戏客户端基础框架》系统设计

    引言 最近到看一个 <贪吃蛇大战开发实例>,其中 贪吃蛇大作战游戏开发实战(3):系统构架设计 提供的系统架构的设计思路我觉得还是值得学习一下的,接下来的内容是我看完视频后的一点笔记. 架 ...

  3. 《Unity 3D游戏客户端基础框架》消息系统

    功能分析: 首先,我们必须先明确一个消息系统的核心功能: 一个通用的事件监听器 管理各个业务监听的事件类型(注册和解绑事件监听器) 全局广播事件 广播事件所传参数数量和数据类型都是可变的(数量可以是 ...

  4. 可扩展多线程异步Socket服务器框架EMTASS 2.0 (转自:http://blog.csdn.net/hulihui)

    可扩展多线程异步Socket服务器框架EMTASS 2.0 (转自:http://blog.csdn.net/hulihui) 0 前言 >>[前言].[第1节].[第2节].[第3节]. ...

  5. C# 实现的多线程异步Socket数据包接收器框架

    转载自Csdn : http://blog.csdn.net/jubao_liang/article/details/4005438 几天前在博问中看到一个C# Socket问题,就想到笔者2004年 ...

  6. Unity 3D游戏开发学习路线(方法篇)

    Unity 3D本来是由德国的一些苹果粉丝开发的一款游戏引擎,一直只能用于Mac平台,所以一直不被业外人士所知晓.但是后来也推出了2.5版,同时发布了PC版本,并将其发布方向拓展到手持移动设备.Uni ...

  7. 可扩展多线程异步Socket服务器框架EMTASS 2.0 续

    转载自Csdn:http://blog.csdn.net/hulihui/article/details/3158613 (原创文章,转载请注明来源:http://blog.csdn.net/huli ...

  8. 可扩展多线程异步Socket服务器框架EMTASS 2.0

    0 前言 >>[前言].[第1节].[第2节].[第3节].[第4节].[第5节].[第6节] 在程序设计与实际应用中,Socket数据包接收服务器够得上一个经典问题了:需要计算机与网络编 ...

  9. Unity 3D游戏开发引擎:最火的插件推荐

    摘要:为了帮助使用Unity引擎的开发人员制作更完美的游戏.我们精心挑选了十款相关开发插件和工具.它们是:2D Toolkit.NGUI.Playmaker.EasyTouch & EasyJ ...

随机推荐

  1. [py]数据描述符优先级

    实例查找属性的顺序: 类属性 > 数据描述符 > 实例属性 > 非数据描述符 > __getattr__ 类属性>数据描述符>实例属性 class Str: def ...

  2. Ubuntu下navicat过期解决办法

    Ubuntu下使用navicat过期.试用期是15天. 可以删除.navicat64/解决.不好的一点就是.需要重新连接数据库,以前的连接记录会被删除 rm -rf ~/.navicat64/

  3. 分布式ID方案有哪些以及各自的优势

    1.    背景 在分布式系统中,经常需要对大量的数据.消息.http请求等进行唯一标识.例如:在分布式系统之间http请求需要唯一标识,调用链路分析的时候需要使用这个唯一标识.这个时候数据自增主键已 ...

  4. PHP获取代码段执行的毫秒时间和消耗内存

    我们在项目开发经常需要做一些优化型测试,比如优化代码段,排查代码段效率问题,或者降低内存消耗成本. <?php $start_memory = memory_get_usage(); //开始内 ...

  5. php时间戳函数mktime()

    在项目开发中,偶尔会遇到跨周期.跨月的的时间操作.PHP为我们提供了一个很方便的函数->mktime,可以很简单的获取制定日期的时间戳了. mktime(hour,minute,second,m ...

  6. foo、bar美国版的张三李四

    不管看javascript还是其他语言举例,经常看到使用foo和bar来充当变量.那么究竟foo.bar是什么鬼? 一说:foo 和 bar 组合在一起所构成的 foobar 应该最能反映其原始的意思 ...

  7. oracle数据库中导入Excel表格中的数据

    1.点击[工具]-->[ODBC 导入器],如图: 2.在导入器里选择第一个[来自ODBC的数据],用户名/系统DSN-->填写[Excel Files],输入用户名和密码,点击 [连接] ...

  8. Java第一周学习总结5311

    20145311 <Java程序设计>第1周学习总结 教材学习内容总结 第一章1.1java的历史:总的来说,Java经历了许许多多版本的变迁,目前已经成为一种经常使用的计算机编程语言.J ...

  9. 在pom.xml中使用distributionManagement将项目打包上传到nexus私服

    本文介绍 如何在pom.xml中使用distributionManagement将项目打包上传到nexus私服 1.pom.xml文件添加distributionManagement节点 <!- ...

  10. ubuntu16.04+七彩虹GTX1060的NVIDIA驱动+Cuda8.0+cudnn5.1+tensorflow+keras搭建深度学习环境【学习笔记】【原创】

    平台信息:PC:ubuntu16.04.i5.七彩虹GTX1060显卡 作者:庄泽彬(欢迎转载,请注明作者) 说明:参考了网上的一堆的资料搭建了深度学习的开发环境,下班在宿舍折腾了好几个晚上才搞定,写 ...