【Unity】unity3d客户端网络框架

Unity3D客户端通用网络模块

请添加图片描述

一、流程
  1. 创建Socket,使用异步方式Connect服务器,然后调用BeginReceive()开始等待服务器数据的到来。
  2. 服务器数据到来后,触发回调函数ReceiveCallback(),然后把携带的消息插入到消息队列msgList中,然后继续调用BeginReceive()等待服务器消息。
  3. Unity的Update()根据设定从msgList消息队列取出指定数量的信息。信息里包含协议名,根据协议名调用对应的函数处理消息内容。
二、注意事项

请添加图片描述

  1. 因为使用到异步Connect,所以回调ReceiveCallback()会开启子线程,不在Unity主线程中。因此对消息队列msgList的操作需要加锁(ReceiveCallback插入消息,和Unity主线程读取消息过程都需要加锁)。
  2. ReceiveCallback()后的消息,需要处理粘包半包、大小端判断等处理。
  3. 自己实现消息发送的写入队列和接收队列,用以处理粘包半包问题。
三、节选代码实现(C#实现)
3.1 Connect相关
public static void Connect(string ip, int port) {
	//状态判断
	if(socket != null && socket.Connected) {
		Debug.Log("Connect fail, already connected!");
		return;
	}
	if(isConnecting){
		Debug.Log("Connect fail, isconnecting!");
		return;
	}
	//初始化成员
	initState();
	//参数设置
	skcoet.NoDelay = true;
	//Connect
	isConnecting = true;
	socket.BeginConnect(ip, port, ConnectCallback, socket); //第三个参数是回调函数
}

//初始化状态
private static void InitState() {
	//Socket
	socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);	
	//接收缓冲区
	readBuff = new ByteArray();
	//写入队列
	writeQueue = new Queue<ByteArray>();
	//是否正在连接
	isConnecting = false;
}

private static void ConnectCallback(IAsyncResult ar) {
	try {
		Socket socket = (Socket)ar.AsyncState;
		socket.EndConnect(ar);
		Debug.Log("Socket Connect Succ");
		FireEvent(NetEvent.ConnectSucc, "");
		isConnecting = false;
	
		//开始接收
		socket.BeginReceive(readBuff.bytes, readBuff.writeIdx, readBuff.remain, 0, ReceiveCallback, socket); //倒数第二个参数是接收到信息的回调函数
	} 
	catch (SocketException ex) {
		Debug.Log("Socket Connect fail" + ex.ToString());
		FireEvent(NetEvent.ConnectFail, ex.ToString());
		isConnecting = false;
	}
}
3.2 Receive相关
public static void ReceiveCallback(IAsyncResult ar) {
	try {
		Socket socket = (Socket)ar.AsyncState;
		//获取接收数据长度
		int count = socket.EndReceive(ar);
		if(count == 0) {
			Close();
			return;
		}
		readBuff.writeIdx += count;
		//处理二进制消息
		OnReceiveData();
		//继续接收数据
		if (readBuff.remain < 8) {
			readBuff.MoveBytes();
			readBuff.ReSize(readBuff.length*2);
		}
		//重新开始接收服务器消息
		socket.BeginReceive(readBuff.bytes, readBuff.writeIdx, readBuff.remain, 0, ReceiveCallback, socket);
	}
	catch (SocketException ex) {
		Debug.Log("Socket Receive fail" + ex.ToString());
	}
}


//OnReceiveData()有两个功能。
//其一是根据协议的前两个字节判断是否接收一条完整的协议。如果收到完整的协议,便解析它;如果没有接收完整协议,则退出等待下一波消息。
//其二是解析协议,根据协议格式解析出协议对象,然后通过msgList.Add(msgBase)将协议对象添加到消息列表中。
//添加消息列表之前,使用lock(msgList)锁住消息队列,是因为OnReceiveData()在子进程将数据写入消息队列,而Update在主线程读取消息队列,为了避免线程冲突,对msgList的操作都需要加锁。
public static void OnReceiveData() {
	//消息长度
	if (readBuff.length <= 2) {
		return;
	}
	//获取消息体长度
	int readIdx = readBuff.readIdx;
	byte[] bytes = readBuff.bytes;
	Int16 bodyLength = (Int16)((bytes[readIdx+1] << 8) | bytes[readIdx]);
	if (readBuff.length < bodyLength+2) 
		return;
	readBuff.readIdx += 2;
	//解析协议名
	int nameCount = 0;
	string protoName = MsgBase.DecodeName(readBuff.bytes, readBuff.readIdx, out nameCount);
	if (protoName == "") {
		Debug.Log("OnReceiveData MsgBase.DecodeName fail");
		return;
	}
	readBuff.readIdx += nameCount;
	//解析协议体
	int bodyCount = bodyLength - nameCount;
	MsgBase msgBase = MsgBase.Decode(protoName, readBuff.bytes, readBuff.readIdx, bodyCount);
	readBuff.readIdx += bodyCount;
	readBuff.CheckAndMoveBytes();
	//添加到消息队列
	lock(msgList) {
		msgList.Add(msgBase);
	}
	msgCount++;
	//继续读取readBuff里剩余消息
	if (readBuff.length > 2) {
		OnReceiveData();
	}
}
3.3 Update相关
//Unity Update
public static void Update() {
	//服务器消息处理
	MsgUpdate();
	//心跳处理
	PingUpdate();
}
//服务器消息处理
public static void MsgUpdate() {
	//初步判断,提升效率
	if (msgCount == 0)
		return;
	
	//一帧内处理的消息数量
	for (int i = 0; i < MAX_MESSAGE_FIRE; i++) {
		//获取消息
		MsgBase msgBase = null;
		lock(msgList){
			if (msgList.Count > 0) {
				msgBase = msgList[0];
				msgList.RemoveAt(0);
				msgCount--;
			}
		}
		//分发消息
		if (msgBase  != null) {
			FireMsg(msgBase.protoName, msgBase);
		} 
		//没有消息了
		else {
			break;
		}
	}
}
//分发消息
private static void FireMsg(string msgName, MsgBase msgBase) {
	if (msgListeners.ContainKey(msgName)) { //预先注册协议处理函数到msgListeners列表里。根据协议名找对应的协议处理函数。
		msgListeners[msgName](msgBase); //调用协议处理函数,把消息协议基类作为参数传去,在具体函数里再强转为对应的子消息协议类
	}
}
//心跳处理
//1.根据isUsePing判断是否启用心跳机制,如果没有开启,直接跳过。
//2.判断当前时间与上一次发送MsgPing协议的时间(lastPingTime)间隔,如果超过指定时间(pingInterval),调用Send(msgPing)向服务端发送MsgPing协议。
//3.判断当前时间与上一次接收MsgPong协议的时间(lastPongTime)间隔,如果超过指定时间(pingInterval*4),调用Close关闭连接。
private static void PingUpdate() {
	//是否启用
	if(!isUsePing)
		return;
	//发送PING
	if (Time.time - lastPingTime > pingInterval) {
		MsgPing msgPing = new MsgPing();
		Send(msgPing);
		lastPingTime = Time.time;
	}	
	//检测PONG时间
	if (Time.time - lastPongTime > pingInterval*4) {
		Close();
	}
}
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值