Unity之网络模式&网络通信(HTTP & TCP/Socket)

一.OSI参考模式

1.网络拓扑图

在这里插入图片描述
用户使用家用电脑通过网线连接到局域网,再通过防火墙连接到中转服务器,由服务器再转接到各个网络设备.

2.七层参考模型&五层参考模型

在这里插入图片描述

OSI7层参考模型:
  • 物理层(网线,水晶头,路由器,交换机),
  • 数据链路层(2进制数据,提供介质访问和链路管理),
  • 网络层(IP:实现主机网络定位,端口号:操作系统虚拟出来的,数据通过门流入流出,套接字建立在此),
  • 传输层(TCP/IP炉石传说,稳定传输,/UDP王者荣耀,不稳定传输/ipconfig/all,建立管理和维护端到端的连接),
  • 会话层(建立管理维护会话),
  • 表示层(数据格式转化、数据加密),
  • 应用层(HTTP为应用程序提供服务)
    在这里插入图片描述
TCP/IP5层模型:

会话层,表示层,应用层和为应用层.

二.网络连接

1.长连接和短连接

长连接:服务器端很容易给客户端发数据;客户端和服务器一开始会进行连接,并一直保持连接,直到不再和服务器交换数据时,会断开连接
短连接:客户端主动找到服务器,服务器反馈数据;需要数据交换时,连接服务器,数据交换完成后,断开连接.
带宽:运营商带宽:比特(bit)带宽,实际带宽:字节(byte)带宽

HTTPS协议,敏感数据传参会被加密 ,更安全.端口号443,HTTP协议未加密,端口号80

2.HTTP协议构成

URL结构
  • 通讯协议:
    • “http://”
    • “https://”
  • 主机地址:
    • IP:39.105.153.133
    • 域名:hxsd.uc.honorzhao.com
  • 端口号
    • “:80”:提供HTTP服务的端口
  • 目录
    • “/目录名”:服务器脚本在服务器上存储的路径
  • 脚本名称
    • “index.php”
  • URL参数
    • “?参数名=参数值&参数名=参数值”
    • 注意,以 ?开头.参数名=参数值;多个参数以&分割; URL地址可以进行伪装(用户看到的假地址,服务器内部进行转换)
  • HTTP元数据
    • username=admin&password=123
  • HTTP状态号
    • http://www.
    • 200:成功
    • 301:重定向(当前页面已过时,跳转到新的页面)
    • 403:对被请求页面的访问被禁止
    • 404:服务器无法找到被请求的页面
    • 500:服务器内部错误
    • 502:服务器从上游服务器收到一个无效的响应(访问量过大,不能提供服务的就会收到)
  • HTTP请求类型(GET和POST的区别)
    • 请求头:客户端向服务器发送数据的报(数据报)头
    • 响应头:服务器向客户端发送回来的报头
    • Get和Post:Get的数据是通过URL地址传递的,Post的数据是通过HTTP数据头传递的
    • 区别
      • Get传递的数据会被浏览器和搜索引擎记录,不安全(被记录)
      • Post传递的数据,记录在请求头部中,相对安全.
    • URLEncode
      • 如果需要在URL传递数据中假如特殊字符,就需要对数据进行URL编码.
网络请求Get和Post

在这里插入图片描述

3.TCP长连接

1)作用

服务器主动发数据,频繁的进行数据交换,防止频繁的出现三次握手四次挥手

2)Socket套接字

将IP地址与主机端口号合并,基于传输层实现
手册:link .

3)TCP编程方法

日常所说的G,M指的是数据的容量
4个字节byte,32位bit
UTF-8编码是1~6个字节存储,中文3个字节.
B:一个字节
KB:1024B
MB:1024KB
GB:1024MB
TB:1024GB
PB:1024TB
Byte

手册
https://msdn.micrisoft.com/zh-cn/

连接(三次握手)

  • 同步连接
    //创建套接字
    socket socket =new socket(addressfamily.internetwork,sockettype.stream,protocoltype.tcp)
    //创建连接方法
    socket.connect(“IP地址”,端口号)
  • 异步连接
    //创建套接字
    socket.beginconnect(host.port,endconnect,null);
    //endconnect回调函数中执行
    socket.endconnect(异步连接结果)

断开(四次挥手)

  • 同步
    socket.disconnect(false);
    soncket.close();

  • 异步
    socket.begindisconnect(false.enddisconnect,null)
    enddisconnect回调函数执行
    socket.enddisconnect(异步断开连接结果);
    socket.close();

监听绑定(服务器开发)
unity运行时跑的是C++代码
Bind()函数实现

接收

发送

4)数据包处理

在这里插入图片描述
字节序(数字存储问题)
大端字节序(头部放栈底):数据的前位,放在内存地址位,后位字节,放在高内存地址位(符合人类思维)
小端字节序(头部放栈顶):将数字的后位字节,放在内存栈的低地址位,前位字节放在高地址位(不符合人类思维方式)
主机字节序(跟硬件和操作系统有关):当前计算机数字的字节表示方式(硬件和操作系统有关)
网络字节序(大端字节序):无论主机是怎么样的字节序,互联网规定,传递数据时,都应该转换为大端字节序进行传递.
数据包定制分析
包头:记录有关于整个数据包的信息
包体:原始数据(需要加密,防止泄露,RC4算法)

  • 数据打包
    对原始数据,添加协议头的过程,就是数据打包的过程,前四个字节记录长度,拼接上到包体结束,成为一个数据包.

  • 数据解包
    接收到数据包时,读取包头,并根据包头记录信息,获取到包内的原始数据的过程,就解包

  • 数据分包,
    当接受到数据后,需要将每一个定制的数据包格式分离出来,缩写的代码就是分包代码,有时是硬件将数据包拼接在一起,有时是代码将数据包拼接在一起,拼接后的代码,发送效率更高.

  • 数据粘包
    发送数据前,
    一组数据包的生命周周期过程
    1.对原始数据打包

  1. 对多个数据包,粘包
  2. 套接字(连接,发送)
  3. 套接字(接收)
  4. 有可能同时接收到多个数据黏在一起,分包
  5. 取得单个数据的原始数据,解包
  6. 根据数据包,执行代码逻辑

心跳包
因为TCP是有连接的,所以必须在两个PC间,建立连接,但如果长时间连接,却又不发送数据,则会占用互联网的通信信道,就可能被网络的中间设备(路由器,防火墙)将网络连接断开,所以为了防止网络被断开,则需要两台计算机间,定期发送一些数据,这样的数据就是心跳数据.
网络延迟计算:-服务器返回心跳时间-客户端发送心跳时间

锁(lock)
当一个线程,锁住变量后,另一个线程,必须等待锁释放,才能继续操作变量,这样就能防止并行代码时,出现内存错误(读写 同时执行)

debug的性能问题
debug输出,会严重影响游戏的运行速度,所以应在正式出包前,将所有打印删除.

5)核心代码
创建socket
//创建Socket对象(TCP)
  _Connection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 //开始异步连接服务器
_Connection.BeginConnect(Host, port, _ConnectedThread, null);
接收
  _Connection.BeginReceive(_ReceiveBuffer, 0, _ReceiveBuffer.Length, SocketFlags.None, _ReceivedThread, null);
发送
  //开启异步线程发送数据
  _Connection.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, null, null);

思路与流程

HTTP核心

在这里插入图片描述
1.通常HTTP请求分为POST和GET请求,两者的差别上面已经介绍过了,给两种请求方式建立枚举,方便检索识别.
2.其实核心方法就一个,请求Request();方法的参数是自己设计的,但是得确保有必要的参数和C#的API参数对应.核心代码 UnityWebRequest engine =UnityWebRequest.Get(url); ,参数共计6个,①请求类型,②url码,③请求成功回调,④请求失败回调,⑤数据表单,⑥请求时间限制
3.假如是POST请求,数据表单不能为空,表单的格式是固定的,以键值对存在Dictionary<string, string> data,因为http的请求格式定好了, ?k=v&k=v的方式书写的.
4.另外在请求网络的时候,不要重复请求,给一个正在请求的标识符即可.
5.当前面的不符合请求规范的状况都避免时,开始协程正式请求,为什么用协程?因为防止被卡死.协程里的参数都是照搬过来的,依旧是6个参数 .接下来对此进行处理.
6.生成一个http请求UnityWebRequest ,如果是get请求下,数据表单不为空的话,得和url之间加一个?,这是php的格式需求. 数据表单在凭借到url之前得做处理,我们的输入是字典形式的数据,在 httputility类里面进行数据处理.
7.数据表单的数据怎么处理呢?就是把键值对用=号连起来,每组键值对之间用 &拼接,值得注意的是键值对的内容需要是对url格式友好的,所以使用API进行转换foreach (string k in data.Keys) { formData += k + "=" + UnityWebRequest.EscapeURL(data[k]) + "&"; } formData = formData.Substring(0, formData.Length - 1); ,转换完后的数据就是符合url需求的数据,等待和url拼接,请求.
8.如果是post请求, 使用一个WWWForm postData类对表单数据存储,wwwform.addfield(k,v),用这个表单数据助手类去处理表单数据,然后UnityWebRequest.Post(url, postData);,传入url和postData.
9.给网络请求对象,限制请求时间timeout,发送请求后把,正在请求标识符变为true, 然后等待发送请求,engine.SendWebRequest();,发送完请求后请求标识符改回来为false.
10.之后对 网络错误HTTP错误进行检验,engine.isNetworkError,engine.error检查错误信息,错误回调函数的参数是自己设计的,一个错误码,一个错误信息,一个url地址,一个表单数据,既然网络有错误,那把这个http请求对象处理掉engine.Dispose(); engine.isHttpError,HTTP的错误,服务器错误, 会有一个错误码engine.responseCode,还有服务器的返回值, engine.downloadHandler.text ,这是接收到的主体数据
11.当不存在错误时,把主题数据转成string,放到 成功请求的回调中.
12.案例使用的,话,只需要调用HttpDriver.Request(),传入相应的参数, 请求类型 ,url地址,注意get请求url是把数据表单合在一起的,这样不安全,post的则是通过表单助手类进行转换存储,每一次请求,会返回对应的回调,成功回调,失败回调等信息.

TCP核心

在这里插入图片描述

1.Tcp连接的时候,最终都是转成字节流传到服务器的,为了防止字节流被截获识别,应对数据添加 混淆码,我这里添加的是 消息号Code,一个int存储下来,然后消息体使用字节数组存储,一消息默认的就包含一个消息号和一个消息体.
2.然后打包和解包,这里是信息安全的关键,打包和解包都是对消息类的操作,先说打包,打包后是字节数组的形式,先看看消息体是不是空的,打好的包应该包括 消息头(占4个字节),消息号(占4个字节),消息体(实际为准),所以打包好的数据byte[] 长度应该是 8+消息体长度.值得注意的是,需要将主机字节序转换成网络字节序.转好字节序后,再用 bitconverter.将int转为 byte[].
3.来一个 byte的list集合,分别按时顺序把 包头,消息号,消息体加进来(AddRange()),最终又转成 byte[] ,值得注意的是AddRange()方法,不单单是该类型的数组,IEnumerable collection,而是可迭代类型都能用.
4.再来说说解包,解包的来源也是 byte[], 使用消息体类接收,从第5位开始,需要注意的是,传过来的字节,是网络字节序,先要转成主机字节序,用BitConverter.ToInt32(),来转换,为int,再把int转为主机字节序,好,把这个消息
5.再检验是否有消息体,判断byte[]的长度是否大于8,这个8是自己设计的,如果加入了混淆码,可能不止8的长度.,消息体的长度就是 传过来的长度-8.然后把数据拷贝下来,这样消息就拿到了.
6.TCP的状态,可分为 离线,开始异步连接,结束异步连接,在线,开始异步断开连接,结束异步断开连接.在不同状态下,执行不同指令.
7.Tcp的传动类,大概分为6大块,monoAPI,Connection,Receive,Send,Message,HeartBeaat. monoAPI制作单例,放入时刻更新方法,关闭时执行断开连接方法.
8.连接Connection, 只有在断开状态下,才连接

//创建Socket对象(TCP)
            _Connection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //开始异步连接服务器
            _Connection.BeginConnect(Host, port, _ConnectedThread, null);

确实网络类型,数据类型,连接类型, 创建了socket对象,开始异步连接,填入 主机地址,端口号,连接回调.开启异步连接后,把状态调整为开始异步连接状态.
9.异步连接回调是IAsyncResult的类型参数,经过这个流程_Connection.EndConnect(ar);开始异步连接结束,这时候才算连上哦,把状态调过来调成,异步连接结束.
10.断开服务器方法,需要在在线状态下执行,然后开启也是使用异步断开,
11.连接成功后才开始请求, 请求给一个容器,再给个缓存容器,这里的数据需要比较复杂的处理,还需要一个消息体的队列,到时候统一处理,开启线程进行接收,接收的API是写好的,需要byte[]数组,偏差位置,长度,过滤类型,接收回调,状态.
12.接收的回调会知道接收的内容有多少,假如接收的内容长度为0,则说明服务器主动断开了连接,把socket关闭置空,状态也调到异步断开结束.假如有内容,
13.缓存区域一开始就是为空,当为空的时候,我又分两种情况考虑,第一种是,先截取当前包的包头获得长度,假如这个长度小于等于剩余的数据量长度,说明这一次还分不完,把这部分的包装好放到消息类队列里面,然后回去继续分包,直到取得的包长度大于剩余的包长度,这种情况,开启一个缓存,缓存区域大小就是当前需要处理的包长度空间,这个临时包,岂不是不完整?
14.时刻监测接收数据,按消息号执行,时刻监测状态变化,在连接成功的时候执行回调,开始接收,马上跳转状态到在线,在断开连接的时候执行断开回调,并条状状态到离线.时刻发送心跳包,在线的状态才能发心跳包,每5秒给待发送的队列里面添加一个心跳包,而待发送的消息队列是时刻发送的,只要当前是在线的,而且不是正在发送的,有消息队列的情况就发,发的这个队列,先进行粘包,使用data.AddRange,粘好了之后开启异步线程,还给一个发送的回调函数,用这个回调来判断发送是否完成,_Connection.EndSend(ar);_Sending = false;给的异步回调函数,是很重要的,需要拿到信息判断当前的执行是否完成了,
15.我们说 一个包可能装有若干条消息,由于这个包的大小是自己定义的,消息大小也是不定的,所以,是若干条消息.最完美的情况就是 一个包刚好装有几个整条的消息,这将截取消息将是完整的,不用额外处理的, 但是除此之外,可能一个包装有2条(整数条也可能0条)消息再加半条(不完整的)消息,最后一条消息不完整,这是就需要缓存消息,等下一个包过来,再接收,而这个新包体,也同样会有前一个包的情况,我上一个缓存的消息,到这个包了,还没给装完,那就得继续接收新包体,直到消息接收完整.
在这里插入图片描述

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值