前言:这段时间主要是优化项目的网络底层,由于项目采用的热更方案是xLua+Ab包,业务都是在lua那边,所以一开始用的是luasocket,而且是同步通讯,导致有时等待连接时主线程阻塞,或者要频繁的设置超时,导致性能和体验方面都不好,最后决定使用C#的Socket来写一遍。这篇主要是想分享在写这个xlua和C#Socket的网络底层时遇到的坑。
分析
为什么不继续用luasocket,因为上网查找到关于luasocket的资料和讨论就比较少,而关于luasocket异步的更少,官方例子也不太适用于框架。所以最后打算用C#那边的socket,然后热更修复就用ILRuntime、InjectFix那些。
开发
官方示例
可以先看下微软官方例子:异步客户端套接字示例 | Microsoft Docs
几个关键API:BeginConnect、BeginReceive、BeginSend、EndConnect、EndReceive、EndSend。异步连接、接收、发送,以及对应的结束挂起的异步。
有个点、官方示例中向异步回调中传值是通过最后一个参数传入的
有可能是避免对主线程的调用?有大佬知道的话可以说一下。
实际开发
1、断线重连:主要是检测心跳包的接收,如果超过一定时间没收到就断线然后重连。
2、断线时没发送的协议重发:可以在发送成功时的回调中返回成功发送的pid或者name用作判断
3、断线重连后之前必须接收的协议的下发:这个有两种情况,比如说登录时有数据模型要初始化,这时就需要有个协议列表,只有在接收完所有协议再进入游戏。
还有一种比如说新手引导等系统中,在某系统的必须连续的步骤时中断的情况下,如何保证前端的正常运行,这种的话就需要服务器下发相应的协议,就比方说新手引导,如果是强制性引导的话服务器可以在重连后检测是否有完成了强制性引导,如果没有的话就下发相关的协议。
4、连接、发送、接收超时的处理:由于是异步 所以SendTimeOut和ReceiveTimeOut不会起作用,如果是断网的情况下会立刻返回抓取到的错误,但是弱网的话可以就要自己去写一个计时了。
5、网络下发数据的拼接与处理:这里要重点说一下,上面也有说过因为项目的业务是写在lua里面,所以C#接收到的数据通过xLua传到lua进行处理。
首先实际的数据接收有两种:
一:检测是否有需要接收的字节流,然后抓取(Revice)几个字节进行计算,拿到包头or包体的长度,然后按照长度取出对应的字节数组,再进行解密,往复循环。
二:检测是否有需要接收的字节流,然后创建一个固定的字节数组进行接收,如果还检测到有剩余的字节流要接收就一直递归,最后再按照一定规则进行解密。
第一种的好处是不用拼接字节,但是需要频繁调用Revice,正常来说调用多次Revice是完全没有问题的,但由于项目中是是lua调C#,而且还是异步接收,所以想稍微避免一下这个问题,所以就采用了第二种方法。
第二种的话需要拼接数据,这里主要是说拼接字节的问题,首先看下xLua的类型映射(XLua官方API 03 类型映射_yhx956058885的博客-CSDN博客)。
byte[] 对应的是 string,所以一开始我打算是把C#接收到的字节数组传到lua端,然后使用 .. 或者
table.concat 进行拼接,实际使用后发现拼接后读取里面的其中的字节后,数值一直为0。后面转为在C#端进行拼接,使用了好几种方法进行拼接后都发现拼接后数据错误。最后找到一种可以正常拼接字节数组,并能正常通过xLua传递给lua端。
public List<byte> byteList = new List<byte>();
byteList.AddRange(state.bytes);
byte[] b = byteList.ToArray();
6、 线程安全:
一、先说一个最重要的点,我们知道线程和异步不是同一个东西。但是异步是基于多线程的,所以也会有下述问题在性能优化那章unity官方有说:
可使用 .NET System.Threading.Thread 类在单独的线程上运行繁重的计算。因此可在多核上运行,但请注意,Unity API 不具有线程安全性;您需要缓冲输入和结果,并在主线程上读取和分配它们以便使用 Unity API 调用。
故不能在非主线程的线程上调用Unity的Api。
二、在实际开发过程中,即使不使用Unity的Api也依然会崩溃,最后在看xLua的GitHub的Issues时,发现在使用多线程时需要加一个 THREAD_SAFE 的宏,其作用是互斥。(这里没找到这个宏的所定义的内容,如果有大佬找到了的话欢迎留言一下)
结语
上述就是在弄这个客户端网络系统时遇到的问题了,主要来说要注意的是两点,一个是字节流的拼接和线程安全。后面如果有时间的话会再研究一下lua的异步。