目录
1.网络通信-通信必备知识-IP地址和端口类
分类 | 详情 |
---|---|
IPAddress类 | 命名空间:System.Net |
IPEndPoint类 | 命名空间:System.Net |
IPHostEntry | 作为域名解析返回值,用于获取IP、主机名等信息。成员变量有:AddressList(获取关联IP)、Aliases(获取主机别名列表)、HostName(获取DNS名称) |
Dns | 静态类,提供根据域名获取IP的方法。常用方法有:GetHostName(获取本地主机名,如 |
2.网络通信中序列化和反序列化2进制数据
类名 | 详情 |
---|---|
BitConverter | 所在命名空间:System |
Encoding | 所在命名空间:System.Text |
3.Socket类
类名 | 详情 |
---|---|
Socket | 命名空间:System.Net.Sockets |
4.TCP同步服务端和客户端基础实现
4.1.服务端基本实现
(1)创建套接字Socket对象(TCP):
//1.创建套接字Socket(TCP)
Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
(2)用Bind方法将套接字与本地地址绑定:
//2.用Bind方法将套接字与本地地址绑定
try
{
IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);//把本机作为服务端程序 IP地址传入本机
socketTcp.Bind(iPEndPoint);//绑定
}
catch (Exception e)
{
//如果IP地址不合法或者端口号被占用可能报错
Console.WriteLine("绑定报错" + e.Message);
return;
}
(3)用Listen方法监听:
//3.用Listen方法监听
socketTcp.Listen(1024);//最大接收1024个客户端
Console.WriteLine("服务端绑定监听结束,等待客户端连入");
(4)用Accept方法等待客户端连接,建立连接,Accept返回新套接字:
//5.建立连接,Accept返回新套接字
Socket socketClient = socketTcp.Accept();
//Accept是阻塞式的方法 会把主线程卡主 一定要等到客户端接入后才会继续执行后面的代码
//客户端接入后 返回新的Socket对象 这个新的Socket可以理解为客户段和服务端的通信通道
Console.WriteLine("有客户端连入了");
(5)用Send和Receive相关方法收发数据:
//6.用Send和Receive相关方法收发数据
//发送字符串转成的字节数组给客户端
socketClient.Send(Encoding.UTF8.GetBytes("欢迎连入服务端"));
//声明接受客户端信息的字节数组 声明1024容量代表能接受1kb的信息
byte[] result = new byte[1024];
//接受客户端信息 返回值为接受到的字节数
int receiveNum = socketClient.Receive(result);
//打印 远程发送信息的客户端的IP和端口 以及 发送过来的字符串
Console.WriteLine("接受到了{0}发来的消息:{1}",
socketClient.RemoteEndPoint.ToString(),
Encoding.UTF8.GetString(result, 0, receiveNum));
(6)用Shutdown方法释放连接:
//7.用Shutdown方法释放连接
//注意断开的是客户段和服务端的通信通道
socketClient.Shutdown(SocketShutdown.Both);
(7)关闭套接字:
//8.关闭套接字
//注意关闭的是客户段和服务端的通信通道
socketClient.Close();
4.2.客户端实现:
(1)创建套接字Socket Tcp
Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
(2)用Connect方法与服务端相连:
//确定服务端的IP和端口 正常来说填的应该是远端服务器的ip地址以及端口号
//由于只有一台电脑用于测试 本机也当做服务器 所以传入当前电脑的ip地址
IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
try
{
//连接
socketTcp.Connect(iPEndPoint);
}
catch (SocketException e)
{
//如果连接没有开启或者服务器异常 会报错 不同的返回码代表不同报错
if (e.ErrorCode == 10061)
print("服务器拒绝连接");
else
print("连接服务器失败" + e.ErrorCode);
return;
}
(3)用Send和Receive相关方法收发数据:
//接收数据
//声明接收数据字节数组
byte[] receiveBytes = new byte[1024];
//Receive方法接受数据 返回接收多少字节
int receiveNum = socketTcp.Receive(receiveBytes);
print("收到服务端发来的消息:" + Encoding.UTF8.GetString(receiveBytes, 0, receiveNum));
//发送数据
socketTcp.Send(Encoding.UTF8.GetBytes("你好,我是韬老狮的客户端"));
(4)用Shutdown方法释放连接:
//4.用Shutdown方法释放连接
socketTcp.Shutdown(SocketShutdown.Both);
(5)关闭套接字:
//5.关闭套接字
socketTcp.Close();
5.区分消息类型
为发送的信息添加标识,比如添加消息 ID。在所有发送的消息的头部加上消息 ID(可以是 int、short、byte、long)。
举例说明:
如果选用 int 类型作为消息 ID 的类型,前 4 个字节为消息 ID,后面的字节为数据类的内容,这样每次收到消息时,先把前 4 个字节取出来解析为消息 ID,再根据 ID 进行消息反序列化即可。
6.分包和粘包
什么是分包、黏包?
分包、黏包指在网络通信中由于各种因素(网络环境、API规则等)造成的消息与消息之间出现的两种状态。
分包:一个消息分成了多个消息进行发送。
黏包:一个消息和另一个消息黏在了一起。
注意:分包和黏包可能同时发生。
解决思路:为消息添加头部记录长度,依据长度判断并处理分包、黏包,仅处理完整消息。
7.TCP同步退出消息和心跳消息
7.1.客服端主动断开连接
- 解决思路及主要修改逻辑
- 客户端:使用
Disconnect
方法主动断开连接,在TcpNetManager
的Close
方法中,依次执行关闭套接字发送和接收、手动停止连接、关闭套接字连接、将套接字置空以及标记连接已关闭等操作。 - 服务端
- 在
ServerSocket
中添加CloseClientSocket
方法,用于关闭客户端连接并从字典中移除,操作客户端对象字典时添加线程锁以保证线程安全。 - 在
ServerSocket
的其他操作或访问字典的方法(如AcceptClientConnect
、ReceiveClientMessage
、Broadcast
)中添加线程锁,确保字典操作的线程安全性。 - 为避免在遍历字典进行消息收发操作时直接移除客户端导致报错,在
ServerSocket
中创建List
存储待移除的客户端socket(delList
),并定义AddDelSocket
方法用于添加待移除的socket。 - 将
Program
的serverSocket
改为静态变量,在ClientSocket
的发送和接收消息方法中,检测到断开连接或解析报错时,将当前客户端添加到服务端待移除的客户端列表中。
- 在
- 客户端:使用
7.2.心跳消息
什么是心跳消息
所谓心跳消息,就是在长连接中,客户端和服务端之间定期发送的一种特殊的数据包,用于通知对方自己还在线,以确保长连接的有效性。由于其发送的时间间隔往往是固定的持续的,就像是心跳一样一直存在,所以我们称之为心跳消息。
为什么需要心跳消息
避免非正常关闭客户端时,服务器无法正常收到关闭连接消息。通过心跳消息我们可以自定义超时判断,如果超时没有收到客户端消息,证明客户端已经断开连接。避免客户端长期不发送消息,防火墙或者路由器会断开连接,我们可以通过心跳消息一直保持活跃状态。
实现心跳消息
客户端:主要功能是定时发送消息。
服务器端:主要功能是不停检测上次收到某客户端消息的时间,如果超时则认为连接已经断开。
8.Socket类TCP异步常用成员
类名 | 方法 | 参数 | 详情 |
---|---|---|---|
Socket | BeginAccept(AsyncCallback callback, object state) | callback :处理异步接受客户端连接操作完成时的回调函数;state :服务端Socket 对象 | 服务端开始接收客户端的连接。 |
Socket | EndAccept(IAsyncResult asyncResult) | asyncResult :BeginAccept 方法返回的IAsyncResult 对象 | 服务端检测到客户端的连接结束,得到客户端Socket 。 |
Socket | BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state) | remoteEP :服务器的终结点(如IPEndPoint );callback :处理客户端异步连接操作完成时的回调函数;state :客户端的Socket 对象 | 客户端异步连接到服务器。 |
Socket | EndConnect(IAsyncResult asyncResult) | asyncResult :BeginConnect 方法返回的IAsyncResult 对象 | 客户端完成异步连接到服务器端的操作。 |
Socket | BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state) | buffer :接收消息的字节数组;offset :从接收消息的字节数组第几位开始存储;size :字节数组长度;socketFlags :Socket 标识;callback :接收消息回调函数;state :Socket 对象 | 开始接收消息。 |
Socket | EndReceive(IAsyncResult asyncResult) | asyncResult :BeginReceive 方法返回的IAsyncResult 对象 | 结束接收消息,返回接收的字节数组长度。 |
Socket | BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state) | buffer :发送消息的字节数组;offset :从发送消息的字节数组第几位开始发送;size :字节数组长度;socketFlags :Socket 标识;callback :发送消息回调函数;state :Socket 对象 | 开始发送消息。 |
Socket | EndSend(IAsyncResult asyncResult) | asyncResult :BeginSend 方法返回的IAsyncResult 对象 | 结束发送消息,返回成功发送的字节数。 |
Socket | AcceptAsync(SocketAsyncEventArgs e) | e :SocketAsyncEventArgs 对象,用于处理异步接受客户端连接操作完成后的操作 | 服务端异步接受客户端连接。 |
Socket | ConnectAsync(SocketAsyncEventArgs e) | e :SocketAsyncEventArgs 对象,用于处理异步连接到服务器操作完成后的操作 | 客户端异步连接到服务器。 |
Socket | SendAsync(SocketAsyncEventArgs e) | e :SocketAsyncEventArgs 对象,设置好发送数据的缓冲区等信息,用于处理异步发送消息操作完成后的操作 | 异步发送消息。 |
Socket | ReceiveAsync(SocketAsyncEventArgs e) | e :SocketAsyncEventArgs 对象,设置好接收数据的缓冲区等信息,用于处理异步接受消息操作完成后的操作 | 异步接受消息。 |
9.UDP
实现UDP客户端通信收发字符串
//1.创建套接字 寻址类型还是Ipv4 Soket类型使用数据包 协议选择Udp
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//2.绑定本机地址 注意模拟测试时客户端本机和服务端远程机的端口号不能一样
IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
socket.Bind(ipPoint);
//3.发送到指定目标 注意模拟测试时客户端本机和服务端远程机的端口号不能一样
IPEndPoint remoteIpPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
//指定要发送的字节数 和 远程计算机的 IP和端口
socket.SendTo(Encoding.UTF8.GetBytes("韬老狮来了"), remoteIpPoint);
//4.接受消息
//装接收消息的字节数组
byte[] bytes = new byte[512];
//装接收消息的IP地址和端口的对象 在ReceiveFrom方法会使用ref赋值 主要是用来记录 谁发的信息给你 传入函数后 在内部 它会帮助我们进行赋值
EndPoint remoteIpPoint2 = new IPEndPoint(IPAddress.Any, 0);
//接收消息 得到消息长度
int length = socket.ReceiveFrom(bytes, ref remoteIpPoint2);
print("IP:" + (remoteIpPoint2 as IPEndPoint).Address.ToString() +
"port:" + (remoteIpPoint2 as IPEndPoint).Port +
"发来了" +
Encoding.UTF8.GetString(bytes, 0, length));
//5.释放关闭
socket.Shutdown(SocketShutdown.Both);
socket.Close();
实现UDP服务端通信收发字符串
//1.创建套接字 寻址类型还是Ipv4 Soket类型使用数据包 协议选择Udp
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//2.绑定本机地址 注意模拟测试时客户端本机和服务端远程机的端口号不能一样
IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
socket.Bind(ipPoint);
Console.WriteLine("服务器开启");
//3.接受消息
//装接收消息的字节数组
byte[] bytes = new byte[512];
//装接收消息的IP地址和端口的对象 在ReceiveFrom方法会使用ref赋值 主要是用来记录 谁发的信息给你 传入函数后 在内部 它会帮助我们进行赋值
EndPoint remoteIpPoint2 = new IPEndPoint(IPAddress.Any, 0);
int length = socket.ReceiveFrom(bytes, ref remoteIpPoint2);
Console.WriteLine("IP:" + (remoteIpPoint2 as IPEndPoint).Address.ToString() +
"port:" + (remoteIpPoint2 as IPEndPoint).Port +
"发来了" +
Encoding.UTF8.GetString(bytes, 0, length));
//4.发送到指定目标
//由于服务端先收了消息 所以服务端已经知道谁发了消息给服务端 是使用remoteIpPoint2记录的 服务端直接发给它就行了
socket.SendTo(Encoding.UTF8.GetBytes("欢迎发送消息给服务器"), remoteIpPoint2);
//5.释放关闭
socket.Shutdown(SocketShutdown.Both);
socket.Close();
10.Socket类UDP异步常用方法
类名 | 方法 | 参数 | 详情 |
---|---|---|---|
Socket | BeginSendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP, AsyncCallback callback, object state) | buffer :要发送的数据字节数组;offset :从字节数组的哪个位置开始发送;size :发送数据的长度;socketFlags :SocketFlags 标识;remoteEP :目标IP和端口号;callback :回调函数;state :回调函数的参数 | 开始向指定IP和端口异步发送数据,将结果异步传回回调函数进行处理 |
Socket | EndSendTo(IAsyncResult asyncResult) | asyncResult :BeginSendTo 方法返回的IAsyncResult 对象 | 结束异步发送操作 |
Socket | BeginReceiveFrom(byte[] buffer, int offset, int size, SocketFlags socketFlags, ref EndPoint remoteEP, AsyncCallback callback, object state) | buffer :缓存区;offset :缓存区的起始位置;size :最大接收数据长度;socketFlags :SocketFlags 标识;remoteEP :接收数据的来源IPEndPoint ;callback :回调函数;state :回调函数的参数 | 开始异步从UDP客户端接收数据,将接收到的数据放入缓存区,接收数据的来源ip和端口号会被保存,有数据到达时调用回调函数处理 |
Socket | EndReceiveFrom(IAsyncResult asyncResult, ref EndPoint remoteEP) | asyncResult :BeginReceiveFrom 方法返回的IAsyncResult 对象;remoteEP :接收数据的来源IPEndPoint | 结束异步接收操作,并返回接收到的字节数 |
Socket | SendToAsync(SocketAsyncEventArgs e) | e :SocketAsyncEventArgs 对象,设置好要发送的数据、目标IP地址等信息 | 开始向指定IP和端口发送数据,发送完成后在回调函数处理 |
Socket | ReceiveFromAsync(SocketAsyncEventArgs e) | e :SocketAsyncEventArgs 对象,设置好接收数据的缓冲区、接收来源的IPEndPoint 等信息 | 开始异步从UDP客户端接收数据,接收完成后在回调函数进行处理 |
11.FTP
类名 | 用途/功能 | 重要方法 | 重要成员 |
---|---|---|---|
NetworkCredential (System.Net ) | 在网络身份验证中存储用户名和密码信息,用于 FTP 文件传输场景下提供访问 FTP 服务器所需的认证凭据 | 无 | 无 |
FtpWebRequest (System.Net ) | 用于执行上传、下载、删除 FTP 服务器上的文件等操作 | 1. Create :创建新 Ftp 请求。2. Abort :终止 Ftp 传输。3. GetRequestStream :获取用于上传的流。4. GetResponse :返回 FTP 服务器响应。 | 1. Credentials :设置通信凭证为NetworkCredential 对象。2. KeepAlive :设置完成请求时是否关闭到 FTP 服务器的控制连接。3. Method :设置 FTP 请求操作命令,如删除、下载、列目录等。4. UseBinary :指定是否采用二进制模式传输数据。5. RenameTo :重命名文件。 |
FtpWebResponse (System.Net ) | 封装 FTP 服务器对请求的响应,提供操作状态及从服务器下载的数据,用于表示响应信息,含响应代码、消息等 | 1. Close :释放所有资源。2. GetResponseStream :返回从 FTP 服务器下载数据的流。 | 1. ContentLength :接收到数据的长度。2. ContentType :接收数据的类型。3. StatusCode :FTP 服务器下发的最新状态码。4. StatusDescription :状态代码的文本描述。5. BannerMessage :登录前建立连接时服务器发送的消息。6. ExitMessage :获取 FTP 会话结束时服务器发送的消息。7. LastModified :获取 FTP 服务器上文件的最后修改日期和时间。 |
11.1.FTP上传文件
try
{
// 创建一个Ftp连接,pic.png代表想上传名叫pic的png图片。
// 这里的ftp://127.0.0.1是使用本机开启服务器进行测试,实际使用时应该是远端服务器IP。
FtpWebRequest ftpWebRequest = FtpWebRequest.Create(new Uri("ftp://127.0.0.1/pic.png")) as FtpWebRequest;
// 设置通信凭证(如果不支持匿名,就必须设置这一步)。
// 将代理相关信息置空,避免服务器同时有http相关服务造成冲突。
ftpWebRequest.Proxy = null;
// 创建并设置通信凭证。
NetworkCredential networkCredential = new NetworkCredential("MrTao", "MrTao");
ftpWebRequest.Credentials = networkCredential;
// 请求完毕后是否关闭控制连接,如果想要关闭,可以设置为false。
ftpWebRequest.KeepAlive = false;
// 设置操作命令。
ftpWebRequest.Method = WebRequestMethods.Ftp.UploadFile;//设置命令操作为上传文件。
// 指定传输类型,使用二进制。
ftpWebRequest.UseBinary = true;
// 得到用于上传的流对象。
Stream uploadRequestStream = ftpWebRequest.GetRequestStream();
// 开始上传,使用流读取StreamingAssets文件夹下的名叫test的图片。
using (FileStream fileStream = File.OpenRead(Application.streamingAssetsPath + "/test.png"))
{
// 我们可以一点一点的把这个文件中的字节数组读取出来,然后存入到上传流中。
byte[] bytes = new byte[1024];
// 返回值是真正从文件中读了多少个字节。
int contentLength = fileStream.Read(bytes, 0, bytes.Length);
// 不停的去读取文件中的字节,除非读取完毕了,不然一直读,并且写入到上传流中。
while (contentLength != 0)
{
// 写入上传流中。
uploadRequestStream.Write(bytes, 0, contentLength);
// 写完了继续读。
contentLength = fileStream.Read(bytes, 0, bytes.Length);
}
// 出了循环就证明写完了。
fileStream.Close();
uploadRequestStream.Close();
// 上传完毕。
print("上传结束");
}
}
catch (Exception e)
{
print("上传出错,失败" + e.Message);
}
11.2.FTP下载文件
try
{
// 创建一个Ftp连接。
// 这里和上传不同,上传的文件名是自己定义的,下载的文件名一定是资源服务器上有的,比如一张叫pic的图片。
FtpWebRequest ftpWebRequest = FtpWebRequest.Create(new Uri("ftp://127.0.0.1/pic.png")) as FtpWebRequest;
// 设置通信凭证(如果不支持匿名,就必须设置这一步)。
ftpWebRequest.Credentials = new NetworkCredential("MrTao", "MrTao");
// 请求完毕后是否关闭控制连接,如果要进行多次操作,可以设置为false。
ftpWebRequest.KeepAlive = false;
// 设置操作命令。
ftpWebRequest.Method = WebRequestMethods.Ftp.DownloadFile;
// 指定传输类型。
ftpWebRequest.UseBinary = true;
// 代理设置为空。
ftpWebRequest.Proxy = null;
// 得到用于下载的流对象。
// 相当于把请求发送给FTP服务器,返回值就会携带我们想要的信息。
FtpWebResponse ftpWebResponse = ftpWebRequest.GetResponse() as FtpWebResponse;
// 这就是下载的流。
Stream downloadResponseStream = ftpWebResponse.GetResponseStream();
// 开始下载。
print(Application.persistentDataPath);
using (FileStream fileStream = File.Create(Application.persistentDataPath + "/pic2.png"))
{
// 读取流的字节数组。
byte[] bytes = new byte[1024];
// 读取下载下来的流数据。
int contentLength = downloadResponseStream.Read(bytes, 0, bytes.Length);
// 一点一点的下载到本地流中。
while (contentLength != 0)
{
// 把读取出来的字节数组写入到本地文件流中。
fileStream.Write(bytes, 0, contentLength);
// 继续读。
contentLength = downloadResponseStream.Read(bytes, 0, bytes.Length);
}
// 下载结束,关闭流。
downloadResponseStream.Close();
fileStream.Close();
}
print("下载结束");
}
catch (Exception e)
{
print("下载出错" + e.Message);
}
12.HTTP
类名(命名空间) | 用途 | 重要方法 | 重要成员 |
---|---|---|---|
HttpWebRequest(System.Net) | 用于向服务器发送HTTP客户端请求,进行消息通信、上传、下载等操作 | 1. Create:创建新的WebRequest,用于HTTP相关操作 2. Abort:终止文件传输 3. GetRequestStream:获取用于上传的流 4. GetResponse:返回HTTP服务器响应 5. Begin/EndGetRequestStream:异步获取用于上传的流 6. Begin/EndGetResponse:异步获取返回的HTTP服务器响应 | 1. Credentials:通信凭证,设置为NetworkCredential对象 2. PreAuthenticate:是否随请求发送一个身份验证标头,一般需身份验证时设为true 3. Headers:构成标头的名称/值对的集合 4. ContentLength:发送信息的字节数,上传信息时需先设置 5. ContentType:POST请求时,需对发送内容进行内容类型设置 6. Method:操作命令设置,如Get、Post、Head等 |
HttpWebResponse(System.Net) | 用于获取服务器反馈信息,通过HttpWebRequest对象的GetResponse()方法获取,使用完毕用Close释放 | 1. Close:释放所有资源 2. GetResponseStream:返回从FTP服务器下载数据的流 | 1. ContentLength:接受到数据的长度 2. ContentType:接受数据的类型 3. StatusCode:HTTP服务器下发的最新状态码 4. StatusDescription:HTTP服务器下发的状态代码的文本 5. BannerMessage:登录前建立连接时HTTP服务器发送的消息 6. ExitMessage:HTTP会话结束时服务器发送的消息 7. LastModified:HTTP服务器上的文件的上次修改日期和时间 |
NetworkCredential、Uri、Stream、FileStream(相关命名空间) | 在HTTP通讯时使用方式与FTP类似 | - | - |
13.WWW
类名(命名空间) | 作用 | 常用方法 | 常用变量 |
---|---|---|---|
WWW(UnityEngine) | 用于简单访问网页,可进行数据的下载和上传。使用 HTTP 协议时默认请求类型是 Get,进行 Post 上传需配合 WWWForm 类 | 1. WWW:构造函数,用于创建一个 WWW 请求 2. GetAudioClip:从下载数据返回一个音效切片 AudioClip 对象 3. LoadImageIntoTexture:用下载数据中的图像来替换现有的一个 Texture2D 对象 4. LoadFromCacheOrDownload:从缓存加载 AB 包对象,如果该包不在缓存则自动下载存储到缓存中,以便以后直接从本地缓存中加载 | 1. assetBundle:如果加载的数据是 AssetBundle,则可直接获取加载结果 2. audioClip:如果加载的数据是音效切片文件,可直接获取加载结果(新版本用 GetAudioClip 方法) 3. bytes:以字节数组形式获取加载内容 4. bytesDownloaded:已下载的字节数 5. error:下载出错时返回错误消息 6. isDone:判断下载是否完成 7. movie:下载视频时可获取 MovieTexture 类型结果(新版本用 GetMovieTexture 方法) 8. progress:下载进度 9. text:下载数据为字符串时,以字符串形式返回内容 10. texture:下载数据为图片时,以 Texture2D 形式返回加载结果 |
14.WWWForm
类名 | 作用 | 注意事项 | 常用方法 |
---|---|---|---|
WWWForm | 在使用 WWW 类下载数据的基础上,若需上传数据,可结合该类使用。主要用于集成数据,可设置上传的参数或者二进制数据,结合使用时主要用 Post 请求类型,通过 Http 协议上传处理 | 使用 WWW 结合 WWWForm 上传数据通常需要与后端程序制定上传规则 | 1. WWWForm:构造函数 2. AddBinaryData:添加二进制数据 3. AddField:添加字段 |
15.UnityWebRequest
15.1.常用上传和获取数据
获取
类名 | 方法 | 方法解析 | 参数 |
---|---|---|---|
UnityWebRequest | Get | 创建一个用于获取文本或二进制数据的 UnityWebRequest 对象。 | uri :请求资源的 URI 地址,如 "http://192.168.1.101:8000/HTTPRoot/test.txt" |
UnityWebRequestTexture | GetTexture | 创建一个用于获取纹理数据的 UnityWebRequest 对象。 | uri :请求纹理资源的 URI 地址,可以是 http 、ftp 或 file 协议的地址,如 "http://192.168.1.101:8000/HTTPRoot/pic.png" 、"ftp://127.0.0.1/pic.png" 或 "file://" + Application.streamingAssetsPath + "/test.png" |
UnityWebRequestAssetBundle | GetAssetBundle | 创建一个用于获取 AB 包数据的 UnityWebRequest 对象。 | uri :请求 AB 包资源的 URI 地址,如 "http://192.168.1.101:8000/HTTPRoot/model" |
UnityWebRequest | SendWebRequest | 发送 Web 请求并等待服务器响应。 | 无 |
DownloadHandlerTexture | GetContent | 从 UnityWebRequest 对象中获取下载的纹理数据。 | request :UnityWebRequest 对象,用于获取其中的纹理数据 |
DownloadHandlerAssetBundle | GetContent | 从 UnityWebRequest 对象中获取下载的 AB 包数据。 | request :UnityWebRequest 对象,用于获取其中的 AB 包数据 |
上传
类名/接口 | 方法/描述 | 参数 |
---|---|---|
IMultipartFormSection | 数据相关类继承的父接口,可使用父类类型的集合来存储子类对象 | 无 |
MultipartFormDataSection | 用于传递键值对数据的类 | 1. 构造函数(二进制字节数组): |
MultipartFormFileSection | 用于传递文件数据的类 | 1. 构造函数(字节数组): |
UnityWebRequest | Post 方法:创建一个用于使用 POST 请求上传数据的UnityWebRequest 对象Put 方法:创建一个用于使用 PUT 请求上传数据的UnityWebRequest 对象SendWebRequest 方法:发送 Web 请求并等待服务器响应 | 1. |
NetWWWManager | UploadFile 方法:启动异步上传文件的操作UploadFileAsync 方法:异步上传文件的具体实现 | 1. |
15.2.高级上传和获取数据
获取
类名 | 方法/属性 | 方法的参数 |
---|---|---|
UnityWebRequest | 构造函数 | 无 |
UnityWebRequest | url | 字符串类型的服务器地址 |
UnityWebRequest | method | 如UnityWebRequest.kHttpVerbPOST等请求类型常量 |
UnityWebRequest | timeout | 整数类型的超时时间(单位:毫秒) |
UnityWebRequest | redirectLimit | 整数类型,0表示不进行重定向 |
UnityWebRequest | responseCode | 无 |
UnityWebRequest | result | 无 |
UnityWebRequest | error | 无 |
UnityWebRequest | downloadHandler | 实现了DownloadHandler的对象,如DownloadHandlerBuffer等 |
UnityWebRequest | uploadHandler | 实现了UploadHandler的对象 |
UnityWebRequest | Get | 字符串类型的请求URL |
UnityWebRequest | GetTexture | 字符串类型的纹理URL |
UnityWebRequestAssetBundle | GetAssetBundle | 字符串类型的AssetBundle URL |
UnityWebRequest | Put | 字符串类型的请求URL和字节数组形式的上传内容 |
UnityWebRequest | Post | 字符串类型的请求URL和List类型的上传数据列表 |
UnityWebRequest | isDone | 无 |
UnityWebRequest | downloadProgress | 无 |
UnityWebRequest | downloadedBytes | 无 |
UnityWebRequest | uploadProgress | 无 |
UnityWebRequest | uploadedBytes | 无 |
UnityWebRequest | SendWebRequest | 无 |
DownloadHandlerBuffer | 构造函数 | 无 |
DownloadHandlerFile | 构造函数 | 字符串类型的本地文件保存路径 |
DownloadHandlerTexture | 构造函数 | 无 |
DownloadHandlerAssetBundle | 构造函数 | 字符串类型的请求URL和校验码(ulong类型,不知道则传入0) |
UnityWebRequestMultimedia | GetAudioClip | 字符串类型的音频文件URL和AudioType类型的音频类型 |
DownloadHandlerAudioClip | GetContent | UnityWebRequest对象 |
CustomDownLoadFileHandler | 构造函数(无参) | 无 |
CustomDownLoadFileHandler | 构造函数(字节数组) | 字节数组类型的参数 |
CustomDownLoadFileHandler | 构造函数(路径) | 字符串类型的本地存储路径 |
CustomDownLoadFileHandler | GetData | 无 |
CustomDownLoadFileHandler | ReceiveData | byte[]类型的数据和int类型的数据长度 |
CustomDownLoadFileHandler | ReceiveContentLengthHeader | ulong类型的内容长度 |
CustomDownLoadFileHandler | CompleteContent | 无 |
上传
类名 | 方法/属性 | 方法的参数 | 描述 |
---|---|---|---|
UnityWebRequest | 构造函数 | string uri , string method :请求的 URL 和 HTTP 请求方法(如 UnityWebRequest.kHttpVerbPOST ) | 创建一个 UnityWebRequest 对象,用于网络请求 |
UnityWebRequest | uploadHandler | 实现了 UploadHandler 的对象,如 UploadHandlerRaw 、UploadHandlerFile 等 | 设置上传处理对象,用于处理上传的数据 |
UnityWebRequest | SendWebRequest | 无 | 发送网络请求,并等待请求返回 |
UnityWebRequest | result | 无 | 获取网络请求的结果(成功或失败) |
UploadHandlerRaw | 构造函数 | byte[] data :要上传的字节数组 | 创建一个用于上传字节数组的 UploadHandlerRaw 对象 |
UploadHandlerRaw | contentType | string 类型的内容类型,如 "类型/细分类型" | 设置上传数据的内容类型,若不设置,默认是 application/octet - stream |
UploadHandlerFile | 构造函数 | string path :要上传的文件路径 | 创建一个用于上传文件的 UploadHandlerFile 对象 |
16.Protobuf
16.1.Protobuf配置规则
(1)配置文件后缀:统一使用.proto
,可通过多个该后缀文件配置。
(2)版本号:第一行需指定,如syntax = "proto3";
,不写默认用proto2。
(3)注释方式:支持//
单行注释和/* */
多行注释。
(4)命名空间:用package
指定,如package 命名空间名;
。
(5)消息类:使用message
定义,格式为message 类名 { // 字段声明 }
。
(6)成员类型和编号:成员类型包含浮点数(float
、double
)、整数(int32
、int64
等)、其他(bool
、string
、bytes
),每个成员需指定从1开始的唯一编号。
(7)特殊标识:optional
表示可选赋值字段;repeated
表示数组(required
在proto3中已移除 )。
(8)枚举:用enum
定义,首个常量必须为0,如enum 枚举名 { 常量1 = 0; 常量2 = 1; }
。
(9)默认值:string
为空字符串、bytes
为空字节、bool
为false
、数值为0、枚举为0、message
在C#中为空。
(10)嵌套:支持消息类和枚举的嵌套定义。
(11)保留字段:使用reserved
关键字保留字段,防止已删编号被重新使用。
(12)导入定义:若使用其他配置中的类型,用import "配置文件路径";
导入。
syntax = "proto3"; // 决定了 proto 文档的版本号
// 规则1:版本号
// 规则2:注释方式
// 注释方式一
/* 注释方式二 */
// 规则11:导入定义
// 两个配置在同一路径可以这样写,在不同路径要包含文件夹路径
import "test2.proto";
// 规则3:命名空间
package GamePlayerTest; // 这决定了命名空间
// 规则4:消息类
message TestMsg {
// 规则5:成员类型 和 唯一编号
// 浮点数
// = 1 不代表默认值,而是代表唯一编号,方便我们进行序列化和反序列化的处理
// 规则6:特殊标识
// required: 必须赋值的字段
required float testF = 1; // C# - float
// optional: 可以不赋值的字段
optional double testD = 2; // C# - double
// 变长编码
// 所谓变长,就是会根据数字的大小来使用对应的字节数来存储,1 2 4
// Protobuf 帮助我们优化的部分,可以尽量少的使用字节数来存储内容
int32 testInt32 = 3; // C# - int 它不太适用于来表示负数,请使用 sint32
// 1 2 4 8
int64 testInt64 = 4; // C# - long 它不太适用于来表示负数,请使用 sint64
// 更实用与表示负数类型的整数
sint32 testSInt32 = 5; // C# - int 适用于来表示负数的整数
sint64 testSInt64 = 6; // C# - long 适用于来表示负数的整数
// 无符号 变长编码
// 1 2 4
uint32 testUInt = 7; // C# - uint 变长的编码
uint64 testULong = 8; // C# - ulong 变长的编码
// 固定字节数的类型
fixed32 testFixed32 = 9; // C# - uint 它通常用来表示大于2的28次方的数,比 uint32 更有效,始终是4个字节
fixed64 testFixed64 = 10; // C# - ulong 它通常用来表示大于2的56次方的数,比 uint64 更有效,始终是8个字节
sfixed32 testSFixed32 = 11; // C# - int 始终4个字节
sfixed64 testSFixed64 = 12; // C# - long 始终8个字节
// 其它类型
bool testBool = 13; // C# - bool
string testStr = 14; // C# - string
bytes testBytes = 15; // C# - BytesString 字节字符串
// 数组 List
repeated int32 listInt = 16; // C# - 类似 List<int> 的使用
// 字典 Dictionary
map<int32, string> testMap = 17; // C# - 类似 Dictionary<int, string> 的使用
// 规则7:枚举
// 枚举成员变量的声明需要唯一编码
TestEnum testEnum = 18;
// 规则8:默认值
// 声明自定义类对象 需要唯一编码
// 默认值是 null
TestMsg2 testMsg2 = 19;
// 规则9:允许嵌套
// 嵌套一个类在另一个类当中,相当于是内部类
message TestMsg3 {
int32 testInt32 = 1;
}
TestMsg3 testMsg3 = 20;
// 规则9:允许嵌套
enum TestEnum2 {
NORMAL = 0; // 第一个常量必须映射到0
BOSS = 1;
}
TestEnum2 testEnum2 = 21;
// 规则10:保留字段
// int32 testInt3233333 = 22;
bool testBool2123123 = 23;
// 告诉编译器 22 被占用,不准用户使用
// 之所以有这个功能,是为了在版本不匹配时,反序列化时,不会出现结构不统一,解析错误的问题
reserved 22;
reserved testInt3233333;
// 规则11:导入定义
GameSystemTest.HeartMsg testHeart = 24;
}
// 规则7:枚举的声明
enum TestEnum {
NORMAL = 0; // 第一个常量必须映射到0
BOSS = 5;
}
// 规则8:默认值
message TestMsg2 {
int32 testInt32 = 1;
}
syntax = "proto3"; // 决定了 proto 文档的版本号
package GameSystemTest; // 这决定了命名空间
message HeartMsg {
int64 time = 1;
}
16.2.Protobuf协议使用
序列化存储为本地文件
- 主要使用方法:生成的类中的
WriteTo
方法和文件流FileStream
对象。 - 示例代码:
TestMsg testMsg1 = new TestMsg();
testMsg1.ListInt.Add(1);
testMsg1.TestBool = false;
testMsg1.TestD = 5.5;
testMsg1.TestInt32 = 99;
testMsg1.TestMap.Add(1, "杨");
testMsg1.TestMsg2 = new TestMsg2();
testMsg1.TestMsg2.TestInt32 = 88;
testMsg1.TestMsg3 = new TestMsg.Types.TestMsg3();
testMsg1.TestMsg3.TestInt32 = 66;
testMsg1.TestHeart = new GameSystemTest.HeartMsg();
testMsg1.TestHeart.Time = 7777;
print(Application.persistentDataPath);
using (FileStream fileStream = File.Create(Application.persistentDataPath + "/TestMsg.tao"))
{
testMsg1.WriteTo(fileStream);
}
- 代码解释:先创建
TestMsg
实例并设置其属性值,然后使用File.Create
方法创建一个文件流,将TestMsg
实例序列化后写入该文件流,实现数据的本地存储。
反序列化本地文件
- 主要使用方法:生成的类中的
Parser.ParseFrom
方法和文件流FileStream
对象。 - 示例代码:
using (FileStream fileStream = File.OpenRead(Application.persistentDataPath + "/TestMsg.tao"))
{
TestMsg testMsg2 = null;
testMsg2 = TestMsg.Parser.ParseFrom(fileStream);
print(testMsg2.TestMap[1]);
print(testMsg2.ListInt[0]);
print(testMsg2.TestD);
print(testMsg2.TestMsg2.TestInt32);
print(testMsg2.TestMsg3.TestInt32);
print(testMsg2.TestHeart.Time);
}
- 代码解释:使用
File.OpenRead
方法打开之前存储的文件流,然后调用Parser.ParseFrom
方法从文件流中反序列化出TestMsg
实例,最后打印该实例的属性值。
得到序列化后的字节数组
- 主要使用方法:生成的类中的
WriteTo
方法和内存流MemoryStream
对象。 - 示例代码:
byte[] bytes = null;
using (MemoryStream memoryStream = new MemoryStream())
{
testMsg1.WriteTo(memoryStream);
bytes = memoryStream.ToArray();
print("字节数组长度" + bytes.Length);
}
- 代码解释:创建一个内存流,将
TestMsg
实例序列化后写入该内存流,再通过ToArray
方法将内存流中的数据转换为字节数组,方便进行网络传输。
从字节数组反序列化
- 主要使用方法:生成的类中的
Parser.ParseFrom
方法和内存流MemoryStream
对象。 - 示例代码:
using (MemoryStream memoryStream = new MemoryStream(bytes))
{
print("内存流当中反序列化的内容");
TestMsg testMsg2 = TestMsg.Parser.ParseFrom(memoryStream);
print(testMsg2.TestMap[1]);
print(testMsg2.ListInt[0]);
print(testMsg2.TestD);
print(testMsg2.TestMsg2.TestInt32);
print(testMsg2.TestMsg3.TestInt32);
print(testMsg2.TestHeart.Time);
}
- 代码解释:使用包含字节数组的内存流,调用
Parser.ParseFrom
方法从内存流中反序列化出TestMsg
实例,最后打印该实例的属性值。
17.大小端模式
(1)大小端模式的基本概念
- 大端模式:数据的高字节保存在内存的低地址中,低字节保存在内存的高地址中。地址由小向大增加时,数据从高位往低位放置,类似字符串顺序处理,符合人类阅读习惯。
- 小端模式:数据的高字节保存在内存的高地址中,低字节保存在内存的低地址中。例如十六进制数据 0x11223344,大端模式存储为 11 22 33 44(地址 0 1 2 3),小端模式存储为 44 33 22 11(地址 0 1 2 3)。
- 存在原因:是计算机硬件的两种存储数据方式(字节序)。计算机内部处理按顺序读取字节,因处理低位字节效率高而采用小端模式,人类读写习惯是大端字节序,网络传输和文件存储等场合一般用大端模式。
(2)大小端模式的影响及转换
- 影响:只有读取数据时需区分大小端字节序。网络传输中,前后端语言和设备差异可能致大小端不统一,如 C# 和 Java/Erlang/AS3 通讯需转换,C# 与 C++ 通信通常无需特殊处理。
- 转换方法
- 判断模式:用
BitConverter.IsLittleEndian
判断,如print("是否是小端模式:" + BitConverter.IsLittleEndian);
。 - 转为大端模式:通过
IPAddress.HostToNetworkOrder
将本机字节序转网络字节序(即大端模式),如int i = 99; byte[] bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(i));
。 - 转为小端模式:用
IPAddress.NetworkToHostOrder
将网络字节序转本机字节序(即小端模式),如int receI = BitConverter.ToInt32(bytes, 0); receI = IPAddress.NetworkToHostOrder(receI);
。 - 倒序数组转换:使用
Array.Reverse
倒序数组,若后端需大端模式且当前是小端模式则转换,如if (BitConverter.IsLittleEndian) Array.Reverse(bytes);
。
- 判断模式:用