网络通信
网络游戏通信方案概述
弱联网和强联网游戏
网络游戏是以C/S模型为基础进行开发的由客户端和服务端组成
弱联网游戏:
这种游戏不会频繁的进行数据通信,客户端和服务端之间每次连接只处理一次请求,服务端处理完客户端的请求后返回数据后就断开连接了
强联网游戏:
这种游戏会频繁的和服务端进行通信,会一直和服务端保持连接状态,不停的和服务器之间交换数据通过之前的知识我们知道,网络游戏是以C/S模型为基础进行开发的由客户端和服务端组成
弱联网游戏代表:
一般的三消类休闲游戏、卡牌游戏等都会是弱联网游戏,这些游戏的核心玩法都由客户端完成,客户端处理完成后只是告诉服务端一个结果,服务端验证结果即可,不需要随时通信
比如:开心消消乐、刀塔传奇、我叫MT等等
强联网游戏代表:
一般的MMORPG(角色扮演)、MOBA(多人在线竞技游戏)、ACT(动作游戏)等等都会是强联网游戏,这些游戏的部分核心逻辑是由服务端进行处理,客户端和服务端之间不停的在同步信息
比如:王者荣耀、守望先锋、和平精英等等
长连接和短连接游戏
长连接和短连接游戏是按照网络游戏通信特点来划分的
弱联网游戏——>短连接游戏
强联网游戏——>长连接游戏
短连接游戏:
需要传输数据时,建立连接,传输数据,获得响应,断开连接
通信特点:需要通信时再连接,通信完毕断开连接
通信方式:HTTP超文本传输协议、HTTPS安全的超文本传输协议(他们本质上是TCP协议)
长连接游戏:不管是否需要传输数据,客户端与服务器一直处于连接状态,除非一端主动断开,或
者出现意外情况(客户端关闭或服务端崩溃等)
通信特点:连接一直建立,可以实时的传输数据
通信方式:TCP传输控制协议 或 UDP用户数据报协议
Socket、HTTP、FTP
Socket:网络套接字,是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象,一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制
主要用于制作长连接游戏(强联网游戏)
Http/Https:(安全的)超文本传输协议,是一个简单的请求-响应协议,它通常运行在TCP协议之上,它指定了客户端可能发送给服务端什么样的信息以及得到什么样的响应。
主要用于制作短连接游戏(弱联网游戏),也可以用来进行资源下载
FTP:文件传输协议,是用于在网络上进行文件传输的一套标准协议,可以利用它来进行网络上资源的下载和上传。它也是基于TCP的传输,是面向连接的,为文件传输提供了可靠的保证
网络通信基础
IP地址和端口类
#region IPAddress类
//命名空间:System.Net;
//类名:IPAddress
//初始化IP信息的方式
//1.用byte数组进行初始化
byte[] ipAddress = new byte[] {
118, 102, 111, 11 };
IPAddress ip1 = new IPAddress(ipAddress);
//2.用long长整型进行初始化
//4字节对应的长整型 一般不建议大家使用
IPAddress ip2 = new IPAddress(0x79666F0B);
//3.推荐使用的方式 使用字符串转换
IPAddress ip3 = IPAddress.Parse("118.102.111.11");
//特殊IP地址
//127.0.0.1代表本机地址
//一些静态成员
//获取可用的IPv6地址
//IPAddress.IPv6Any
#endregion
#region IPEndPoint类
//命名空间:System.Net;
//类名:IPEndPoint
//IPEndPoint类将网络端点表示为IP地址和端口号,表现为IP地址和端口号的组合
//初始化方式
IPEndPoint ipPoint = new IPEndPoint(0x79666F0B, 8080);
IPEndPoint ipPoint2 = new IPEndPoint(IPAddress.Parse("118.102.111.11"), 8080);
#endregion
#region 总结
//程序表示IP信息
IPAddress ip = IPAddress.Parse("IPv4地址");
//程序表示通信目标
IPEndPoint point = new IPEndPoint(ip, 8080);
#endregion
域名解析
域名解析也叫域名指向、服务器设置、域名配置以及反向IP登记等等,说得简单点就是将好记的域名解析成IP,IP地址是网络上标识站点的数字地址,但是IP地址相对来说记忆困难,所以为了方便记忆,采用域名来代替IP地址标识站点地址。
比如 我们要登录一个网页 www.baidu.com 这个就是域名 我们可以通过记忆域名来记忆一个远端服务器的地址,而不是记录一个复杂的IP地址
域名解析就是域名到IP地址的转换过程。域名的解析工作由DNS服务器完成,我们在进行通信时有时会有需求通过域名获取IP
域名系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务,它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网,是因特网上解决网上机器命名的一种系统,因为IP地址记忆不方便,就采用了域名系统来管理名字和IP的对应关系
#region IPHostEntry类
//命名空间:System.Net
//类名:IPHostEntry
//主要作用:域名解析后的返回值 可以通过该对象获取IP地址、主机名等等信息
//该类不会自己声明,都是作为某些方法的返回值返回信息,我们主要通过该类对象获取返回的信息
//获取关联IP 成员变量:AddressList
//获取主机别名列表 成员变量:Aliases
//获取DNS名称 成员变量:HostName
#endregion
#region Dns类
//命名空间:System.Net
//类名:Dns
//主要作用:Dns是一个静态类,提供了很多静态方法,可以使用它来根据域名获取IP地址
//常用方法
//1.获取本地系统的主机名
print(Dns.GetHostName());
//2.获取指定域名的IP信息
//根据域名获取
//同步获取
//注意:由于获取远程主机信息是需要进行网路通信,所以可能会阻塞主线程
IPHostEntry entry = Dns.GetHostEntry("www.baidu.com");
for (int i = 0; i < entry.AddressList.Length; i++)
{
print("IP地址:" + entry.AddressList[i]);
}
for (int i = 0; i < entry.Aliases.Length; i++)
{
print("主机别名" + entry.Aliases[i]);
}
print("DNS服务器名称" + entry.HostName);
//异步获取
GetHostEntry();
#endregion
如果不知道对方的IP地址,想通过域名和对方进行通信,可以通过Dns类通过域名得到IP地址后再和对方建立连接并通信
序列化和反序列化2进制数据
网络通信中传输的数据
在网络通信中
把想要传递的类对象信息序列化为2进制数据(一般为byte字节数组)
再将该2进制数据通过网络传输给远端设备
远端设备获取到该2进制数据后再将其反序列化为对应的类对象
序列化:
将类对象信息转换为可保存或传输的格式的过程
反序列化:
与序列化相对,将保存或传输过来的格式转换为类对象的过程
比如
将C#类对象序列化为xml、json、2进制三种格式的数据保存在本地,达到持久化的目的,再将保存在本地的持久化数据文件反序列化为C#类对象
字符编码
字符编码(英语:Character encoding)也称字集码
是把字符集中的字符,编码为指定集合中某一对象,以便文本在计算机中存储或通过网络进行传递。
**说人话:计算机里只能存数字(2机制),所以如果文字字符想要进行存储的话,就需要把对应的文字字符转换为数字才能进行处理,而字符编码就是文字字符在计算机中和数值的对应关系,是人为定义的一种映射规则。**比如
ASCII码(一种字符编码规则)中 数值65 用来映射 字符A
我们存储 A 这个字符,本质上存在内存中的是数值65对应的2进制是0100 0001
常见的一些字符编码规则有
每个国家针对自己国家语言制定的编码规则(因为语言文字的数量各不相同)
ASCII码(美国)、GB2312编码(中国)、Shift_JIS编码(日本)、Euc-kr(韩国)等等
世界通用的编码规则(把所有语言统一到一套编码里)
Unicode编码 以及 基于Unicode实现的编码规则
UTF-8、UTF-16、UTF-32
乱码
假设我们的一个文件是采用中国制定的GB2312编码进行编辑存储的,而此时我们并不使用GB2312这个编码规则去读取文件,而是采用其它的规则,比如日本制定的Shift_JIS编码读取该文件,那么由于编码格式的存读不统一就会造成乱码的出现。因为不同的编码规则,字符和数值的映射关系是不同的。
比如:130这个数值在法语编码中代表了é
,在希伯来语编码中却代表了字母Gimel
(ג
),在中文编码中又会代表另一个符号。
ASCII码
在计算机内部,所有的信息最终都是一个二进制值。
一个二进制数就是一位(bit),有0和1两种状态。一个字节(byte)是8个二进制数组成的,所以1 byte = 8 bit。
也就是说一个字节一共可以用来表示256种不同的状态,从00000000到11111111。如果每一个状态都代表一个符号的话,那么一个字节可以用来表示256个符号。
上个世界60年代,美国制定了一套算是最早的字符编码,制定了一套基于英文字符与二进制位之间的对应关系。这套标准被称为ASCII码,一直使用到今天。
ASCII码一共规定了128个字符的编码。比如:
字符 A = 65(十进制数)= 0100 0001(二进制数)
字符 1 = 49(十进制数)= 0011 0001(二进制数)等等
下图为ASCII码的对照表
这128个字符的编码规则,只占用了一个字节的后面7位,最前面的一位统一规定为0。
英文国家使用128个字符编码就足够了,但是如果用来表示其它国家的语言,128个符号是远远不够的,于是乎就出现了非ASCII码们。
非ASCII码
非ASCII码基本都是基于ASCII码进行的扩充,他们都保留了ASCII码0~127这段编码的规范。也就是说非ASCII码的前面部分往往是和ASCII码的规则是相同的。
对于一些欧洲国家,他们使用一个字节便可以表示完自己所有的文字,他们利用了字节中闲置的最高位编入新的符号,因为我们知道ASCII码的编码规则是:一个字节中的8位,只占用了一个字节的后面7位,最前面的一位统一规定为0。所以这些语言系统中文字较少的国家让最前面的一位可以为1,他们就可以为自己的文字在128~255这一段加入新的对应规则。
比如:130这个数值在法语编码中代表了é
,在希伯来语编码中却代表了字母Gimel
(ג
)
这种一个字节就把字符表示完的做法只适用于语言系统中文字较少的国家,因为他们的语言的字母是有限的。
对于使用象形文字的国家来说,一个字节完全不够用!比如中国,我们的汉字多达10万左右,一个字节最多也只能表示256种符号,是远远不够的。所以必须使用多个字节来表示一个符号。比如我们前面提到的中国的简体中文GB2312编码,是使用两个字节表示一个汉字,所以理论上来说可以表示256x256=65536个符号。
所以所谓的非ASCII码,就是指的除了ASCII码以外的编码格式,每个国家都至少有1种针对自己语言文字的编码格式,每一个编码格式中 数值和字符的对应关系都可能不相同。这也就造成了前面说到的乱码问题。在全世界范围内进行网络通信时,如果每个国家都使用不统一的编码格式,那么出现乱码的情况将随处可见。
因此随着互联网的发展,人们决定要制定一套全世界统一的的编码规则,将世界上所有的符号都纳入其中,为每一个符号赋予独一无二的编码(2进制数值)。那么这样就不会出现乱码问题,影响信息的传递了。
Unicode
Unicode可以理解为是 Unique Code 的简写,翻译过来是“唯一的编码”。
它出现的主要原因就是用来解决乱码问题的,它将世界上所有的符号都纳入其中,每一个符号都为其分配一个独一无二的二进制数表示它,那么乱码问题就会消失。
Unicode是一个很大的集合,现在的规模可以容纳100多万个符号,每个符号对应的二进制数都不一样。这样就确保了不同语言的字符不会再有冲突。
那么这样可能就存在一个问题,就是有的符号用1个字节8位就可以表示了,有的符号可能需要使用2个字节16位甚至3个字节24位才能表示。就比如说ASCII码,它的存储规则就是一个字节存储一个字符,那么当我们使用Unicode编码时,到底用几个字节来存储字符呢?
因此我们需要注意:Unicode编码只是一个符号集,它只规定符号和二进制的对应关系,并没有规定这个二进制数值应该如何存储。
而UTF-8、UTF-16、UTF-32三种编码格式才是基于Unicode实现的具体编码方案
UTF-8编码:可变字节编码方案,可以根据实际情况使用1个、2个、3个、4个字节来存储字符
UTF-16编码:可变字节编码方案,可以根据实际情况使用2个、4个字节来存储字符
UTF-32编码:固定字节编码方案,用4个字节来存储字符
UTF-8
UTF-8是Unicode的实现方式之一,它的最大特点是:它是一种变长的编码方式,可以使用1~4个字节表示一个字符,根据不同的符号而变化字节的长度。
Unicode是世界上所有符号对应二进制数据的关系集合
UTF-8是Unicode的实现方式之一:UTF-8 = Unicode符号集 + 变长的编码规则
序列化
#region 非字符串类型转字节数组
//关键类:BitConverter
//所在命名空间:System
//主要作用:除字符串的其它常用类型和字节数组相互转换
byte[] bytes = BitConverter.GetBytes(1);
#endregion
#region 字符串类型转字节数组
//关键类:Encoding
//所在命名空间:System.Text
//主要作用:将字符串类型和字节数组相互转换,并且决定转换时使用的字符编码类型,网络通信时建议大家使用UTF-8类型
byte[] byte2 = Encoding.UTF8.GetBytes("的卡萨福利卡决胜巅峰卡视角的副驾驶的");
#endregion
#region 如何将一个类对象转换为二进制
//注意:网络通信中我们不能直接使用数据持久化2进制知识点中的
//BinaryFormatter 2进制格式化类
//因为客户端和服务器使用的语言可能不一样,BinaryFormatter是C#的序列化规则,和其它语言之间的兼容性不好
//如果使用它,那么其它语言开发的服务器无法对其进行反序列化
//我们需要自己来处理将类对象数据序列化为字节数组
//单纯的转换一个变量为字节数组非常的简单
//但是我们如何将一个类对象携带的所有信息放入到一个字节数组中呢
//我们需要做以下几步
//1.明确字节数组的容量(注意:在确定字符串字节长度时要考虑解析时如何处理)
PlayerInfo info = new PlayerInfo();
info.lev = 10;
info.name = "刘英博";
info.atk = 88;
info.sex = false;
//得到的 这个Info数据 如果转换成 字节数组 那么字节数组容器需要的容量
int indexNum = sizeof(int) + //lev int类型 4
sizeof(int) + //代表 name字符串转换成字节数组后 数组的长度 4
Encoding.UTF8.GetBytes(info.name).Length + //字符串具体字节数组的长度
sizeof(short) + //atk short类型 2
sizeof(bool); //sex bool类型 1
//2.申明一个装载信息的字节数组容器
byte[] playerBytes = new byte[indexNum];
//3.将对象中的所有信息转为字节数组并放入该容器当中(可以利用数组中的CopeTo方法转存字节数组)
//CopyTo方法的第二个参数代表 从容器的第几个位置开始存储
int index = 0;//从 playerBytes数组中的第几个位置去存储数据
//等级
BitConverter.GetBytes(info.lev).CopyTo(playerBytes, index);
index += sizeof(int);
//姓名
byte[] strBytes = Encoding.UTF8.GetBytes(info.name);
int num = strBytes.Length;
//存储的是姓名转换成字节数组后 字节数组的长度
BitConverter.GetBytes(num).CopyTo(playerBytes, index);
index += sizeof(int);
//存储字符串的字节数组
strBytes.CopyTo(playerBytes, index);
index += num;
//攻击力
BitConverter.GetBytes(info.atk).CopyTo(playerBytes, index);
index += sizeof(short);
//性别
BitConverter.GetBytes(info.sex).CopyTo(playerBytes, index);
index += sizeof(bool);
#endregion
BitConverter转换非字符串的类型的变量为字节数组
Encoding.UTF8转换字符串类型的变量为字节数组(注意:为了考虑反序列化,我们在转存2进制,序列化字符串之前,先序列化字符串字节数组的长度)
反序列化
#region 字节数组转非字符串类型
//关键类:BitConverter
//所在命名空间:System
//主要作用:除字符串的其它常用类型和字节数组相互转换
byte[] bytes = BitConverter.GetBytes(99);
int i = BitConverter.ToInt32(bytes, 0);
print(i);
#endregion
#region 字节数组转字符串类型
//关键类:Encoding
//所在命名空间:System.Text
//主要作用:将字符串类型和字节数组相互转换,并且决定转换时使用的字符编码类型,网络通信时建议大家使用UTF-8类型
byte[] bytes2 = Encoding.UTF8.GetBytes("123123空间大撒了房间阿斯利康放大镜");
string str = Encoding.UTF8.GetString(bytes2, 0, bytes2.Length<