1.处理报文的逻辑()
原理:按照报文的序号,进行顺序处理,如果是收到超过当前顺序加1的报文,先进行缓存,等待下一个报文处理后再处理后面的报文
public void Handle(BufferEntity buffer)
2.在USocket里面增加一个接口,在Updata里面进行调用,服务器可以开线程循环调用
原理:从消息队列的字典中取消息,将其反序列化,打印日志,传给业务处理逻辑的函数(local.Handle(bufferEntity);)
public void Handle()
3.业务逻辑处理函数
原理:先判断报文的类型,
1)如果是ACK的报文,将发送报文队列中的报文包去掉(按照序号去掉,sendPackage.TryRemove(buffer.sn,out bufferEntity)),不再进行超时重传的检测;
2)如果是业务报文,先回复服务器,表示已经收到这个报文,传给HandleLogincPackage(BufferEntity buffer)这个接口,这个函数就是检测这个报文的序号,如果这个序号大于1就缓存起来到awaitHandle这个线程安全的队列,处理完这个数据后,再从缓冲区取数据包,如果缓冲区没这下一个数据包,如果有的化再调用本函数
4.代码
//UClient.cs
using System.Net;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Game.Net
{
//客户端的代理
public class UClient
{
//参数
public IPEndPoint endPoint;
USocket uSocket; //USocket内部封装了发送的接口
public int sessionID; //会话ID,客户端只需要接受报文的会话ID赋值就行了
//服务端要拿到不同的会话ID区分不同的客户端
public int sendSN = 0; //发送序号
public int handleSN = 0; //处理的序号,为了保证报文的顺序性
//这里为什么要当作形参来传递呢?
//为了应对以后的业务扩展
Action<BufferEntity> handleAction; //定义处理报文的函数,实际就是分发报文给各个游戏模块
//先来个构造函数->完成初始化
//sendSN 发送的序号
//handlSN 处理的序号
//sessionID 会话ID
//Action<BufferEntity> dispatchNetEvent 派发报文的事件传递进来,也就是待处理的消息
public UClient(USocket uSocket,IPEndPoint endPoint,int sendSN,int handlSN,int sessionID, Action<BufferEntity> dispatchNetEvent)
{
this.uSocket = uSocket;
this.endPoint = endPoint;
this.sendSN = sendSN;
this.handleSN = handleSN;
this.sessionID = sessionID;
handleAction = dispatchNetEvent;
//初始化就开始调用时间统计
CheckOutTime();
}
//处理消息
//按照报文的序号,进行顺序处理,如果是收到超过当前顺序加1的报文,先进行缓存,等待下一个报文处理后再处理后面的报文
public void Handle(BufferEntity buffer)
{
//服务器给我们客户端已经分配了一个会话ID
if (0 == this.sessionID && buffer.session != 0)
{
//把会话ID缓存起来
this.sessionID = buffer.session;
}
//判断消息的类型
switch(buffer.messageType)
{
case 0: //0是ACK确认报文
BufferEntity bufferEntity;
if(sendPackage.TryRemove(buffer.sn,out bufferEntity))
{
//移除成功打印日志
Debug.Log($"收到ACK确认报文,序号是:{buffer.sn}");
}
break;
case 1: //1是业务报文
BufferEntity ackPacka = new BufferEntity(buffer);
uSocket.SendACK(ackPacka); //先告诉服务器,我客户端已经收到这个报文
//再来处理业务报文
HandleLogincPackage(buffer);
break;
default:
break;
}
}
//定义字典存缓存,int是序号,值是BufferEntity,使用线程安全的字典
ConcurrentDictionary<int, BufferEntity> awaitHandle = new ConcurrentDictionary<int, BufferEntiy>();
//处理业务报文
void HandleLogincPackage(BufferEntity buffer)
{
//若过处理的需要小于当前处理的序号,直接返回
if (buffer.sn <= handleSN)
{
return;
}
//压入缓冲区
if (buffer.sn - handleSN > 1)
{
if (WaitHandle.TryAdd(buffer.sn, buffer))
{
Debug.Log($"收到错序的报文:{buffer.sn}");
}
//如果不是错序的报文,直接返回
return;
}
//如果不是错序的报文
//更新一下已经处理的报文
//涉及到委托相关的知识
handleSN = buffer.sn;
if (handleAction != null)
{
handleAction(buffer);
}
//判断缓冲区里面有没有下一条要处理的数据
//如果有就remove移除
BufferEntity nextBuffer;
if(WaitHandle.Remove(handleSN+1,out nextBuffer))
{
//移除放到nextBuffer
HandleLogincPackage(nextBuffer);
}
}
//定义字典存缓存,int是序号,值是BufferEntity,使用线程安全的字典
ConcurrentDictionary<int, BufferEntity> sendPackage = new ConcurrentDictionary<int, BufferEntiy>();
//发送报文的接口
//这里不需要在void前加async,因为在下面的send已经实现了
public void Send(BufferEntity package)
{
//更新报文发送的时间
package.time = TimeHelper.Now(); //暂时先等于0
sendSN += 1;
package.sn = sendSN;
package.Encoder(false); //对package编码,传递false表示不是ACK的报文
if (sessionID != 0) //等于0表示还没跟服务器建立连接
{
//缓存起来,因为可能需要重发(缓存需要定义一个字典)
sendPackage.TrtAdd(sendSN, package);
}
else
{
//还没跟服务器建立连接,所以不需要j进行缓存
}
uSocket.Send(package.buffer, endPoint); //客户端的endPoint没啥用,还可以再优化下
}
//定义超时的时间
int overtime = 150;
//超时重传检测的接口
//需要定期从字典里面取出报文,然后判断是否超时就可以了
public async void CheckOutTime()
{
await Tash.Delay(overtime);
foreach (var package in sendPackage.Values)
{
//确定是不是超过最大发送次数,考虑关闭socket
if (TimeHelper.Now() - package.time >= overtime * 10)
{
OnDisconnect();
return;
}
//如果没超过10次
if(TimeHelper.Now()-package.time>=(package.recurCount+1)*overtime)
{
package.recurCount += 1; //超过150秒就重传
uSocket.Send(package.buffer, endPoint);//重传,内部已经实现了(await)
}
}
CheckOutTime(); //继续等待150毫秒后进行检测
}
//关闭socket的函数
public void OnDisconnect()
{
if (local != null)
{
local = null;
}
if (udpClient != null)
{
uSocket.Close();
handleAction = null;
}
}
}
}