73.总结
73.1 知识点
学习的主要内容
必须达到的水平
学会举一反三
学会了这些对于我们的意义
73.2 核心要点速览
IP地址和端口类
分类 | 详情 |
---|---|
IPAddress类 | 命名空间:System.Net 初始化方式: 1. byte数组:byte[] ipAddress = new byte[] { 118, 102, 111, 11 }; IPAddress iPAddress1 = new IPAddress(ipAddress); 2. long长整型:IPAddress iPAddress2 = new IPAddress(0x76666F0B);(不建议使用) 3. 字符串转换:IPAddress iPAddress3 = IPAddress.Parse(“118.102.111.11”);(推荐) 特殊IP地址:127.0.0.1代表本机地址 静态成员:IPAddress.IPv6Any(获取可用的IPv6地址) |
IPEndPoint类 | 命名空间:System.Net 作用:将网络端点表示为IP地址和端口号,用IP地址和端口号表示计算机的一个程序 初始化方式: 1. long长整型IP和端口号:IPEndPoint iPEndPoint1 = new IPEndPoint(0x79666F0B, 8080); 2. IP地址对象和端口号:IPEndPoint iPEndPoint2 = new IPEndPoint(IPAddress.Parse(“118.102.111.11”), 8080);(推荐) |
IPHostEntry | 作为域名解析返回值,用于获取IP、主机名等信息。成员变量有:AddressList(获取关联IP)、Aliases(获取主机别名列表)、HostName(获取DNS名称) |
Dns | 静态类,提供根据域名获取IP的方法。常用方法有:GetHostName(获取本地主机名,如print(Dns.GetHostName());)、GetHostEntry(同步获取IP,如IPHostEntry entry = Dns.GetHostEntry(“www.baidu.com");)、GetHostEntryAsync(异步获取IP,如Task |
网络通信中序列化和反序列化2进制数据
类名 | 详情 |
---|---|
BitConverter | 所在命名空间:System 1. GetBytes():将非字符串类型数据(如int、short、bool等)转换为字节数组,用于类对象二进制序列化时处理非字符串类型变量。 2. ToInt32()、ToInt16()、ToBoolean()等:将字节数组转换为对应的非字符串类型数据(如int、short、bool等),用于类对象二进制反序列化时将字节数组分组转换为对应类型变量。 |
Encoding | 所在命名空间:System.Text 1. UTF8.GetBytes():将字符串转换为字节数组,在类对象二进制序列化时,先计算字符串字节数组长度并序列化,再序列化字符串实际内容。 2. UTF8.GetString():传入字节数组、起始位置和长度,将字节数组转换为字符串,在类对象二进制反序列化时,先读取字符串字节数组长度,再根据长度和字节数组转换为字符串。 |
Socket类
类名 | 详情 |
---|---|
Socket | 命名空间:System.Net.Sockets 作用:支持TCP/IP网络通信的基本操作单位,是网络通信的数据通道,包含本机与对方主机的IP地址和端口、双方通信协议信息,可连接客户端和服务端以收发消息。常用于长连接游戏的通信。 类型: 1. 流套接字:用于TCP通信,提供面向连接、可靠、有序、无差错且无重复的数据传输服务。 2. 数据报套接字:用于UDP通信,提供无连接通信服务,数据包长度不超32KB,不保证正确性和顺序,可能重发、丢失。 3. 原始套接字:用于IP数据包通信,访问协议较低层,用于侦听和分析数据包(不常用)。 构造函数:public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType); 构造函数参数说明: 1. AddressFamily:网络寻址枚举类型,常用InterNetwork(IPv4寻址)、InterNetwork6(IPv6寻址)。 2. SocketType:套接字枚举类型,常用Dgram(用于UDP通信)、Stream(用于TCP通信)。 3. ProtocolType:协议类型枚举类型,常用TCP、UDP。 常用搭配: 1. SocketType.Dgram + ProtocolType.Udp:UDP协议通信。 2. SocketType.Stream + ProtocolType.Tcp:TCP协议通信。 实例化: 1. TCP流套接字:Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 2. UDP数据报套接字:Socket socketUdp = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 常用属性: 1. Connected:套接字的连接状态。 2. SocketType:套接字的类型。 3. ProtocolType:套接字的协议类型。 4. AddressFamily:套接字的寻址方案。 5. Available:套接字可读取字节数。 6. LocalEndPoint:本机EndPoint对象。 7. RemoteEndPoint:远程EndPoint对象。 常用方法: 服务端: 1. Bind:绑定IP和端口,传入IPEndPoint对象。 2. Listen:设置客户端连接的最大数,传入整形数字。 3. Accept:等待客户端连入,返回一个Socket对象。 客户端: 1. Connect:连接远程服务端,传入IPEndPoint对象。 客户端服务端通用: 1. Shutdown:释放连接并关闭Socket,选择Both可同时防止发送和接收。 2. Close:关闭连接,释放所有Socket关联资源。 总结:TCP和UDP两种长连接通信方案基于Socket套接字,需掌握TCP和UDP的Socket套接字声明。 |
TCP同步服务端和客户端基础实现
服务端基本实现
(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();
客户端实现:
(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();
区分消息类型
为发送的信息添加标识,比如添加消息 ID。在所有发送的消息的头部加上消息 ID(可以是 int、short、byte、long)。
举例说明:
如果选用 int 类型作为消息 ID 的类型,前 4 个字节为消息 ID,后面的字节为数据类的内容,这样每次收到消息时,先把前 4 个字节取出来解析为消息 ID,再根据 ID 进行消息反序列化即可。
分包和粘包
什么是分包、黏包?
分包、黏包指在网络通信中由于各种因素(网络环境、API规则等)造成的消息与消息之间出现的两种状态。
分包:一个消息分成了多个消息进行发送。
黏包:一个消息和另一个消息黏在了一起。
注意:分包和黏包可能同时发生。
解决思路:为消息添加头部记录长度,依据长度判断并处理分包、黏包,仅处理完整消息。
TCP同步退出消息和心跳消息
客服端主动断开连接
客户端:使用Disconnect方法主动断开连接,在TcpNetManager的Close方法中,依次执行关闭套接字发送和接收、手动停止连接、关闭套接字连接、将套接字置空以及标记连接已关闭等操作。
服务端:在ServerSocket中添加CloseClientSocket方法,用于关闭客户端连接并从字典中移除,操作客户端对象字典时添加线程锁以保证线程安全。
在ServerSocket的其他操作或访问字典的方法(如AcceptClientConnect、ReceiveClientMessage、Broadcast)中添加线程锁,确保字典操作的线程安全性。
为避免在遍历字典进行消息收发操作时直接移除客户端导致报错,在ServerSocket中创建List存储待移除的客户端socket(delList),并定义AddDelSocket方法用于添加待移除的socket。
将Program的serverSocket改为静态变量,在ClientSocket的发送和接收消息方法中,检测到断开连接或解析报错时,将当前客户端添加到服务端待移除的客户端列表中。
心跳消息
什么是心跳消息
所谓心跳消息,就是在长连接中,客户端和服务端之间定期发送的一种特殊的数据包,用于通知对方自己还在线,以确保长连接的有效性。由于其发送的时间间隔往往是固定的持续的,就像是心跳一样一直存在,所以我们称之为心跳消息。
为什么需要心跳消息
避免非正常关闭客户端时,服务器无法正常收到关闭连接消息。通过心跳消息我们可以自定义超时判断,如果超时没有收到客户端消息,证明客户端已经断开连接。避免客户端长期不发送消息,防火墙或者路由器会断开连接,我们可以通过心跳消息一直保持活跃状态。
实现心跳消息
客户端:主要功能是定时发送消息。
服务器端:主要功能是不停检测上次收到某客户端消息的时间,如果超时则认为连接已经断开。
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对象,设置好接收数据的缓冲区等信息,用于处理异步接受消息操作完成后的操作 | 异步接受消息。 |
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();
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客户端接收数据,接收完成后在回调函数进行处理 |
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 服务器上文件的最后修改日期和时间。 |
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);
}
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);
}
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类似 | - | - |
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 形式返回加载结果 |
WWWForm
类名 | 作用 | 注意事项 | 常用方法 |
---|---|---|---|
WWWForm | 在使用 WWW 类下载数据的基础上,若需上传数据,可结合该类使用。主要用于集成数据,可设置上传的参数或者二进制数据,结合使用时主要用 Post 请求类型,通过 Http 协议上传处理 | 使用 WWW 结合 WWWForm 上传数据通常需要与后端程序制定上传规则 | 1. WWWForm:构造函数 2. AddBinaryData:添加二进制数据 3. AddField:添加字段 |
UnityWebRequest
常用上传和获取数据
获取
类名 | 方法 | 方法解析 | 参数 |
---|---|---|---|
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. 构造函数(二进制字节数组):MultipartFormDataSection(byte[] data),参数data为二进制字节数组 2. 构造函数(字符串):MultipartFormDataSection(string data),参数data为字符串 3. 构造函数(参数名,参数值,编码类型,资源类型):MultipartFormDataSection(string name, string data, Encoding encoding, string contentType),参数name为参数名,data为参数值(字符串或字节数组形式),encoding为编码类型,contentType为资源类型 4. 构造函数(参数名,字节数组,资源类型):MultipartFormDataSection(string name, byte[] data, string contentType),参数name为参数名,data为字节数组,contentType为资源类型 |
MultipartFormFileSection | 用于传递文件数据的类 | 1. 构造函数(字节数组):MultipartFormFileSection(byte[] data),参数data为字节数组 2. 构造函数(文件名,字节数组):MultipartFormFileSection(string fileName, byte[] data),参数fileName为文件名,data为字节数组 3. 构造函数(字符串数据,文件名):MultipartFormFileSection(string data, string fileName),参数data为字符串数据,fileName为文件名 4. 构造函数(字符串数据,编码格式,文件名):MultipartFormFileSection(string data, Encoding encoding, string fileName),参数data为字符串数据,encoding为编码格式,fileName为文件名 5. 构造函数(表单名,字节数组,文件名,文件类型):MultipartFormFileSection(string formName, byte[] data, string fileName, string contentType),参数formName为表单名,data为字节数组,fileName为文件名,contentType为文件类型 6. 构造函数(表单名,字符串数据,编码格式,文件名):MultipartFormFileSection(string formName, string data, Encoding encoding, string fileName),参数formName为表单名,data为字符串数据,encoding为编码格式,fileName为文件名 |
UnityWebRequest | Post方法:创建一个用于使用 POST 请求上传数据的UnityWebRequest对象 Put方法:创建一个用于使用 PUT 请求上传数据的UnityWebRequest对象 SendWebRequest方法:发送 Web 请求并等待服务器响应 |
1. Post方法:UnityWebRequest.Post(string uri, List 2. Put方法:UnityWebRequest.Put(string uri, byte[] data),参数uri为请求的 URI 地址,data为要上传的文件内容(字节数组形式) 3. SendWebRequest方法:无参数 |
NetWWWManager | UploadFile方法:启动异步上传文件的操作 UploadFileAsync方法:异步上传文件的具体实现 |
1. UploadFile方法:UploadFile(string fileName, string localPath, UnityAction<UnityWebRequest.Result> action),参数fileName为上传后的文件名,localPath为本地要上传文件的路径,action为上传完成后的回调函数 2. UploadFileAsync方法:UploadFileAsync(string fileName, string localPath, UnityAction<UnityWebRequest.Result> action),参数fileName为上传后的文件名,localPath为本地要上传文件的路径,action为上传完成后的回调函数 |
高级上传和获取数据
获取
类名 | 方法/属性 | 方法的参数 |
---|---|---|
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 对象 |
Protobuf
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;
}
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 实例,最后打印该实例的属性值。
大小端模式
大小端模式的基本概念
大端模式:数据的高字节保存在内存的低地址中,低字节保存在内存的高地址中。地址由小向大增加时,数据从高位往低位放置,类似字符串顺序处理,符合人类阅读习惯。
小端模式:数据的高字节保存在内存的高地址中,低字节保存在内存的低地址中。例如十六进制数据 0x11223344,大端模式存储为 11 22 33 44(地址 0 1 2 3),小端模式存储为 44 33 22 11(地址 0 1 2 3)。
存在原因:是计算机硬件的两种存储数据方式(字节序)。计算机内部处理按顺序读取字节,因处理低位字节效率高而采用小端模式,人类读写习惯是大端字节序,网络传输和文件存储等场合一般用大端模式。
大小端模式的影响及转换
影响:只有读取数据时需区分大小端字节序。网络传输中,前后端语言和设备差异可能致大小端不统一,如 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); 。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com