51.网络通信-超文本传输HTTP-上传数据
51.1 知识点
上传文件到 HTTP 资源服务器需要遵守的规则
- ContentType = “multipart/form-data; boundary=边界字符串”;
- 上传的数据必须按照格式写入流中
// --边界字符串
// Content-Disposition: form-data; name="字段名字,之后写入的文件2进制数据和该字段名对应";filename="传到服务器上使用的文件名"
// Content-Type:application/octet-stream(由于我们传2进制文件 所以这里使用2进制)
// 空一行
// (这里直接写入传入的内容 即文件数据体)
// --边界字符串--
- 保证服务器允许上传
- 写入流前需要先设置 ContentLength 内容长度
HFS服务器可以设置所有人可上传,也可以设置用户上传权限,否则可能没有上传权限
上传文件
创建 HttpWebRequest 对象,不需写要上传的文件名
//1.创建HttpWebRequest对象 不用写要上传的文件名
HttpWebRequest httpWebRequest = HttpWebRequest.Create("http://192.168.1.101:8000/HTTPRoot/") as HttpWebRequest;
相关设置(请求类型、内容类型、超时、身份验证等)
//2.相关设置(请求类型,内容类型,超时,身份验证等)
httpWebRequest.Method = WebRequestMethods.Http.Post;
httpWebRequest.ContentType = "multipart/form-data;boundary=MrTao";//复合文件类型 自定义边界字符串是MrTao
httpWebRequest.Timeout = 500000;
httpWebRequest.Credentials = new NetworkCredential("MrTao", "MrTao");//服务器身份验证信息
httpWebRequest.PreAuthenticate = true;//先验证身份 再上传数据 标识设置为True
按格式拼接字符串并转为字节数组用于上传
文件数据前的头部信息
//3-1.文件数据前的头部信息
//其中\r\n是多平台换行 自定义边界字符串是MrTao 字段名字传入file 声明是文件 字段名可以自定义的
// --边界字符串
// Content-Disposition: form-data; name="字段名字,之后写入的文件2进制数据和该字段名对应";filename="传到服务器上使用的文件名"
// Content-Type:application/octet-stream(由于我们传2进制文件 所以这里使用2进制)
// 空一行
string head =
"--MrTao\r\n" +
"Content-Disposition:form-data;name=\"file\";filename=\"http上传的文件.jpg\"\r\n" +
"Content-Type:application/octet-stream\r\n\r\n";
//头部拼接字符串规则信息的字节数组
byte[] headBytes = Encoding.UTF8.GetBytes(head);
结束的边界信息
//3-2.结束的边界信息
// --边界字符串--
byte[] endBytes = Encoding.UTF8.GetBytes("\r\n--MrTao--\r\n");
写入上传流
//4.写入上传流
using (FileStream localFileStream = File.OpenRead(Application.streamingAssetsPath + "/test.png"))
{
//4-1.设置上传长度
//总长度 是前部分头部字符串 + 文件本身有多大 + 后部分边界字符串
httpWebRequest.ContentLength = headBytes.Length + localFileStream.Length + endBytes.Length;
//用于上传的流
Stream uploadRequestStream = httpWebRequest.GetRequestStream();
//4-2.先写入前部分头部信息
uploadRequestStream.Write(headBytes, 0, headBytes.Length);
//4-3.再写入文件数据
byte[] bytes = new byte[2048];//字节数组
//不停的读取
int contentLength = localFileStream.Read(bytes, 0, bytes.Length);
while (contentLength != 0)
{
uploadRequestStream.Write(bytes, 0, contentLength);
contentLength = localFileStream.Read(bytes, 0, bytes.Length);
}
//4-4.在写入结束的边界信息
uploadRequestStream.Write(endBytes, 0, endBytes.Length);
uploadRequestStream.Close();
localFileStream.Close();
}
上传数据,获取响应
//5.上传数据,获取响应
HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
if (httpWebResponse.StatusCode == HttpStatusCode.OK)
print("上传通信成功");
else
print("上传失败" + httpWebResponse.StatusCode);
测试结果
总结
HTTP 上传文件相对比较麻烦,需要按照指定的规则进行内容拼接达到上传文件的目的。其中相对重要的知识点是上传文件时的规则:
- 边界字符串
- Content-Disposition: form-data; name=”file”; filename=”传到服务器上使用的文件名”
- Content-Type: application/octet-stream(由于我们传2进制文件,所以这里使用2进制)
- 空行
- (这里直接写入传入的内容)
- 边界字符串
关于更多规则,可前往官网查看详细说明。
有关 ContentType 的更多内容,可前往 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Type。
有关媒体类型的更多内容,可前往 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types。
有关 Content-Disposition 的更多内容,可前往 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition。
51.2 知识点代码
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using UnityEngine;
public class Lesson51_网络通信_超文本传输HTTP_上传数据 : MonoBehaviour
{
void Start()
{
#region 知识点一 上传文件到HTTP资源服务器需要遵守的规则
//上传文件时内容的必备规则
// 1:ContentType = "multipart/form-data; boundary=边界字符串";
// 2:上传的数据必须按照格式写入流中
// --边界字符串
// Content-Disposition: form-data; name="字段名字,之后写入的文件2进制数据和该字段名对应";filename="传到服务器上使用的文件名"
// Content-Type:application/octet-stream(由于我们传2进制文件 所以这里使用2进制)
// 空一行
// (这里直接写入传入的内容 即文件数据体)
// --边界字符串--
// 3:保证服务器允许上传
// 4:写入流前需要先设置ContentLength内容长度
#endregion
#region 知识点二 上传文件
//1.创建HttpWebRequest对象 不用写要上传的文件名
HttpWebRequest httpWebRequest = HttpWebRequest.Create("http://192.168.1.101:8000/HTTPRoot/") as HttpWebRequest;
//2.相关设置(请求类型,内容类型,超时,身份验证等)
httpWebRequest.Method = WebRequestMethods.Http.Post;
httpWebRequest.ContentType = "multipart/form-data;boundary=MrTao";//复合文件类型 自定义边界字符串是MrTao
httpWebRequest.Timeout = 500000;
httpWebRequest.Credentials = new NetworkCredential("MrTao", "MrTao");//服务器身份验证信息
httpWebRequest.PreAuthenticate = true;//先验证身份 再上传数据 标识设置为True
//3.按格式拼接字符串并且转为字节数组之后用于上传
//3-1.文件数据前的头部信息
//其中\r\n是多平台换行 自定义边界字符串是MrTao 字段名字传入file 声明是文件 字段名可以自定义的
// --边界字符串
// Content-Disposition: form-data; name="字段名字,之后写入的文件2进制数据和该字段名对应";filename="传到服务器上使用的文件名"
// Content-Type:application/octet-stream(由于我们传2进制文件 所以这里使用2进制)
// 空一行
string head =
"--MrTao\r\n" +
"Content-Disposition:form-data;name=\"file\";filename=\"http上传的文件.png\"\r\n" +
"Content-Type:application/octet-stream\r\n\r\n";
//头部拼接字符串规则信息的字节数组
byte[] headBytes = Encoding.UTF8.GetBytes(head);
//3-2.结束的边界信息
// --边界字符串--
byte[] endBytes = Encoding.UTF8.GetBytes("\r\n--MrTao--\r\n");
//4.写入上传流
using (FileStream localFileStream = File.OpenRead(Application.streamingAssetsPath + "/test.png"))
{
//4-1.设置上传长度
//总长度 是前部分头部字符串 + 文件本身有多大 + 后部分边界字符串
httpWebRequest.ContentLength = headBytes.Length + localFileStream.Length + endBytes.Length;
//用于上传的流
Stream uploadRequestStream = httpWebRequest.GetRequestStream();
//4-2.先写入前部分头部信息
uploadRequestStream.Write(headBytes, 0, headBytes.Length);
//4-3.再写入文件数据
byte[] bytes = new byte[2048];//字节数组
//不停的读取
int contentLength = localFileStream.Read(bytes, 0, bytes.Length);
while (contentLength != 0)
{
uploadRequestStream.Write(bytes, 0, contentLength);
contentLength = localFileStream.Read(bytes, 0, bytes.Length);
}
//4-4.在写入结束的边界信息
uploadRequestStream.Write(endBytes, 0, endBytes.Length);
uploadRequestStream.Close();
localFileStream.Close();
}
//5.上传数据,获取响应
HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
if (httpWebResponse.StatusCode == HttpStatusCode.OK)
print("上传通信成功");
else
print("上传失败" + httpWebResponse.StatusCode);
#endregion
#region 总结
//HTTP上传文件相对比较麻烦
//需要按照指定的规则进行内容拼接达到上传文件的目的
//其中相对重要的知识点是
//上传文件时的规则
// --边界字符串
// Content-Disposition: form-data; name="file";filename="传到服务器上使用的文件名"
// Content-Type:application/octet-stream(由于我们传2进制文件 所以这里使用2进制)
// 空行
// (这里直接写入传入的内容)
// --边界字符串--
//关于其更多的规则,可以前往官网查看详细说明
//关于ContentType更多内容可以前往
//https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Type
//关于媒体类型可以前往
//https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types
//关于Content-Disposition更多内容可以前往
//https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition
#endregion
}
}
51.3 练习题
在之前的Http管理器当中,封装一个上传的相关方法,同样还是以多线程进行上传,避免影响主线程逻辑
在Http管理器声明上传文件方法
private string USER_NAME = "MrTao";
private string PASS_WORD = "MrTao";
/// <summary>
/// 上传文件
/// </summary>
/// <param name="fileName">传到远端服务器上的文件名</param>
/// <param name="loacalFilePath">本地的文件路径</param>
/// <param name="action">上传结束后的回调函数</param>
public async void UpLoadFile(string fileName, string loacalFilePath, UnityAction<HttpStatusCode> action)
{
//上面状态码变量 默认请求失败
HttpStatusCode httpStatusCodeResult = HttpStatusCode.BadRequest;
await Task.Run(() =>
{
try
{
// 创建HTTP请求对象,用于与服务器通信
HttpWebRequest httpWebRequest = HttpWebRequest.Create(HTTP_PATH) as HttpWebRequest;
// 设置HTTP请求方法为POST,用于上传数据
httpWebRequest.Method = WebRequestMethods.Http.Post;
// 设置HTTP请求的内容类型为multipart/form-data,其中boundary参数指定分隔符
httpWebRequest.ContentType = "multipart/form-data;boundary=MrTao";
// 设置HTTP请求的超时时间为500,000毫秒(500秒)
httpWebRequest.Timeout = 500000;
// 设置HTTP请求的凭据,包括用户名和密码
httpWebRequest.Credentials = new NetworkCredential(USER_NAME, PASS_WORD);
// 启用预身份验证,以在请求之前进行身份验证
httpWebRequest.PreAuthenticate = true;
//拼接字符串 头部
string head =
"--MrTao\r\n" +
"Content-Disposition:form-data;name=\"file\";filename=\"{0}\"\r\n" +
"Content-Type:application/octet-stream\r\n\r\n";
//替换文件名
head = string.Format(head, fileName);
//得到头部字节数组
byte[] headBytes = Encoding.UTF8.GetBytes(head);
//尾部的边界字符串
byte[] endBytes = Encoding.UTF8.GetBytes("\r\n--MrTao--\r\n");
//读入指定路径文件资源
using (FileStream localStream = File.OpenRead(loacalFilePath))
{
//设置长度
httpWebRequest.ContentLength = headBytes.Length + localStream.Length + endBytes.Length;
//写入流
Stream uploadRequestStream = httpWebRequest.GetRequestStream();
//写入头部
uploadRequestStream.Write(headBytes, 0, headBytes.Length);
//创建字节数组 不停的写入上传文件
byte[] bytes = new byte[4096];
int contentLenght = localStream.Read(bytes, 0, bytes.Length);
while (contentLenght != 0)
{
uploadRequestStream.Write(bytes, 0, contentLenght);
contentLenght = localStream.Read(bytes, 0, bytes.Length);
}
//写入尾部
uploadRequestStream.Write(endBytes, 0, endBytes.Length);
//关闭流
uploadRequestStream.Close();
localStream.Close();
}
//得到响应结果
HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
//让外部去处理结果
httpStatusCodeResult = httpWebResponse.StatusCode;
//关闭流
httpWebResponse.Close();
}
catch (WebException w)
{
Debug.Log("上传出错" + w.Status + w.Message);
}
});
//执行回调
action?.Invoke(httpStatusCodeResult);
}
进行上传测试
HttpManager.Instance.UpLoadFile("HttpManager上传.png", Application.streamingAssetsPath + "/test.png", (code) =>
{
if (code == HttpStatusCode.OK)
print("上传指令成功");
else
print("上传指令失败" + code);
});
测试结果
51.4 练习题代码
HttpManager
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;
public class HttpManager : BaseSingletonInCSharp<HttpManager>
{
private string HTTP_PATH = "http://192.168.1.101:8000/HTTPRoot/";
private string USER_NAME = "MrTao";
private string PASS_WORD = "MrTao";
/// <summary>
/// 下载指定文件到本地指定路径中
/// </summary>
/// <param name="fileName">远程文件名</param>
/// <param name="loacFilePath">本地路径</param>
/// <param name="action">下载结束后的回调函数</param>
public async void DownLoadFile(string fileName, string loacFilePath, UnityAction<HttpStatusCode> action)
{
// 创建一个临时变量,用于存储HTTP响应的状态码,默认为OK。
HttpStatusCode httpStatusCodeResult = HttpStatusCode.OK;
// 使用异步任务来执行下载操作,以避免阻塞主线程。
await Task.Run(() =>
{
try
{
// 判断文件是否存在 Head
// 1. 创建HTTP连接对象,用于发送HTTP请求到服务器。
HttpWebRequest httpWebRequest = HttpWebRequest.Create(HTTP_PATH + fileName) as HttpWebRequest;
// 2. 设置请求类型为HEAD,这样我们可以检查文件是否存在,以及设置其他相关参数。
httpWebRequest.Method = WebRequestMethods.Http.Head;
httpWebRequest.Timeout = 2000;
// 3. 发送请求并获取HTTP响应。
HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
// 如果文件存在才下载
if (httpWebResponse.StatusCode == HttpStatusCode.OK)
{
Debug.Log("文件存在且可用");
Debug.Log(httpWebResponse.ContentLength); // 打印文件大小
Debug.Log(httpWebResponse.ContentType); // 打印文件的MIME类型
// 关闭判断文件是否存在的请求。
httpWebResponse.Close();
// 下载文件 Get
// 1. 创建新的HTTP连接对象,用于下载文件。
httpWebRequest = HttpWebRequest.Create(HTTP_PATH + fileName) as HttpWebRequest;
// 2. 设置请求类型为GET,以获取文件内容,并设置其他相关参数。
httpWebRequest.Method = WebRequestMethods.Http.Get;
httpWebRequest.Timeout = 2000;
// 3. 发送请求并获取HTTP响应。
httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
// 4. 存储数据到本地
if (httpWebResponse.StatusCode == HttpStatusCode.OK)
{
// 创建本地文件并准备将下载的数据写入其中。
using (FileStream fileStream = File.Create(loacFilePath))
{
// 获取HTTP响应的数据流,从中读取数据。
Stream downloadResponseStream = httpWebResponse.GetResponseStream();
// 创建一个字节数组,用于存储从数据流中读取的数据。
byte[] bytes = new byte[4096];
// 从数据流中读取一部分数据,并返回读取的字节数。
int contentLength = downloadResponseStream.Read(bytes, 0, bytes.Length);
// 使用循环将数据写入本地文件,直到数据流读取完毕。
while (contentLength != 0)
{
fileStream.Write(bytes, 0, contentLength);
contentLength = downloadResponseStream.Read(bytes, 0, bytes.Length);
}
// 关闭本地文件和数据流。
fileStream.Close();
downloadResponseStream.Close();
}
// 如果下载成功,将HTTP响应状态码设置为OK。
httpStatusCodeResult = HttpStatusCode.OK;
}
else
{
// 如果下载失败,将HTTP响应状态码存储在 httpStatusCodeResult 变量中。
httpStatusCodeResult = httpWebResponse.StatusCode;
}
}
else
{
// 如果文件不存在,将HTTP响应状态码存储在 httpStatusCodeResult 变量中。
httpStatusCodeResult = httpWebResponse.StatusCode;
}
// 关闭HTTP响应。
httpWebResponse.Close();
}
catch (WebException w)
{
// 如果发生Web异常,将状态码设置为服务器内部错误,并记录错误消息。
httpStatusCodeResult = HttpStatusCode.InternalServerError;
Debug.Log("下载出错" + w.Message + w.Status);
}
});
// 调用传递进来的 UnityAction 委托,将HTTP响应状态码传递给调用者。
action?.Invoke(httpStatusCodeResult);
}
/// <summary>
/// 上传文件
/// </summary>
/// <param name="fileName">传到远端服务器上的文件名</param>
/// <param name="loacalFilePath">本地的文件路径</param>
/// <param name="action">上传结束后的回调函数</param>
public async void UpLoadFile(string fileName, string loacalFilePath, UnityAction<HttpStatusCode> action)
{
//上面状态码变量 默认请求失败
HttpStatusCode httpStatusCodeResult = HttpStatusCode.BadRequest;
await Task.Run(() =>
{
try
{
// 创建HTTP请求对象,用于与服务器通信
HttpWebRequest httpWebRequest = HttpWebRequest.Create(HTTP_PATH) as HttpWebRequest;
// 设置HTTP请求方法为POST,用于上传数据
httpWebRequest.Method = WebRequestMethods.Http.Post;
// 设置HTTP请求的内容类型为multipart/form-data,其中boundary参数指定分隔符
httpWebRequest.ContentType = "multipart/form-data;boundary=MrTao";
// 设置HTTP请求的超时时间为500,000毫秒(500秒)
httpWebRequest.Timeout = 500000;
// 设置HTTP请求的凭据,包括用户名和密码
httpWebRequest.Credentials = new NetworkCredential(USER_NAME, PASS_WORD);
// 启用预身份验证,以在请求之前进行身份验证
httpWebRequest.PreAuthenticate = true;
//拼接字符串 头部
string head =
"--MrTao\r\n" +
"Content-Disposition:form-data;name=\"file\";filename=\"{0}\"\r\n" +
"Content-Type:application/octet-stream\r\n\r\n";
//替换文件名
head = string.Format(head, fileName);
//得到头部字节数组
byte[] headBytes = Encoding.UTF8.GetBytes(head);
//尾部的边界字符串
byte[] endBytes = Encoding.UTF8.GetBytes("\r\n--MrTao--\r\n");
//读入指定路径文件资源
using (FileStream localStream = File.OpenRead(loacalFilePath))
{
//设置长度
httpWebRequest.ContentLength = headBytes.Length + localStream.Length + endBytes.Length;
//写入流
Stream uploadRequestStream = httpWebRequest.GetRequestStream();
//写入头部
uploadRequestStream.Write(headBytes, 0, headBytes.Length);
//创建字节数组 不停的写入上传文件
byte[] bytes = new byte[4096];
int contentLenght = localStream.Read(bytes, 0, bytes.Length);
while (contentLenght != 0)
{
uploadRequestStream.Write(bytes, 0, contentLenght);
contentLenght = localStream.Read(bytes, 0, bytes.Length);
}
//写入尾部
uploadRequestStream.Write(endBytes, 0, endBytes.Length);
//关闭流
uploadRequestStream.Close();
localStream.Close();
}
//得到响应结果
HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
//让外部去处理结果
httpStatusCodeResult = httpWebResponse.StatusCode;
//关闭流
httpWebResponse.Close();
}
catch (WebException w)
{
Debug.Log("上传出错" + w.Status + w.Message);
}
});
//执行回调
action?.Invoke(httpStatusCodeResult);
}
}
Lesson51_练习题
using System.Collections;
using System.Collections.Generic;
using System.Net;
using UnityEngine;
public class Lesson51_练习题 : MonoBehaviour
{
void Start()
{
HttpManager.Instance.UpLoadFile("HttpManager上传.png", Application.streamingAssetsPath + "/test.png", (code) =>
{
if (code == HttpStatusCode.OK)
print("上传指令成功");
else
print("上传指令失败" + code);
});
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com