Unity3D游戏制作学习记录03——丛林战争

Siki学院的视频教程指路牌:http://www.sikiedu.com/course/61.

粘包和分包

粘包

我们要发送出去的数据成为包,当我们发送的数据包很小,发送间隔又很短的时候,这对于性能的消耗是很大的,Socket就会自动优化,把这些数据组合起来作为一条消息发送到服务器端。
出现的情况一般是在大型网络游戏中,要一直更新定位,血量等等的数据,就很容易出现粘包的问题了。

分包

当我们发送的数据包很大的时候,Socket就会把包分开去传输。一方面,如果整个包去发送的话,如果发送失败,就要重新来过,十分耗费时间;另一方面,把大的数据包分成小包去发送,发送的也会更快,如果有某几个包发送失败了,只需要单独重新发送那几个包就可以了。

解决方法

在发送的数据包取几位,记录数据包的长度,当所接收到的数据包长度小于记录的长度,我们就暂时不处理,直到接收到的数据包长度和记录长度相等,再统一处理。
解决的主要是由于强制分包导致每个包最后几位或者最前几位识别错误(byte转string)的问题。
(有点像我以前学过的课《计算机网络》里面的内容,有学过的朋友应该比较好理解吧o( ̄▽ ̄)ブ)

那么具体记录长度的字节要有多大呢?
因为记录长度的肯定是整型int,那么我们根据所选的int所占用的字节长度,来作为数据包的长度存储。
int有int16,int32,int64,这里选的是int32,即32位,4个字节。

字节转换方式
说到转成字节,那我们按照之前消息发送那样子用,把数据包的长度转换成字节:

int count = 10000;
System.Text.Encoding.UTF8.GetBytes(count.ToString());

它的转换原理是针对字符串(引用类型)的,它转换是先将字符串一个个字符转换成对应的字节来存储,比如说字符串“10000”,转换出来的字节数组就是“49;48;48;48;48;”(一个分号一个字节)
那如果是“100”,转换出来的就是:“49;48;48;”

至于为什么1对应49
是因为在UTF-8中:1的UTF-8编码对应的十进制数是49,

可见,转换出来的int所占字节的位数是不固定的,它和这个数据的字符串对应的长度有关,因为是一个数字对应一个字符转换成字节。

这显然是不满足我们采取固定字节长度来存储数据包大小的要求啦。

所以,我们转换字节的方式不能用以前的办法,应该要用针对值类型的字节转换方式:
BitConverter.GetBytes(count);

至于为什么233直接是233;0;0;0;
是因为233可以用一个字节表示完全,一个字节是8位,
二进制:11101001

同理,10000
十进制:10000
二进制:00100111;00010000

这是我转成二进制输出的效果:

我的问题:
我不理解的是,根据输出,是从左往右填写,在后面补0,为什么不是从右往左填值,在左边补0?

客户端——消息整合发送

  1. 我们给客户端添加一个新建项:MessagePackup
    将处理发送的消息的数据拼接方法封装在这个类里面

    2.先处理消息,转换为字节数组;
    然后获取到数组的长度,同样转换称字节数组;
    然后利用byte数组的内置方法将两个数组合并,返回即可。
namespace TCPClient
{/// <summary>/// 处理消息的拼接和组装,添加长度信息等/// </summary>public class MessagePackup{/// <summary>/// 将消息字符串转换成字节数组/// </summary>/// <param name="msg">要发送的消息</param>/// <returns></returns>static public byte[] GetByte(string msg){// 数据转字节byte[] dataBytes = System.Text.Encoding.UTF8.GetBytes(msg);// 长度转字节byte[] dataLength = BitConverter.GetBytes(dataBytes.Length);// 拼接byte[] msgBytes = dataLength.Concat(dataBytes).ToArray();return msgBytes;}}
}
  1. 我们修改一下客户端发送消息的转换

             while (true){// 客户端向服务器发送消息,发送的消息由用户在控制台输入string msgSend = Console.ReadLine();socketClient.Send(MessagePackup.GetByte(msgSend));}
    

服务器端——消息解析接收

  1. 为服务器端添加新项,用于消息解析:MessageUnpack
  2. 用于消息解析,因此我们需要有存储消息的字节数组 receiveBytes;
    了解决粘包的问题并防止数据包覆盖原来未处理的数据,我们需要一个记录下一个数据包可以追击的下标 addIndex,那这个字段同时也可以表示当前存储的数据长度。
    由于这两个字段是和读取到的消息有关,并不能随意更改,我们让它私有,但为了外部的获取,同时为它们添加属性。
         // 存储接收到的byte消息private byte[] receiveBytes = new byte[1024];// 填充位置的标记,记录上一次数据存储的位置// 也相当于当前数组内存储的数据长度private int addIndex = 0;/// <summary>/// 提供给外部 存储消息的字节数组/// </summary>public byte[] receiveMssage{get { return receiveBytes; }}/// <summary>/// 提供给外部 获取消息存储的开始位置/// </summary>public int AddIndex{get { return addIndex; }}

由于粘包情况,我们需要判断当前这个数据还是否能存得下数据,所以我们添加一个remainSize来记录剩余大小。

         /// <summary>/// 提供给外部 获取当前数组剩余长度/// </summary>public int RemainSize{get { return receiveBytes.Length - addIndex; }}

目前这些字段的关系如下图辣:
2.服务器消息处理:
由于服务器的消息接收处理是在AcceptCallback()回调函数中,我们就在那里面进行修改。
先声明一个静态全局的MessageUnpack类的对象:msgUnpack
在开启异步接收BeginAceive()的参数修改:

 // 异步接收客户端的消息// 读取的数据存储数组,存储的起始位置,接收数据的长度,套接字的标志位,接收到消息时要执行的函数,要给函数传递的信息clientSocket.BeginReceive(msgUnpack.receiveMssage, msgUnpack.AddIndex, msgUnpack.RemainSize, SocketFlags.None, ReceiveCallback, clientSocket);

我们把存储的起始位置改为了追加的下标addIndex;
可接收数据的长度是remainSize,用于防止字节数组无法存储的情况;

3.由于接收消息的操作在BeginReceive()的回调函数:ReceiveCallback()中:
① 在接收到新消息的时候,更新追加下标的值addIndex,让其向后移

② 对消息进行解析,我们将这步操作封装在MessageUnpack类里的ReadMessage()发方法内:
Ⅰ.判断消息有效性,即是否长度小于一个int32所占字节的长度
Ⅱ. 判断数据是否接收完毕:
1. 获取消息长度:读取第一个int32字节:
int msgSize = BitConverter.ToInt32( receiveMssage, 0);
2. 当前字符数据长度大于接收到的该条数据:(addIndex>=msgSize+sizeof(int32))
2.1 则可以处理:
string msgRecv = System.Text.Encoding.UTF8.GetString(receiveMssage, 4, msgSize);
消息处理结束后,消息字节数组前移,追加下标addIndex前移:
int moveSize = msgSize + 4;
Array.Copy(receiveBytes, moveSize, receiveBytes, 0, addIndex - moveSize);
addIndex -= moveSize;
2.2 否则等待下一条消息,直到满足2.1

代码

     /// <summary>/// 接收新消息 更新追加下标后移/// </summary>/// <param name="addSize"></param>public void UpdateAddIndex(int addSize){addIndex += addSize;}/// <summary>/// 消息解析/// </summary>public void ReadMessage(){while (true){// 接收数据无效if (addIndex <= 4) return;//  有效数据 消息解析// 1. 获取消息长度,从消息数据的第0位开始读取一个int32的大小的数据int msgSize = BitConverter.ToInt32(receiveMssage, 0);// 2.处理长度后的消息内容// 2.1 消息长度是否足够 【粘包和正常情况时】if ((addIndex - 4) >= msgSize){// 数据长度足够,从第4位开始读取string msgRecv = System.Text.Encoding.UTF8.GetString(receiveMssage, 4, msgSize);Console.WriteLine("消息解析\n" +"size:"+ msgSize+"  内容:"+ msgRecv);// 将后半部分的数据往前移int moveSize = msgSize + 4;Array.Copy(receiveBytes, moveSize, receiveBytes, 0, addIndex - moveSize);// 追加下标前移addIndex -= moveSize;}// 2.2 接收的消息比消息长度短【分包的情况时】else{break;}}}

运行结果

客户端实验代码:

            for (int i = 0; i < 2333; ++i){socketClient.Send(MessagePackup.GetByte(i.ToString()));}

粘包解决效果:

实验结果显示没有出现粘包现象。

由于我没有保留原来的代码,只好截视频的粘包结果了:
视频循环是0-100,在第二次发送可以看到包背结合了

Unity3D游戏制作学习记录03——丛林战争相关推荐

  1. Unity3D游戏制作学习记录02——丛林战争

    Unity3D游戏制作学习记录02--丛林战争 Siki学院的视频教程指路牌:http://www.sikiedu.com/course/61. 一.服务器端--消息接收的异步处理 由于之前使用Rec ...

  2. Unity3D游戏制作学习记录01——丛林战争

    Unity3D游戏制作学习记录01--丛林战争 Siki学院的视频教程指路牌:http://www.sikiedu.com/course/61. 学业繁忙-和朋友一起跟着siki的丛林战争的教程跟着做 ...

  3. 拇指接龙游戏升级记录03(升级MainScene.cpp)

    MainScene是拇指接龙游戏的主游戏场景文件,拥有近5000行代码. 说实在的,实现自cocos2d-x 2.x版本向当下最新的3.8.1版本的升级过程,其中涉及的技术不是一下能够说明的.有些是形 ...

  4. 游戏制作-联网对战丛林战争制作笔记(一)

    开篇先说明,这个游戏制作也是我跟随别人的教程制作的游戏,因此想要了解更多的内容可以去看siki老师的视频,我这里做笔记的目的有两个,一个是帮助喜欢看文字版教程的朋友进一步的学习,一个是保存自己在学习中 ...

  5. 2021.03.17 pokémon小游戏开发记录与周总结

    2021.03.17 pokémon小游戏开发记录与周总结 此篇仅包含部分项目代码,只是个人的学习总结. 文章目录 2021.03.17 pokémon小游戏开发记录与周总结 前言 一.前期准备 二. ...

  6. 游戏思考系列03:游戏匹配机制(MMR、ELO、trueskill2、皇家战争、Glicko等,详细讲ELO,其他的简略)

    文章目录 一.MMR机制--炉石传说.DOTA2.LOL.LOLM 1)简介 2)影响因素 二.ELO机制--王者/LOLM(也就是LOL手游) 1)简介 三.trueskill 2--光环.彩虹六号 ...

  7. unity网络实战开发(丛林战争)-正式开发阶段(014-游戏客户端与服务器端连接搭建)

    使用工具:VS2017,unity3d 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 上一篇文章中,我已经把服务器端的框架进行了搭建,接下来, ...

  8. unity网络实战开发(丛林战争)-正式开发阶段(018-声音管理器模块的完善)

    使用工具:VS2017,unity3d 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 上一篇文章中,已经完成了注册事件的处理,接下来将完善声音 ...

  9. unity网络实战开发(丛林战争)-正式开发阶段(016-数据库设计以及登录处理)

    使用工具:VS2017,unity3d 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 上一篇文章中,已经完成了游戏场景与开始界面UI的搭建,接 ...

  10. Unity3D 游戏引擎之脚本实现模型的平移与旋转(六)

    Unity3D 游戏引擎之脚本实现模型的平移与旋转 雨松MOMO原创文章如转载,请注明:转载至我的独立域名博客雨松MOMO程序研究院,原文地址:http://www.xuanyusong.com/ar ...

最新文章

  1. PHP下载/采集远程图片到本地
  2. 好莱坞科幻新片《b》,钦定 AI 机器人出演女主角!
  3. 全局脚手架了解一下【fle-cli】
  4. Java 洛谷 P1150 Peter的烟
  5. Linux下编写选择排序(C语言)
  6. 医院计算机操作权限管理制度,附五医院网络工作站管理制度
  7. python快乐数字怎么表达_Python经典面试题:这些面试题你会了吗?
  8. 安装nginx、drizzle和lua
  9. Service获取客户端IP地址(java)
  10. CCF201709-1 打酱油(100分)
  11. windows下安装GCC编译器
  12. 使用蒙特卡罗模拟期权定价
  13. 实用是计算机知识,实用电脑知识
  14. java md5加密 jar包,java md5加密工具类学习示例
  15. 浪潮服务器销售案例ppt,浪潮服务器产品线介绍(ppt 105页)
  16. Exploring $ORIGIN - 探索 $ORIGIN
  17. 会写SQL==精通MYSQL?NONONO,进来学习一下吧! 0.0
  18. MD5 JS实现加密
  19. Matlab中的c2d函数用法
  20. vue2+element ui 导入和导出后端传过来的文件

热门文章

  1. 常见的Web服务器、应用服务器(Apache、tomcat、jetty、Nginx)简介及优缺点总结
  2. 模板文件云存储管理 Sisyphus
  3. 分享一个jQuery的自动客户端本地保存插件Sisyphus.js - 帮助你自动保存用户输入内容
  4. CNI IPAM插件分析 --- 以hostlocal为示例
  5. Elasticsearch 聚合系列:adjacency matrix aggregation(邻接矩阵聚合)
  6. java编写平行四边形的代码_Java代码编写四边形
  7. 静态路由 动态路由 php,静态路由汇总(路由聚合)
  8. Hadoop-Hive常用,命令
  9. LeetCode第714题解析
  10. 软件体系结构描述语言与建模实验描述c2软件体系结构风格,软件体系结构描述语言.pdf...