摘要
FTP的目标是提高文件的共享性,提供非直接使用远程计算机,使存储介质对用户透明和可靠高效地传送数据。虽然我们也可以手工使用它,但是它的主要作用是供程序使用的。本文详细介绍了FTP协议内容及FTP工作原理以及基于FTP协议通过C#实现FTP客户端和FTP服务端。
1 FTP协议
文件传输协议FTP(File Transfer Protocol)是因特网中使用最广泛的文件传输协议。FTP使用交互式的访问,允许客户指定文件的类型和格式(如指明是否使用ASCII码),并允许文件具有存取权限(如访问文件的用户必须经过授权,并输入有效的口令)。
文件传输协议有基于TCP的FTP和基于UDP的简单文件传输协议TFTP,它们都是文件共享协议中的一大类,即复制整个文件,其特点是:若要存取一个文件,就必须先获得一个本地的文件副本。如果要修改文件,只能对文件的副本进行修改,然后再将修改后的文件传回到原节点。
2 FTP服务基本工作原理
FTP屏蔽了各计算机系统的细节,因而适合在异构网络中任意计算机之间传送文件。FTP只提供文件传送的一些基本服务,它使用TCP可靠地运输服务,FTP主要功能是减小或消除在不同系统下处理文件的不兼容性。
FTP使用客户端-服务器模型,一个FTP服务器进程可以为多个客户进程提供服务。FTP服务器有两大部分组成:一个主进程,负责接受新的请求;还有若干从属进程,负责处理单个请求。主进程工作步骤
- 打开熟知端口(21),使客户进程能够连接上
- 等待客户进程发送连接请求
- 启动从属进程处理客户进程发送的连接请求,从属进程处理完请求后结束,从属进程在运行期间可能根据需要可创建其他一些子进程
- 回到等待状态,继续接受其他客户进程发起的请求,主进程与从属进程的处理是并发进行的
FTP工作时情况
FTP控制连接在整个会话期间都保持打开,只用来发送连接/传送请求。当客户进程向服务器发送连接请求时,寻找连接服务器进程的熟知端口21,同时还要告诉服务器进程自己的另一个端口号码,用于建立数据传送连接。接着,服务器进程用自己传送数据的熟知端口20与客户进程所提供的端口号码建立数据传送连接,FTP使用了2个不同的端口号,所以数据连接和控制连接不会混乱。
2.1 FTP数据表示
FTP协议规定了控制协议传送与存储的多种选择,在以下4个方面必须做出一个选择。
- 文件类型:ASCII码文件(默认的)/ 图像文件类型(二进制的)/ 本地文件类型(用于在具有不同字节大小主机间传送二进制数据)
- 格式控制:该选项针对ASCII类型文件适用,非打印(默认选择,文件中不包含垂直格式信息)/ 远程登录格式控制
- 结构:文件结构(默认选择,文件被认为是一个连续的字节流,不存在内部的文件结构)/ 记录结构(用于文本文件)
- 传输方式:流方式(模式选择,文件以字节流方式传输,对于文件结构,发方在文件尾提示关闭数据连接,对于记录结构,有专用的两字节序列码记录结束和文件结束)/ 块方式(文件以一系列块来传送,每块前面有一个或多个首部字节)/ 压缩方式
2.2 FTP命令和应答
命令和应答在客户和服务器的控制连接上以 NVT ASCII码形式传送。这就要求在每行结尾都要返回C R、 L F对(也就是每个命令或每个应答)。这些命令都是3或4个字节的大写ASCII字符,其中一些带选项参数。从客户向服务器发送的FTP命令超过30种。下图是比较常用的几种命令:
应答都是ASCII码形式的3位数字,并跟有报文选项。其原因是软件系统需要根据数字代码来决定如何应答,而选项串是面向人工处理的。由于客户通常都要输出数字应答和报文串,一个可交互的用户可以通过阅读报文串(而不必记忆所有数字回答代码的含义)来确定应答的含义。
2.3 FTP工作模式
FTP有两种工作模式,分别是主动模式(PORT)和被动模式(PASV)两种模式,这两种模式是按照FTP服务器的“角度”来说的,更通俗一点说就是:在传输数据时,如果是服务器主动连接客户端,那就是主动模式;如果是客户端主动连接服务器,那就是被动模式。
- 主动模式 (PORT)
主动模式下,客户端随机打开一个大于 1024 的端口向服务器的命令端口 P,即 21 端口,发起连接,同时开放N +1 端口监听,并向服务器发出 “port N+1” 命令,由服务器从它自己的数据端口 (20) 主动连接到客户端指定的数据端口 (N+1)。FTP 的客户端只是告诉服务器自己的端口号,让服务器来连接客户端指定的端口。对于客户端的防火墙来说,这是从外部到内部的连接,可能会被阻塞。
为了解决服务器发起到客户的连接问题,有了另一种 FTP 连接方式,即被动方式。命令连接和数据连接都由客户端发起,这样就解决了从服务器到客户端的数据端口的连接被防火墙过滤的问题。
- 被动模式 (PASV)
被动模式下,当开启一个 FTP 连接时,客户端打开两个任意的本地端口 (N > 1024 和 N+1) 。第一个端口连接服务器的 21 端口,提交 PASV 命令。然后,服务器会开启一个任意的端口 (P > 1024 ),返回如“227 entering passive mode (127,0,0,1,4,18)”。这意味着在服务器上有一个端口被开放。客户端收到命令取得端口号之后, 会通过 N+1 号端口连接服务器的端口 P,然后在两个端口之间进行数据传输。
3 实现FTP客户端和服务端程序
FTP客户端部分代码,实现上传文件和下载文件:
/// <summary>
/// 文件上传
/// </summary>
/// <param name="fileLocalPath">文件在本地的路径</param>
/// <param name="filePathInfo">文件将上传到服务器的路径信息</param>
/// <returns></returns>
public void UploadFile(string fileLocalPath,string destRelativePath)
{
try
{
FileInfo fileInfo = new FileInfo(fileLocalPath);
string uri = GetUri(destRelativePath);
FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.UploadFile);
request.ContentLength = fileInfo.Length;
byte[] buffer = new byte[BufLength];
FileStream fileStream = fileInfo.OpenRead();
Stream resquestStream = request.GetRequestStream();
int contentLength = fileStream.Read(buffer, 0, BufLength);
while (contentLength != 0)
{
resquestStream.Write(buffer, 0, contentLength);
contentLength = fileStream.Read(buffer, 0, BufLength);
}
resquestStream.Close();
fileStream.Close();
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
if (response == null)
{
throw new Exception("服务器未响应");
}
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 下载文件
/// </summary>
/// <param name="sourcePath">文件在服务器的路径信息</param>
/// <param name="destPath">文件下载到本地的路径</param>
/// <returns></returns>
public void DownloadFile(string sourceRelaticePath,string destAbsolutionPath)
{
try
{
string uri = GetUri(sourceRelaticePath);
FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.DownloadFile);
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
Stream responseStream = response.GetResponseStream();
if (response == null)
{
throw new Exception("服务器未响应");
}
FileInfo fileInfo = new FileInfo(destAbsolutionPath);
if (!fileInfo.Directory.Exists)
{
fileInfo.Directory.Create();
}
FileStream fileStream = fileInfo.Create();
int buflength = 8196;
byte[] buffer = new byte[buflength];
int bytesRead = 1;
while (bytesRead != 0)
{
bytesRead = responseStream.Read(buffer, 0, buflength);
fileStream.Write(buffer, 0, bytesRead);
}
responseStream.Close();
fileStream.Close();
}
catch
{
throw;
}
}
private FtpWebRequest CreateFtpWebRequest(string uri, string command)
{
FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(uri);
request.Credentials = netCrediental;
request.KeepAlive = true;
request.UseBinary = true;
request.Method = command;
return request;
}
private string GetUri(string path)
{
if (string.IsNullOrEmpty(path))
{
return serverUri;
}
return string.Concat(serverUri, "/", path);
}
不同系统在系统自带服务中都提供了对应平台的FTP服务器程序,用户可自行启用。在Window系统下用户可在启用或关闭Windows功能中启用FTP服务器,然后在通过IIS控制台添加FTP站点启动FTP服务器。
本项目中FTP服务器采用自行实现的服务器,实现了FTP协议常用的命令。
完整项目CSDN路径:https://download.csdn.net/download/uiuan00/11966603