Unity网络开发基础——FTP(叁)
学习笔记主要来源于唐老狮的Unity课程,经过个人简单整理而成。笔记总共五个部分:
- Unity网络开发基础——基础知识(壹)
- Unity网络开发基础——TCP与UDP(贰)
- Unity网络开发基础——FTP(叁)
- Unity网络开发基础——HTTP(肆)
- Unity网络开发基础——消息处理(伍)
这个课程的大致内容如下:
- 网络通信中的一些必备理论知识(OSI模型、TCP/IP协议等)。
- 对自定义类对象进行序列化和反序列化。
- 使用Socket,进行TCP、UDP的同步异步通信。
- 处理网络通信中的分包黏包和心跳消息。
- 使用FTP和HTTP协议进行文件的上传和下载。
- 掌握Unity的WWW类和UnityWebRequest类。
- 熟练使用Protobuf,了解自定义协议生成工具的制作原理。
- 了解大小端模式,消息加密原理。
课程不包含游戏的实际游戏开发的内容,比如后端相关知识、同步模式(帧同步、状态同步)等等,只包含网络通信最基本的内容。
六、网络通信——文件传输FTP
(一)FTP概述
1、FTP概念
FTP(File Transfer Protocol) 文件传输协议,是支持Internet文件传输的各种规则所组成的集合,这些规则使用户可以把文件从一台主机拷贝到另一台主机上,此外还提供登录、目录查询以及其他会话控制等功能。
简单来说,FTP文件传输协议就是一个在网络中上传下载文件的一套规则。
2、FTP工作原理
(1)原理
FTP本质是TCP通信,通过FTP传输文件,双发至少需要建立两个TCP连接
- 控制连接:用于传输FTP命令。
- 数据连接:用于传输文件数据。
FTP的数据连接和控制连接方向一般是相反的,比如FTP客户端连接FTP服务器请求下载文件:
- 控制连接方向:客户端主动连接服务器告知其下载命令。
- 数据连接方向:服务端主动连接客户端下发数据。
(2)传输模式
当客户端和FTP服务器建立控制连接后,需要告诉服务器采用哪种传输模式:
- 主动模式(Port模式):服务器主动连接客户端,然后传输文件。
- 被动模式(Passive模式):客户端主动连接服务器,即控制连接和数据连接都由客户端发起。
一般情况下主动模式会受到客户端防火墙影响,所以被动模式使用较多。
(3)数据传输方式
在使用FTP进行数据传输时,有两种数据传输方式:
- ASCII传输方式:适用于传输仅包含英文的命令和参数或者英文文本文件。
- 二进制传输方式:可以指定采用哪种编码传输命令和文件数据,推荐使用。
(4)关于登录
FTP传输文件时,需要客户端登录服务器,获得相应权限后,才能上传或下载文件。如果服务器允许,也可以匿名登录。

3、如何学习
在实际学习过程中,并不需要利用FTP工作原理来实现FTP通信。C#中实现了FTP通信需要用到的相关类:FtpWebRequest、FtpWebResponse、NetworkCredential。
我们只需使用这些类实现如下功能:
- 搭建FTP服务器。
- 上传文件到FTP服务器。
- 从FTP服务器下载文件。
4、FTP的应用
- 游戏中的上传和下载功能
- AB包的上传和下载
(二)搭建FTP服务器
1、搭建FTP服务器的方式
- 使用别人做好的FTP服务器软件(学习阶段建议使用)。
- 自己编写FTP服务器应用程序,基于FTP的工作原理,用Socket中TCP通信来进行编程。
- 将电脑搭建为FTP文件共享服务器。
其中2、3点我们主要做了解即可,这些主要由后端程序员完成。
2、使用Serv-U搭建服务器
这里使用Serv-U进行搭建,当然其他FTP服务器软件也是可以的,这里就不过多赘述了。
- 下载Serv-U软件
- 运行后,执行如下步骤即可搭建FTP服务器
- 创建域 直接不停下一步即可。
- 使用单向加密。
- 创建用于上传下载的 FTP 账号和密码。
(三)FTP相关类
1、NetworkCredential
通信凭证类,用于在FTP文件传输时,设置账号密码。
NetworkCredential credential = new NetworkCredential(userName, password);
2、FtpWebRequest
FTP客户端请求类,主要用于上传、下载、删除服务器上的文件。
- 创建WebRequest
- 例:创建 FTP 服务器
127.0.0.1上的Test.txt文件的请求通道。
- 例:创建 FTP 服务器
FtpWebRequest req = FtpWebRequest.Create(new Uri("ftp://127.0.0.1/Test.txt")) as FtpWebRequest;
- 终止文件传输
req.Abort();
- 获取上传的流
- 这是同步方法,当然还有异步的方法,当然和之前TCP/UDP的异步使用类似,这里不再赘述。
Stream s = req.GetRequestStream();
- 获取FTP服务器响应
- 这是同步方法,当然还有异步的方法,这里不再赘述。
FtpWebResponse res = req.GetResponse() as FtpWebResponse;
- 设置或获取通信凭证
req.Credentials;
- 请求完成时,是否关闭与FTP服务器的控制连接(默认为true,不关闭)
req.KeepAlive;
- 操作命令设置
req.Method;
// 常用的命令
WebRequestMethods.Ftp.DeleteFile; // 删除文件
WebRequestMethods.Ftp.DownloadFile; // 下载文件
WebRequestMethods.Ftp.ListDirectory; // 获取文件简短列表
WebRequestMethods.Ftp.ListDirectoryDetails; // 获取文件详细列表
WebRequestMethods.Ftp.MakeDirectory; // 创建目录
WebRequestMethods.Ftp.RemoveDirectory; // 删除目录
WebRequestMethods.Ftp.UploadFile; // 上传文件
- 是否使用二进制传输
req.UseBinary;
- 重命名
req.RenameTo;
3、FtpWebResponse
FTP服务器响应类,它提供了操作状态以及从服务器下载数据。
该类对象可通过FtpWebRequest中的GetResponse方法获取。使用完毕时,要使用Close释放。
- 释放所有资源
res.Close();
- 获取从FTP服务器下载数据的流
Stream s = res.GetResponseStream();
- 常用成员属性
res.ContentLength; // 接收的数据长度
res.ContentType; // 接受的数据类型
res.StatusCode; // FTP服务器下发的最新状态码
res.StatusDescription; // FTP服务器下发的状态代码的文本
res.BannerMessage; // 登录前建立连接时FTP服务器发送的消息
res.ExitMessage; // FTP会话结束时服务器发送的消息
res.LastModified; // FTP服务器上的文件的上次修改日期和时间
(四)FTP上传
FTP的上传代码如下所示,这里展示的是StreamingAssets/test.png上传到ftp服务器的ftp://127.0.0.1/pic.png中。
上传文件时,将文件流读取的字节数组写入上传流中即可。
// 1、创建一个Ftp连接
FtpWebRequest req = FtpWebRequest.Create(new Uri("ftp://127.0.0.1/pic.png")) as FtpWebRequest;
// 2、设置通信凭证
NetworkCredential credential = new NetworkCredential("pmm", "123456");
req.Credentials = credential;
req.Proxy = null; // 可以Proxy置null,避免服务器同时有http相关服务时,造成冲突
req.KeepAlive = false; // 请求完毕后 是否关闭控制连接,这里选择关闭
// 3、设置操作命令:上传文件
req.Method = WebRequestMethods.Ftp.UploadFile;
// 4、指定传输类型:二进制
req.UseBinary = true;
// 5、获取用于上传的流对象
Stream uploadStream = req.GetRequestStream();
// 6、上传文件
string filePath = Application.streamingAssetsPath + "/test.png"; // 需要上传的文件路径
using (FileStream fs = File.OpenRead(filePath))
{
byte[] bytes = new byte[1024];
// 不断读取要上传的文件中的字节数组,然后存入上传流中
int contentLength = fs.Read(bytes, 0, bytes.Length);
while (contentLength != 0)
{
uploadStream.Write(bytes, 0, contentLength);
contentLength = fs.Read(bytes, 0, bytes.Length);
}
fs.Close();
uploadStream.Close();
}
// 7、获取响应便于检查操作是否成功
FtpWebResponse res = req.GetResponse() as FtpWebResponse;
Debug.Log($"上传完成,状态:{res.StatusDescription}");
res.Close();
(五)FTP下载
FTP的下载代码如下所示,这里展示的是将服务器的ftp://127.0.0.1/Test.txt下载到Application.persistentDataPath中。
下载文件时,将下载流读取的字节数组写入目标文件流中即可。需要注意的是最后response记得要Close。
// 1、创建一个Ftp连接
FtpWebRequest req = FtpWebRequest.Create(new Uri("ftp://127.0.0.1/Test.txt")) as FtpWebRequest;
// 2、设置通信凭证
NetworkCredential credential = new NetworkCredential("pmm", "123456");
req.Credentials = credential;
req.Proxy = null; // 可以Proxy置null,避免服务器同时有http相关服务时,造成冲突
req.KeepAlive = false; // 请求完毕后 是否关闭控制连接,这里选择关闭
// 3、设置操作命令:下载文件
req.Method = WebRequestMethods.Ftp.DownloadFile;
// 4、指定传输类型:二进制
req.UseBinary = true;
// 5、获取用于下载的流对象
FtpWebResponse res = req.GetResponse() as FtpWebResponse; // 请求发送给服务器后返回的信息
Stream downloadStream = res.GetResponseStream();
// 6、下载文件
string filePath = Application.persistentDataPath + "/Test.txt"; // 需要下载到的文件路径
using (FileStream fs = File.Create(filePath))
{
byte[] bytes = new byte[1024];
// 不断读取下载的文件中的字节数组,然后存入目标文件流中
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();
}
res.Close(); // 记得关闭
Debug.Log("下载成功");
(六)FTP其他操作
除了上传下载这两个最常用的操作外,还有一些比较简单常用的操作,核心的信息都在FtpWebResponse中,这里列举几个。
其中有几个变量在外部声明了,这里简单解释下:FTP_PATH:FTP服务器路径、fileName:文件名、directoryName文件夹名、USERNAME:用户名,PASSWORD:密码。
- 删除文件
// 创建请求
FtpWebRequest req = FtpWebRequest.Create(FTP_PATH + fileName) as FtpWebRequest;
// 基础设置
req.Credentials = new NetworkCredential(USERNAME, PASSWORD);
req.Proxy = null;
req.KeepAlive = false;
req.Method = WebRequestMethods.Ftp.DeleteFile; // 设置操作命令:删除文件
req.UseBinary = true;
// 获取请求的响应,即删除文件
FtpWebResponse res = req.GetResponse() as FtpWebResponse;
res.Close();
Debug.Log("删除成功");
- 获取文件大小(单位:字节)
// 创建请求
FtpWebRequest req = FtpWebRequest.Create(FTP_PATH + fileName) as FtpWebRequest;
// 基础设置
req.Credentials = new NetworkCredential(USERNAME, PASSWORD);
req.Proxy = null;
req.KeepAlive = false;
req.Method = WebRequestMethods.Ftp.GetFileSize; // 设置操作命令:获取文件大小
req.UseBinary = true;
// 获取请求的响应
FtpWebResponse res = req.GetResponse() as FtpWebResponse;
long size = res.ContentLength; // 获取大小
res.Close();
Debug.Log("获取文件大小成功");
- 创建文件夹
// 创建请求
FtpWebRequest req = FtpWebRequest.Create(FTP_PATH + directoryName) as FtpWebRequest;
// 基础设置
req.Credentials = new NetworkCredential(USERNAME, PASSWORD);
req.Proxy = null;
req.KeepAlive = false;
req.Method = WebRequestMethods.Ftp.MakeDirectory; // 设置操作命令:创建文件夹
req.UseBinary = true;
// 获取请求的响应
FtpWebResponse res = req.GetResponse() as FtpWebResponse;
res.Close();
Debug.Log("创建文件夹成功");
- 获取文件列表
// 创建请求
FtpWebRequest req = FtpWebRequest.Create(FTP_PATH + directoryName) as FtpWebRequest;
// 基础设置
req.Credentials = new NetworkCredential(USERNAME, PASSWORD);
req.Proxy = null;
req.KeepAlive = false;
req.Method = WebRequestMethods.Ftp.ListDirectory; // 设置操作命令:获取文件夹的文件列表
req.UseBinary = true;
// 获取请求的响应
FtpWebResponse res = req.GetResponse() as FtpWebResponse;
StreamReader reader = new StreamReader(res.GetResponseStream()); // 获取文本读取流,方便一行行读取
List<string> fileList = new List<string>();
string line = reader.ReadLine();
while (line != null)
{
fileList.Add(line);
line = reader.ReadLine();
}
res.Close();
Debug.Log("获取文件列表成功");