73.Unity网络开发基础知识总结

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 task = Dns.GetHostEntryAsync(“www.baidu.com“); await 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 formData),参数uri为请求的 URI 地址,formData为包含上传数据的IMultipartFormSection列表
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

×

喜欢就点赞,疼爱就打赏