本文章以组队系统向服务器发送创建队伍消息为例,展示Socket协议是如何运作的
第一张图是proto文件
Lua相关
下图是Lua版本的PB文件,使用这些PB文件去序列化和反序列化数据
重点关注标注2和标注3,标注2是数据包的包头重要组成部分,也是服务器和客户端之间通信数据包头的关键内容,是头内的内容
第三张图是点击组队按钮后,按钮调用创建房间函数,调用pb创建数据结构体,并将结构体内填充正确的数据,
例如UserData.playerID就是我自己角色的ID,队伍目标等级为0等数据,
Lua层通知C#层的发送数据包,该函数第一行序列化数据,这是重要知识点,什么是序列化,一句话解释:把一个数据转换成二进制数组,在C#层就是byte[]格式,包头数据是不需要序列化的,第二步就是把Socket的ID、包头数据MRID GROUPID UNITID、序列化数据传到C#层
转到C#层,m_sendMessageInfoBuff和m_SendList组成了包数据
C#相关
由于框架是C#写的,所以在Lua层序列化好的数据一定是交由C#去发送给服务器的,C#接收Lua的序列化数据如下图所示
下图是包头的定义,在Socket初始化时会用到包头,MSG_HEADER_LEGTH代表长度最大是7,包头占一条数据的前8个字节,tid、gid、uid占056位置,包头是干嘛用的,在后面会讲。
下面开始处理咱们传过来的数据
m_packProcess.MakeHead是制作包头,第二步m_SocketOutputSteam是发送缓存区的写入并等待发送,
由于服务器同学知道包头结构的,C++代码的拆解方式和C#是一样滴,因此当服务器同学拿到这个数据流时,先判断当前接收缓存区的数据够不够包头大小,根据包头内的数据长度判断当前数据够不够长,我的工程包头大小定义为8字节,不够8字节那肯定不满足我的包头大小,等8字节够了后再去判断数据的长度,数据的长度也够了就把byte[]数据序列化成proto结构体数据,服务器这里就拿到了和我们当初lua手里一样的数据了,
反过来服务器向我们发送数据,我们的原理也是相同的,先从接收缓存池中判断二级制流的长度是否够8字节,因为我们的包头是8字节不变的,如果够再去判断数据的长度,如果数据的长度也够,那么这个包就是完整的,把这个包的数据踢出去
客户端在Update函数中时刻执行socket的接收并把数据写入接收缓存区中,之后再把需要发送的数据写入发送缓存区。这一帧的接收数据判断不满足一个包的完整数据长度,因此继续等待下一帧的数据,直到我们拿到一个完整的包,重点知识来了!!由于多个包组成了一条完整数据,因此这个过程叫分包/拆包。
当发送缓存区发送多个包时,可能会把多个包数据合并成一个包发送出去,这个过程叫粘包,因为有包头的存在,我们也可以把这个粘起来的包进行完美的切割并得到我们想要的数据。