ET服务器框架学习笔记(九)

ET服务器框架学习笔记(九)


前言

本篇记录ET内部相关的通讯底层的数据处理相关。

一、ET之Channel

AChannel抽象类,主要是用来描述如何处理数据的方式,其核心功能是封装了socket。由于AChannel是抽象类,我们直接看基于TCP得TChannel类。

1.TChannel

一个TChannel代表着一个连接,可以通过他发送与接收数据
内部包含:1个Socket类 ,2个SocketAsyncEventArgs,2个CircularBuffer类,1个MemoryStream类。

  • 1个MemoryStream:用于将Socket套接字数据存放进去处理的类,存入后方便上层逻辑处理。
  • 2个CircularBuffer:主要封装了字节数组,在内部通过循环使用的方式提升性能,其中1个用于处理发送数据的buffer,另外一个用于接收套接字数据,他们主要与MemoryStream进行交互。
  • 2个SocketAsyncEventArgs:其中1个(outArgs)用于主动发起连接时的socket事件监听,另外1个(innArgs)用于监听接收接收数据的事件监听。
  • 1个Socket类:基本的套接字功能类

注意点

  • 2个SocketAsyncEventArgs的Completed都关联到TChannel的OnComplete上,与之前所写一样,由于socket是IO异步的操作,ET为了实现游戏逻辑单线程异步,OnComplete实际很可能由其他线程调用,所以根据LastOperation,封装好各类回调,然后以OneThreadSynchronizationContext.Instance.Post(this.OnConnectComplete, e);的方式传入到主线程进行调用,实现游戏逻辑上的单线程异步。
  • TChannel内部用了C#的socket,以及SocketAsyncEventArgs,需要自行去查阅相关用法了。

2.PacketParser

单独拿出这个类来记录一下,是因为这个类,是相关数据解析的类,就是它将CircularBuffer,与MemoryStream类关联起来。

  • Parse()方法,确保了将Buffer里面的数据以规定的规格读取到MemoryStream中,同时以下面的方式确保数据被正确的处理:
while (!finish)
			{
				switch (this.state)
				{
					case ParserState.PacketSize:
						if (this.buffer.Length < this.packetSizeLength)
						{
							finish = true;
						}
						else
						{
							this.buffer.Read(this.memoryStream.GetBuffer(), 0, this.packetSizeLength);
							
							switch (this.packetSizeLength)
							{
								case Packet.PacketSizeLength4:
									this.packetSize = BitConverter.ToInt32(this.memoryStream.GetBuffer(), 0);
									if (this.packetSize > ushort.MaxValue * 16 || this.packetSize < Packet.MinPacketSize)
									{
										throw new Exception($"recv packet size error, 可能是外网探测端口: {this.packetSize}");
									}
									break;
								case Packet.PacketSizeLength2:
									this.packetSize = BitConverter.ToUInt16(this.memoryStream.GetBuffer(), 0);
									if (this.packetSize > ushort.MaxValue || this.packetSize < Packet.MinPacketSize)
									{
										throw new Exception($"recv packet size error:, 可能是外网探测端口: {this.packetSize}");
									}
									break;
								default:
									throw new Exception("packet size byte count must be 2 or 4!");
							}
							this.state = ParserState.PacketBody;
						}
						break;
					case ParserState.PacketBody:
						if (this.buffer.Length < this.packetSize)
						{
							finish = true;
						}
						else
						{
							this.memoryStream.Seek(0, SeekOrigin.Begin);
							this.memoryStream.SetLength(this.packetSize);
							byte[] bytes = this.memoryStream.GetBuffer();
							this.buffer.Read(bytes, 0, this.packetSize);
							this.isOK = true;
							this.state = ParserState.PacketSize;
							finish = true;
						}
						break;
				}
			}
  1. 通过状态,判定区分是PacketSize(确认消息数据大小阶段),还是处于PacketBody(处理消息数据本身阶段),
  2. 如果是数据大小阶段,则首先通过buffer长度,与packetSizeLength比较,确定数据是否处理完毕。
  3. 未处理完毕,则从buffer取出规定长度的字节,再根据设定的包体大小字节数,将其转换成对应的ToInt32,ToUInt16值存放到packetSize中,将这个值作为后续包体大小的值,然后与设置好的值进行比较,通过了设置状态,从而进行下一步处理。
  4. PacketBody阶段,判定buffer剩余长度与解出来的packetSize进行比较,小于它,则认为已经处理完毕。
  5. 如果不小于他,则正常流程,就是将buffer内,长度为packetSize的内容,读取到memoryStream中,然后处理完毕。

上面的流程,可以保证黏包问题,有多余的数据,不会进入到memoryStream影响后续的数据处理。
下面的流程,用于取到处理好的memoryStream,然后将OK状态设置为false。这样下次循环时,调用Parse()会返回false。

public MemoryStream GetPacket()
		{
			this.isOK = false;
			return this.memoryStream;
		}

3.TChannel开始

TChannel接收数据都是从Start()函数开始,意味着外层调用Start之后,TChannel开始工作,包括接收数据与发送数据。至于在哪调用这个函数,得从上层逻辑看,这里先看TChannel的接收数据工作流程。

  1. 如果TChannel未连接,则走异步连接逻辑ConnectAsync,最终会回到Start()函数
  2. 如果没有开始接收数据,则调用StartRecv,处理一些recvBuffer状态,便于数据处理。然后调用RecvAsync
  3. RecvAsync开始接收数据,异步或同步接收数据完毕,会调用到OnRecvComplete。
  4. OnRecvComplete内部对接收到的数据进行处理,会用到上面的parser处理buffer到memoryStream,然后触发readCallback事件,将数据返回给上层逻辑。内部还包含了Socket异常状态处理,以及传输数据为0的处理。
  5. 最后又回到StartRecv,这样就能一旦有数据过来马上就能监听并处理。

再来看下发送数据的工作流程。
this.GetService().MarkNeedStartSend(this.Id);这行代码会将发送的启动交给上层统一调度。
主要得发送入口是StartSend()

  1. 对sendBuffer进行一些处理,便于发送,然后调用SendAsync
  2. 再次处理数据,设置SocketAsyncEventArgs,然后调用this.socket.SendAsync(this.outArgs)即可发送
  3. 发送完毕,进入OnSendComplete,处理发送数据是否发送完毕,进行一些处理,如果没有发送完,则再次调用StartSend
  4. TChannel提供了一个额外接口,可以直接发数据,public override void Send(MemoryStream stream),这个函数,内部对传过来的MemoryStream 进行处理,将其存入到sendbuff中,在调用MarkNeedStartSend,交给上层调度。同时应该注意到,在Start()函数内部也有this.GetService().MarkNeedStartSend(this.Id);

总结

TChannel由于内部包含了两种通信方式,一种是主动,一种是被动,所以看起来很多地方都是有两套的,包括上面的接受数据,与发送数据。实际上一个TChannel,只会处于一种方式,要么被动等待连接的方式,要么主动连接的方式,但是大多数处理相同,所有ET将两种方式都写在一起了。
比如,发送数据,在start与send里面都包含了MarkNeedStartSend函数,将发送交给上层,实际上应该只会有一种方式运行。
上面纯粹个人理解,如果有误,欢迎讨论。下篇继续记录通讯里的Service。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
要将ET框架部署到腾讯云服务器,可以按照以下步骤进行操作: 1. 购买云服务器:首先,在腾讯云平台上购买一台适合的云服务器,可以选择按需付费或预付费的方式。 2. 远程连接服务器:使用SSH工具(如Xshell或PuTTY)远程连接到你的云服务器,确保已经获得了服务器的root权限。 3. 安装依赖环境:根据ET框架的要求,在服务器上安装所需的运行环境,如Python、MySQL、Redis等。可以使用命令行工具或包管理器(如apt或yum)来安装这些依赖。 4. 下载ET框架:在服务器上使用命令行工具,如wget或git,将ET框架的源代码下载到指定的文件夹中。 5. 配置ET框架:在ET框架的配置文件中,根据自己的需求设置相关参数,如数据库连接信息、日志路径等。 6. 启动ET框架:使用命令行工具进入到ET框架的目录,并执行启动命令,如python main.py或sh start.sh。 7. 配置服务器访问权限:为了能够从外部访问到ET框架提供的服务,需要在服务器的安全组规则中添加相应的端口和协议,并将访问方式设置为允许。 8. 域名绑定:如果需要使用域名来访问ET框架,可以在腾讯云的DNS解析服务中,将域名和服务器的IP地址进行绑定。 9. 监控和维护:定期监控服务器的运行状态和性能状况,保持系统的稳定性和安全性。同时,及时更新ET框架和依赖环境的版本,以获取最新的功能和修复漏洞。 总结:部署ET框架到腾讯云服务器需要购买服务器、安装依赖环境、下载ET框架、配置框架服务器、启动框架、配置访问权限、域名绑定、监控和维护等步骤。这些步骤能够确保ET框架能够在腾讯云服务器上正常运行,并通过互联网提供服务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值