Unity网络开发基础——HTTP(肆)

目录

目录

学习笔记主要来源于唐老狮的Unity课程,经过个人简单整理而成。笔记总共五个部分:

这个课程的大致内容如下:

  • 网络通信中的一些必备理论知识(OSI模型、TCP/IP协议等)。
  • 对自定义类对象进行序列化和反序列化。
  • 使用Socket,进行TCP、UDP的同步异步通信。
  • 处理网络通信中的分包黏包和心跳消息。
  • 使用FTP和HTTP协议进行文件的上传和下载。
  • 掌握Unity的WWW类和UnityWebRequest类。
  • 熟练使用Protobuf,了解自定义协议生成工具的制作原理。
  • 了解大小端模式,消息加密原理。

课程不包含游戏的实际游戏开发的内容,比如后端相关知识、同步模式(帧同步、状态同步)等等,只包含网络通信最基本的内容。

七、网络通信——超文本传输HTTP

(一)HTTP概述

1、HTTP概念

HTTP(HyperText Transfer Protocol)超文本传输协议,是因特网上应用最为广泛的一种网络传输协议。最初设计HTTP是为了提供一种发布和接收由文本文件组成的HTML页面的方法,后来发展到还可以传输图片、音频、视频、压缩文件以及各种程序文件等。 HTTP主要用于超文本传输,相对FTP更简单一些,目前常见的HTTP标准是HTTP/1.1

简单来说,HTTP超文本传输协议就是一个在网络中上传下载文件的一套规则

2、HTTP工作原理

HTTP的本质也是TCP通信,HTTP定义了Web客户端(一般指浏览器)如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。

HTTP客户端首先与服务器建立TCP连接,然后客户端通过套接字发送HTTP请求,并通过套接字接收HTTP响应,由于HTTP采用TCP传输数据,因此不会丢包、不会乱序。

3、HTTP的特点

(1)HTTP以TCP方式工作

HTTP/1.0中,客户端和服务器建立TCP连接后,发送一个请求到服务器,服务器发送一个应答给客户端,然后立即断开TCP连接。即连接—>请求—>响应—>断开短连接形式。

但在HTTP/1.1中,可以支持持久连接,这使得在获取上一个请求的应答前能够发送多个请求。此外,HTTP/1.1可以发送的请求类型比HTTP/1.0多,因此目前使用的基本上都是HTTP/1.1版本。

(2)HTTP是无状态的

无状态的意思是,客户端发送一次请求后,服务端并没有存储关于该客户端的任何状态信息,即使客户端再次请求同一个对象,服务端仍会重新发送这个对象,不会在意之前是否已经向客户端发送过这个对象。

简单来说,就是客户端要什么给什么,要多少给多少,服务端不会因为你要过了而不给你。

(3)HTTP使用元信息作为标头

HTTP通过添加标头(header)的方式向服务端提供本次HTTP请求的相关信息,即在主要数据前添加一部分额外信息,称为元信息(metainformation),元信息主要包含传输的对象属于哪种类型,采用的是哪种编码等等。

4、HTTP协议的请求类型和响应状态码

(1)请求类型

关于请求类型,在HTTP请求的头部信息的请求行中,HTTP请求格式如下:

image-20260530233437154

请求类型如下:

  • HTTP/1.0GET、POST、HEAD
  • HTTP/1.1GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT

image-20260530233725615

(2)状态码

关于响应状态码,在HTTP响应的头部信息的状态行中,HTTP响应格式如下:

image-20260530233609329

  • 状态码类型如下

    • 1xx消息:请求已被服务端接收,继续处理。
    • 2xx成功:请求已成功被服务端理解并接收 。
    • 3xx重定向:需要后续操作才能完成这一请求 。
    • 4xx请求错误:请求含有语法错误或者无法被执行 。
    • 5xx服务器错误:服务端在处理某个正确请求时发生错误。
  • 常用状态码

image-20260530233906203

5、如何学习

在实际学习过程中,我们并不需要利用HTTP原理来实现HTTP通信。C#中实现了HTTP通信需要用到的相关类:WebRequestHttpWebRequestHttpWebResponse等。Unity中也实现了HTTP通信需要用到的相关类:UnityWebRequestWWWWWWFrom等。

我们只需使用这些类实现如下功能:

  • 搭建HTTP服务器。
  • 获取HTTP服务器上内容。
  • 上传内容到HTTP服务器。

(二)搭建HTTP服务器

1、搭建HTTP服务器的方式

  • 使用别人做好的HTTP服务器软件,一般作为资源服务器时使用该方式(学习阶段建议使用)。
  • 自己编写HTTP服务器应用程序,一般作为Web服务器或者短连接游戏服务器时使用该方式。

其中第2点一般由后端程序员完成。

2、使用hfs搭建HTTP资源服务器

这里使用hfs进行搭建,使用起来非常简单,因此这里就不过多赘述了。

3、关于Web服务器

由于Web服务器一般由后端程序员完成,因此在学习过程中,可以直接在别人做好的Web服务器上获取信息和资源,比如我们可以下载任意网站上可被下载的图片进行测试。

(三)HTTP——C#相关类

1、HttpWebRequest

HTTP客户端请求类,主要用于发送HTTP客户端请求给服务器,可以进行消息通信、上传、下载等等操作。

  • 创建WebRequest
    • 例:创建 HTTP 服务器 http://127.0.0.1:80/HTTP_Server/ 的请求通道。
    • 这里和FTP用法类似,而且会发现这个Create其实是父类WebRequest的方法,那它是怎么分辨具体的WebRequest呢?原因就是这个Create是一个工厂方法,它根据地址开头的协议,返回对应类型的请求对象,比如这里是http://开头,他就返回HttpWebRequest对象。
HttpWebRequest req = HttpWebRequest.Create(new Uri("http://127.0.0.1:80/HTTP_Server/")) as HttpWebRequest;
  • 终止文件传输
req.Abort();
  • 获取上传的流
    • 这是同步方法,当然还有异步的方法,这里不再赘述。
Stream s = req.GetRequestStream();
  • 获取HTTP服务器响应
    • 这是同步方法,当然还有异步的方法,这里不再赘述。
HttpWebResponse res = req.GetResponse() as HttpWebResponse;
  • 设置或获取通信凭证
    • 注意,这里设置通信凭证的话,需要将PreAuthenticate设置为true
req.Credentials;
req.PreAuthenticate = true; // 是否随请求发送一个身份验证标头,一般进行身份验证时需要设为true,表示身份验证后再发送包含请求的HTTP授权标头
  • 指定 HTTP 标头的键/值对集合
req.Headers;
  • 发送信息的字节数
req.ContentLength; // 上传信息时需要先设置该内容长度
  • 发送信息的类型
req.ContentType; // 进行POST请求时,需要进行内容类型的设置
  • 操作命令设置
req.Method;

// 所有HTTP命令
WebRequestMethods.Http.Get; // 获取请求,一般用于获取数据
WebRequestMethods.Http.Post; // 提交请求,一般用于上传数据,同时可以获取
WebRequestMethods.Http.Head; // 获取和Get一致的内容,只是只会返回消息头,不会返回具体内容
WebRequestMethods.Http.Put; // 向指定位置上传最新内容
WebRequestMethods.Http.Connect; // 表示与代理一起使用的 HTTP CONNECT 协议方法,该代理可以动态切换到隧道
WebRequestMethods.Http.MkCol; // 请求在请求 URI(统一资源标识符)指定的位置新建集合

2、HttpWebRespose

HTTP服务器响应类,主要用于获取服务器反馈信息。

该类对象可通过HttpWebRequest中的GetResponse方法获取。使用完毕时,要使用Close释放。

  • 释放所有资源
res.Close();
  • 获取从FTP服务器下载数据的流
Stream s = res.GetResponseStream();
  • 常用成员属性
res.ContentLength; // 接收到数据的长度
res.ContentType; // 接收数据的类型
res.StatusCode; // HTTP服务器下发的最新状态码
res.StatusDescription; // HTTP服务器下发的状态代码的文本
res.LastModified; // HTTP服务器上的文件的上次修改日期和时间
req.Timeout; // 超时设置

(四)HTTP下载数据——C#相关类

以获取HTTP服务器上的Test.png为例,我们将使用Head请求和Get请求来下载数据,这里分两步:

1、使用Head请求检测资源可用性

// 1、创建HttpWebRequest对象
HttpWebRequest req = HttpWebRequest.Create(new Uri("http://127.0.0.1:80/HTTP_Server/Test.png")) as HttpWebRequest;
// 2、设置请求类型 和 其它相关参数
req.Method = WebRequestMethods.Http.Head; // 设置Head请求类型
req.Timeout = 2000; // 设置超时时间
// 3、发送请求,获取响应
HttpWebResponse res = req.GetResponse() as HttpWebResponse;

if (res.StatusCode == HttpStatusCode.OK)
    Debug.Log($"文件存在且可用,文件类型:{res.ContentType},文件大小:{res.ContentLength}");

res.Close();

2、使用Get请求下载数据

如果上一步检测到状态码是OK的,那么就可以使用Get请求下载数据。

// 1、创建HttpWebRequest对象
HttpWebRequest req = HttpWebRequest.Create(new Uri("http://127.0.0.1:80/HTTP_Server/Test.png")) as HttpWebRequest;

// 2、设置请求类型 和 其它相关参数
req.Method = WebRequestMethods.Http.Get; // 设置Get请求类型
req.Timeout = 3000; // 设置超时时间

// 3、发送请求,获取响应
HttpWebResponse res = req.GetResponse() as HttpWebResponse;

// 4、获取响应数据流,写入数据到下载位置
if (res.StatusCode == HttpStatusCode.OK)
{
    string path = Application.persistentDataPath + "/Test.png"; // 下载的目标路径
    using (FileStream fs = File.Create(path))
    {
        byte[] bytes = new byte[1024];
        Stream downloadStream = res.GetResponseStream();
        int contentLength = downloadStream.Read(bytes, 0, bytes.Length);
        while (contentLength != 0)
        {
            fs.Write(bytes, 0, contentLength);
            contentLength = downloadStream.Read(bytes, 0, bytes.Length);
        }
        fs.Close();
        downloadStream.Close();
    }
    Debug.Log("下载成功");
}

res.Close();

(五)Get和Post请求的额外参数

我们在进行HTTP通信时,可能需要传递一些额外的参数数据给服务端,而GetPost请求都是可以传递这些额外参数的,那这两者的区别是什么呢?

1、Get请求携带额外参数

我们在使用Get请求进行HTTP通信时,可以在地址URL后面加一些额外参数传递给服务端。

举例:http://www.aspxfans.com:8080/news/child/index.asp?boardID=5&ID=24618&page=1
这个额外参数为?之后的部分,即boardID=5&ID=24618&page=1,包含3个参数:

  • boardID = 5
  • ID = 24618
  • page = 1

2、Post请求携带额外参数

Post传递的参数放在HTTP请求的报文体(Request Body)中,而不是放在URL后面。

  • C#实现步骤
    • 关键步骤就是req.Method设置Post请求类型,req.ContentType设置上传数据的类型,以及将数据写入流中。
// 1、创建与设置
HttpWebRequest req = HttpWebRequest.Create("http://127.0.0.1:80/HTTP_Server/") as HttpWebRequest;
req.Method = WebRequestMethods.Http.Post; // Post请求
req.Timeout = 2000;
// 2、设置上传内容的类型
req.ContentType = "application/x-www-form-urlencoded";
// 3、设置上传的数据
string str = "Name=PMM&ID=1";
byte[] bytes = Encoding.UTF8.GetBytes(str);
req.ContentLength = bytes.Length; // 设置内容长度
// 4、写入数据
Stream stream = req.GetRequestStream();
stream.Write(bytes, 0, bytes.Length);
stream.Close();
// 5、上传数据并获取响应
HttpWebResponse res = req.GetResponse() as HttpWebResponse;
Debug.Log(res.StatusCode);
1、通用2进制类型
application/octet-stream
2、通用文本类型
text/plain 
3、键值对参数
application/x-www-form-urlencoded
4、复合类型(传递的信息由多种类型组成,上传资源服务器时需要用该类型)
multipart/form-data

3、Get和Post的区别

用途

  • Get:一般从指定的资源请求数据,主要用于获取数据
  • Post:一般向指定的资源提交想要被处理的数据,主要用于上传数据

不同点

  • 传递参数时,Get传递的参数都包含在URL中,是暴露式的;Post传递的参数放在请求数据中,不在URL中,是隐藏式的。Post相对Get更加的安全
  • Get在传递数据时有大小的限制,因为它在URL中拼接参数,而URL的长度是有限制的;Post在传递数据时没有限制
  • 在浏览器中Get请求能被缓存,Post不能缓存。
  • 传输次数可能不同
    • Get:一次传输, 建立连接 —> 请求行、请求头、请求数据 -> 获取响应 —> 断开连接
    • Post:可能分两次,建立连接 —> 请求行,请求头 —> 请求数据 —> 获取响应 —> 断开链接

我们在和服务端通信时,只要按照这种规则格式进行通信,就可以传递参数给对象,主要用于web网站服务器短连接游戏服务器等。

(六)HTTP上传数据——C#相关类

1、上传文件到HTTP资源服务器的规则

上传文件到HTTP资源服务器时,我们需要使用multipart/form-data类型,即复合类型。而该类型的请求格式大致如下:

该请求用到了边界字符串,这是用于分隔多个数据块的“分隔符”。其中--${Boundary}表示一个字段的开始,之后的Content-DispositionContent-Type用于表示文件的信息和类型,空一行之后就是该文件的二进制数据。如果有多个文件,可以继续用--${Boundary}表示新字段,然后往下写。最后请求体以--${Boundary}--作为结束标志。

# 请求头
Content-Type: multipart/form-data; boundary=${Boundary}

# 请求体
--${Boundary}
Content-Disposition: form-data; name="file"; filename="example.png"
Content-Type: application/octet-stream

bytes of file
--${Boundary}--

上传时,文件的信息和类型意思如下:

  • form-data:表示这份数据是来自HTML表单的多部分编码格式。
  • name:字段名,之后写入的文件二进制数据和该字段名对应。
  • filename:传到服务器上的文件名。
  • application/octet-stream:HTTP中的二进制数据通用MIME类型,由于我们传的是二进制文件,所以可以不用改。

2、使用Post上传数据

这里展示的是StreamingAssets/test.png上传到http服务器的http://127.0.0.1:80/HTTP_Server/Test2.jpg中。

几个注意点:

  • 需要确保服务器允许上传,我这里设置了用户的上传权限,因此设置了通信凭证,同时需要将req.PreAuthenticate设为true,之前C#相关类知识点中提及过。
  • 上传的数据就是上面提到的请求体的内容,这里将其拆成了3个部分进行拼接:head就是--${Boundary}的部分,end就是--${Boundary}--的部分,中间就是文件数据的部分,当然上传时都是二进制的形式
// 1、创建HttpWebRequest对象
HttpWebRequest req = HttpWebRequest.Create("http://127.0.0.1:80/HTTP_Server/") as HttpWebRequest;

// 2、相关设置(请求类型,内容类型,超时,身份验证等)
req.Method = WebRequestMethods.Http.Post; // Post请求
req.Timeout = 500000;
req.ContentType = "multipart/form-data;boundary=PMM123"; // 设置内容类型和边界字符串
req.Credentials = new NetworkCredential("pmm", "123456"); // 设置通信凭证
req.PreAuthenticate = true; // 先验证身份,再上传数据

// 3、按格式拼接字符串并且转为字节数组之后用于上传
string head = "--PMM123\r\n" +
    "Content-Disposition:form-data;name=\"file\";filename=\"Test2.jpg\"\r\n" +
    "Content-Type:application/octet-stream\r\n" +
    "\r\n";
byte[] headBytes = Encoding.UTF8.GetBytes(head); // 头部信息
byte[] endBytes = Encoding.UTF8.GetBytes("\r\n--PMM123--\r\n"); // 结束标志

// 4、写入上传流
string path = Application.streamingAssetsPath + "/test.png"; // 要上传的文件路径
using (FileStream fs = File.OpenRead(path))
{
    // 设置上传长度(头部 + 数据 + 尾部)
    req.ContentLength = headBytes.Length + fs.Length + endBytes.Length;

    Stream uploadStream = req.GetRequestStream();
    // 写入头部信息
    uploadStream.Write(headBytes, 0, headBytes.Length);
    // 写入文件数据
    byte[] bytes = new byte[2048];
    int contentLength = fs.Read(bytes, 0, bytes.Length);
    while (contentLength != 0)
    {
        uploadStream.Write(bytes, 0, contentLength);
        contentLength = fs.Read(bytes, 0, bytes.Length);
    }
    // 写入结束标志
    uploadStream.Write(endBytes, 0, endBytes.Length);

    fs.Close();
    uploadStream.Close();
}

// 5、上传数据并获取响应
HttpWebResponse res = req.GetResponse() as HttpWebResponse;
if (res.StatusCode == HttpStatusCode.OK)
    Debug.Log("上传成功");

(七)HTTP——Unity相关类

1、WWW

WWW类,是Unity提供的用于简单的访问网页的类,可以用来下载和上传一些数据。在使用http协议时,默认的请求类型是Get,如果想要Post上传,需要配合WWWFrom类使用。

(1)主要支持的协议

  • http://https://:超文本传输协议。
  • ftp://:文件传输协议(但仅限于匿名下载,Serv-U的匿名用户名为Anonymous)。
  • file://:本地文件传输协议,用于异步加载本地文件。

(2)注意

  • 该类一般配合协同程序使用。
  • 该类在较新Unity版本中已经过时,新版本将其功能整合进了UnityWebRequest类

(3)常用成员属性与方法

  • 创建一个WWW请求
WWW www = new WWW("http://localhost/HTTP_Server/Test.png");
  • 从下载数据获取音效切片对象
www.GetAudioClip();
  • 用下载数据中的图像来替换现有的Texture2D对象
www.LoadImageIntoTexture(texture);
  • 从缓存加载AB包对象
    • 如果该包不在缓存中,则自动下载并存储到缓存中,以便以后直接从本地缓存中加载。
WWW.LoadFromCacheOrDownload(url, version); // 静态方法
  • 常用成员属性
www.assetBundle; // 获取加载的AB包
www.bytes; // 以字节数组的形式获取加载到的内容
www.bytesDownloaded; // 已下载的字节数
www.error; // 返回一个错误消息
www.isDone; // 判断是否已经完成
www.progress; // 下载进度
www.text; // 获取加载的字符串数据
www.texture; // 获取加载的图片

2、WWWForm

WWW表单类,用于使用UnityWebRequestWWW生成表单数据以发布到Web服务器。

上节介绍了使用WWW类下载数据,若要上传数据,则需要配合WWWForm类。它主要用的是Post请求,使用http协议进行上传处理。

需要注意的是,使用WWW结合WWWFrom上传数据时,无法直接上传文件到资源服务器需要后端处理Post请求的信息,一般配合后端程序制定上传的规则。

  • 构造函数
WWWForm form = new WWWForm();
  • 向表单添加二进制数据
form.AddBinaryData(fieldName, contents);
  • 向表单添加简单字段
form.AddField(fieldName, value);

3、UnityWebRequest

该类是Unity用于与Web服务器通信的核心类,集成了之前WWW的相关功能,主要的区别就是UnityWebRequest把数据处理单独提取出来了,可以自己选择对应的数据处理对象来获取数据。

(1)注意

  • 该类一般配合协同程序使用。
  • 支持http、ftp、file协议下载或加载资源。
  • 与WWW类不同,它能够上传文件到HTTP资源服务器。

(2)常用成员属性和方法

  • 创建UnityWebRequest
    • 可以使用UnityWebRequest及其子类的工厂方法。也可以使用构造函数创建。
    • downloadHandler是下载处理器,用于处理接收到数据;uploadHandler是上传处理器,用于处理数据的上传,后面会讲解如何使用。
// 工厂方法创建
UnityWebRequest.Get(uri);
UnityWebRequest.Head(uri);
UnityWebRequest.Put(uri);
UnityWebRequest.Post(uri);

// 构造函数创建
UnityWebRequest req = new UnityWebRequest(uri, method, downloadHandler, uploadHandler);
  • 发送请求
req.SendWebRequest(); // 返回UnityWebRequestAsyncOperation,用于协程操作
  • 常用成员属性
// 1、一些设置
req.url; // 请求地址
req.method = UnityWebRequest.kHttpVerbPOST; // 请求类型(可使用定义好的常量字符串,k开头)
req.downloadHandler; // 下载处理器
req.uploadHandler; // 上传处理器
req.timeout; // 超时设置
req.redirectLimit; // 重定向次数,设置为0表示不进行重定向

// 2、进度
req.isDone; // 是否完成
req.downloadProgress; // 下载进度
req.uploadProgress; // 上传进度
req.downloadedBytes; // 下载的字节数
req.uploadedBytes; // 上传的字节数

// 3、响应结果
req.result; // 结果
req.error; // 错误内容
req.responseCode; // 响应状态码

4、IMultipartFormSection

IMultipartFormSection是一个接口,用于将数据组合到多部分表单,配合 UnityWebRequest 可实现上传文本字段/文件。

关于表单数据,有两种方式,一种是旧版的WWWForm,另一种则是这里的IMultipartFormSection,它相比WWWForm更标准、更灵活。

(1)IMultipartFormSection定义

内部定义了段的各个属性,是表单段的统一规范。

public interface IMultipartFormSection
{
    string contentType { get; } // 当前段的 Content-Type(如 text/plain、image/png)
    string fileName { get; } // 文件名(文件段用,普通字段可返回 null)
    byte[] sectionData { get; } // 段的原始二进制数据(不可空、不可空数组)
    string sectionName { get; } // 字段名(对应 form 的 name)
}

(2)使用

发送表单数据给服务器时,我们会发现参数是List<IMultipartFormSection>类型的,这表示表单的所有分段内容清单,我们只需要往这个列表添加字段即可。

Unity为数据和文件部分提供了IMultipartFormSection接口的两个默认实现:MultipartFormDataSectionMultipartFormFileSection

  • MultipartFormDataSection对象用于普通键值对字段,类似WWWForm的AddField
  • MultipartFormFileSection对象用于文件/二进制字段,类似WWWForm的AddBinaryData

接下来举个例子,包含了常用的一些构造函数:

// 声明容器,表示表单字段数据
List<IMultipartFormSection> formData = new List<IMultipartFormSection>();
// MultipartFormDataSection:用于普通文本/字段
formData.Add(new MultipartFormDataSection(name, bytes)); // 字节数组
formData.Add(new MultipartFormDataSection(name, str)); // 字符串
// MultipartFormFileSection:用于文件
formData.Add(new MultipartFormFileSection(fileName, bytes)); // 二进制文件
formData.Add(new MultipartFormFileSection(str, encoding, fileName)); // 文本文件

(八)HTTP上传下载——WWW

1、关于协程

使用WWW进行上传和下载时,我们需要用到协程来等待请求完成。这里有两种方式,为了代码简洁些,后续统一使用方式1。

// 方式一:等待加载完成
yield return www;
// 方式二:等待过程中进行处理
while (!www.isDone)
{
    Debug.Log(www.bytesDownloaded);
    Debug.Log(www.progress);
    yield return null;
}

2、下载数据

  • 关于FTP和本地文件的下载,和HTTP类似,唯一区别的就是创建WWW对象时所填的文件地址不同。
  • 如果想要下载保存到本地,可以用www.bytes获取字节数组,然后用File.WriteAllBytes写入本地文件。
IEnumerator DownloadHttp()
{
    // 1、创建WWW对象
    WWW www = new WWW("http://localhost/HTTP_Server/Test.png");
    
    // 2、等待加载结束
    yield return www;

    // 3、将加载的图片赋值给场景上的UI图片对象
    if (www.error == null)
        image.texture = www.texture;
    else
        Debug.Log("下载失败:" + www.error);
}

3、上传数据

结合WWWForm表单上传数据,创建WWW对象时传入WWWForm数据。

IEnumerator UploadData()
{
    // 1、创建表单
    WWWForm form = new WWWForm();
    form.AddField("name","PMM");
    form.AddField("age", 20);
    form.AddBinaryData("file", 
        File.ReadAllBytes(Application.streamingAssetsPath + "/test.png"),
        "formTest.png",
        "application/octet-stream");
    
    // 2、创建WWW对象
    WWW www = new WWW("http://localhost/HTTP_Server/", form);
    
    // 3、等待加载结束
    yield return www;
    
    // 4、获取上传结果信息
    if (www.error == null)
        Debug.Log("上传成功");
    else
        Debug.Log("上传失败:" + www.error);
}

(九)HTTP上传下载——UnityWebRequest

1、关于协程

使用UnityWebRequest进行上传和下载时,我们需要用到协程来等待请求完成。这里有两种方式,为了代码简洁些,后续统一使用方式1。

// 方式一:等待加载完成
yield return req.SendWebRequest(); 
// 方式二:等待过程中获取进度
req.SendWebRequest();
while (!req.isDone)
{
    Debug.Log(req.downloadProgress);
    Debug.Log(req.downloadedBytes);
    yield return null;
}

2、下载数据

(1)举例:这里简单3个例子,分别是下载文本、下载纹理和下载AB包。

  • 这里都使用了工厂方法创建GET请求(也可以用构造函数进行创建),可以发现在下载纹理和AB包时,使用了它的子类UnityWebRequestTextureUnityWebRequestAssetBundle,区别就是内部使用了不同的DownloadHandler进行处理,方便我们获取对应数据。
  • 数据都是通过DownloadHandler获取的,不同DownloadHandler处理数据方式不同。
  • 获取数据有两种方式,可以用DownloadHandler的成员变量获取,也可以用静态方法GetContent获取。
// 下载文本
IEnumerator LoadText()
{
    // 1、创建请求
    UnityWebRequest req = UnityWebRequest.Get("http://127.0.0.1/HTTP_Server/test.txt");
    // 2、发送请求
    yield return req.SendWebRequest();
    // 3、获取请求结果
    if (req.result == UnityWebRequest.Result.Success)
    {
        Debug.Log(req.downloadHandler.text); // 获取文本
        byte[] bytes = req.downloadHandler.data; // 获取字节数组
    }
}

// 下载纹理图片
IEnumerator LoadTexture()
{
    // 1、创建请求
    UnityWebRequest req = UnityWebRequestTexture.GetTexture("http://127.0.0.1/HTTP_Server/Test.png");
    // 2、发送请求
    yield return req.SendWebRequest();
    // 3、获取请求结果
    if (req.result == UnityWebRequest.Result.Success)
    {
        image.texture = (req.downloadHandler as DownloadHandlerTexture).texture; // 方式一:成员属性
        image.texture = DownloadHandlerTexture.GetContent(req); // 方式二:静态方法
    }
}

// 下载AB包
IEnumerator LoadAB()
{
    // 1、创建请求
    UnityWebRequest req = UnityWebRequestAssetBundle.GetAssetBundle("http://127.0.0.1/HTTP_Server/test");
    // 2、发送请求
    yield return req.SendWebRequest();
    // 3、获取请求结果
    if (req.result == UnityWebRequest.Result.Success)
    {
        AssetBundle ab = (req.downloadHandler as DownloadHandlerAssetBundle).assetBundle; // 方式一:成员属性
        AssetBundle ab2 = DownloadHandlerAssetBundle.GetContent(req); // 方式二:静态方法
    }
}

(2)DownloadHandler

负责接收服务器返回的数据,并把数据解析成你需要的格式。这里介绍各个不同的子类实现:

  • DownloadHandlerBuffer:用于简单的数据存储,得到对应的2进制数据,通用的DownloadHandler子类。
UnityWebRequest req = new UnityWebRequest(url, UnityWebRequest.kHttpVerbGET);
DownloadHandlerBuffer handler = new DownloadHandlerBuffer();
req.downloadHandler = handler;

yield return req.SendWebRequest();

if(req.result == UnityWebRequest.Result.Success)
{
    handler.data // 获取字节数组
}
  • DownloadHandlerFile:用于下载文件并将文件保存到磁盘(内存占用少)。
UnityWebRequest req = new UnityWebRequest(url, UnityWebRequest.kHttpVerbGET);
req.downloadHandler = new DownloadHandlerFile(path);

yield return req.SendWebRequest();

if(req.result == UnityWebRequest.Result.Success)
{
    // 下载成功
}
  • DownloadHandlerTexture:用于下载图像。
UnityWebRequest req = new UnityWebRequest(url, UnityWebRequest.kHttpVerbGET);
DownloadHandlerTexture handler = new DownloadHandlerTexture();
req.downloadHandler = handler;

yield return req.SendWebRequest();

if(req.result == UnityWebRequest.Result.Success)
{
    handler.texture; // 获取纹理图片
}
  • DownloadHandlerAssetBundle:用于提取 AssetBundle。
UnityWebRequest req = new UnityWebRequest(url, UnityWebRequest.kHttpVerbGET);
DownloadHandlerAssetBundle handler = new DownloadHandlerAssetBundle(url, crc);
req.downloadHandler = handler;

yield return req.SendWebRequest();

if (req.result == UnityWebRequest.Result.Success)
{
    handler.assetBundle; // 获取ab包
}
  • DownloadHandlerAudioClip:用于下载音频文件。
UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip(url, audioType);

yield return req.SendWebRequest();

if (req.result == UnityWebRequest.Result.Success)
{
    DownloadHandlerAudioClip.GetContent(req); // 获取音频
}

(3)自定义DownloadHandler

继承DownloadHandlerScript,实现相关方法即可自定义DownloadHandler。这里简单举个例子:写一个能够下载文件的下载处理器。

public class DownloadHandlerCustom : DownloadHandlerScript
{
    private string savePath; // 保存路径
    private byte[] bytes; // 收到的字节数组
    private int index = 0; // 已接收的字节数

    public DownloadHandlerCustom(string savePath) : base()
    {
        this.savePath = savePath;
        index = 0;
    }

    // data 属性的值返回的字节数组
    protected override byte[] GetData()
    {
        return bytes; // 获取字节数组
    }

    // 从远程服务器收到数据时的回调,收到的数据会存储在缓冲区中,如果缓冲区有未处理的数据则每帧调用
    protected override bool ReceiveData(byte[] data, int dataLength)
    {
        data.CopyTo(bytes, index); // 将收到的数据拷贝进字节数组中
        index += dataLength;
        return true;
    }

    // 收到 Content-Length 标头的回调,表示消息体大小
    protected override void ReceiveContentLengthHeader(ulong contentLength)
    {
        bytes = new byte[contentLength]; // 初始化字节数组大小
    }

    // 接收所有数据后的回调
    protected override void CompleteContent()
    {
        File.WriteAllBytes(savePath, bytes); // 写入文件
    }
}

3、上传数据

(1)上传数据

结合IMultipartFormSection上传表单数据,创建UnityWebRequest对象时可以传入表单数据。

  • 和WWW不同,例子中的文件是可以是可以上传到资源服务器的。当然字段数据还是需要后端服务器处理。
IEnumerator Upload()
{
    // 1、准备上传的数据
    List<IMultipartFormSection> formData = new List<IMultipartFormSection>();
    formData.Add(new MultipartFormDataSection("name", "PMM")); // 上传键值对
    string path = Application.streamingAssetsPath + "/test.png";
    formData.Add(new MultipartFormFileSection("Test.png", File.ReadAllBytes(path))); // 上传文件

    // 2、创建请求
    UnityWebRequest req = UnityWebRequest.Post("http://127.0.0.1/HTTP_Server/", formData);
     
    // 3、发送请求
    yield return req.SendWebRequest();  

    // 4、获取请求结果
    if (req.result == UnityWebRequest.Result.Success)
        Debug.Log("上传成功");
}

(2)UploadHandler

负责把Unity中的数据打包发送给服务器。由于上面这种发送表单的上传方式已经够用了,因此UploadHandler使用的较少,这里简单了解即可。

  • UploadHandlerRaw:用于上传字节数组。
UnityWebRequest req = new UnityWebRequest("http://127.0.0.1/HTTP_Server/", UnityWebRequest.kHttpVerbPOST);
req.uploadHandler = new UploadHandlerRaw(bytes); // 需要上传的字节数组
//req.uploadHandler.contentType = "application/octet-stream"; // 可以设置内容类型,默认就是这个类型

yield return req.SendWebRequest();

if (req.result == UnityWebRequest.Result.Success)
    Debug.Log("上传成功");
  • UploadHandlerFile:用于上传文件。
UnityWebRequest req = new UnityWebRequest("http://127.0.0.1/HTTP_Server/", UnityWebRequest.kHttpVerbPOST);
req.uploadHandler = new UploadHandlerFile(filePath); // 上传文件

yield return req.SendWebRequest();

if (req.result == UnityWebRequest.Result.Success)
    Debug.Log("上传成功");