引言:

  在本专题中将和大家分享如何自己实现一个简单的FTP服务器。在我们平时的上网过程中,一般都是使用FTP的客户端来对商家提供的服务器进行访问(上传、下载文件),例如我们经常用到微软的SkyDrive网盘,115网盘等,然而我们经常用到的都是网页版本的,网页版本和客户端版本的不同,网页版本的FTP客户端,它与服务器的交流是使用HTTP协议发出对服务器的请求的,而客户端版本采用的是FTP协议发出命令对服务器进行请求。然后我们接触到FTP服务器却很少的, 所以本专题中将和大家介绍下如何实现一个FTP服务器(不要觉得服务器很深奥一样的,大家可以简单的认为服务器也是一个程序,该程序是对客户端发来的请求做处理的,请求大家可以简单理解为字符串,从这个角度看, 服务器程序就是一个对字符串解析的过程。),也是为后面的一个专题做一个铺垫,因为后面专题将和大家介绍下FTP客户端——文件上传下载器,有了自己自定义的FTP服务器后, 自定义的FTP客户端就可以对自定义的FTP服务器进行访问,使两者形成一个完整的软件,从而也让大家对基于FTP协议的工具有一个初步的了解。

一、基于FTP协议的客户端和服务器是如何"沟通的"?

  FTP客户端和FTP服务器之间的“沟通”分为四个阶段的:

  1. 启动FTP

  客户通过FTP客户端软件,发起FTP交互式的命令,就是告诉服务器(也就是一台电脑,服务器上与一个程序(FTP服务)会接收命令,并解析发来的命令,然后发出回复信息)说:“我想和你聊聊天,可以吗?”  

  2. 建立控制连接

  客户端TCP层根据客户给出的服务器IP地址,向服务器提供FTP服务的21号端口发出主动建立连接的请求,服务器接收到请求后,通过3次握手之后,客户端和服务器之间就建立一个TCP连接(就是一条通道,就好比生活中马路,有了马路之后车才能够在两地之间运送东西),之后,所有用户发出的FTP命令和服务器的回应都是通过该连接来传送的, 所以也把这个TCP连接叫做控制连接,控制连接在用户退出之前一直存在。

  3. 建立数据连接和进行文件传输

  现在客户端和服务器端已经建立聊天的通道了(控制连接),但是两者聊天过程中如果互相想赠送礼物要怎么办呢?(这里形象的把客户端和服务器端文件的传输比喻两个人通过聊天后互相赠送礼物的过程),此时我们就需要另外一条马路(数据连接)来进行“礼物的赠送”了,具体赠送礼物的过程如下:

  1. 客户端通过控制连接向服务器发送一个上传文件的命令时,会自己分配一个临时的TCP端口号。
  2. 客户端通过控制连接向服务器发送一个命令(下面将会介绍的PORT命令)来告诉服务器自己的IP地址和临时的端口号,然后再发送一条上传文件的命令(可以理解为——客户端要送礼物给服务器时,实际上不是简单的发送一个送礼物命令的,在这之前还需要发送一条自我介绍命令(就是告诉服务器自己的IP地址和端口号)来告诉服务器自己就是刚刚和它聊天的那位,这也很符合我们日常送礼物的流程的,一般大家接到礼物都要弄明白送礼物的人是谁,是不是自己认识的)
  3. 服务器接收到客户端的IP地址和临时端口号后,以这个IP地址和端口号为目标,使用服务器上的20端口(该端口是用来传输数据的端口)向客户端发出主动建立连接的请求。
  4. 客户端收到请求后,通过三次握手后就与服务器之间建立了另外一条TCP连接——数据连接,即用来互相送礼物的通道。
  5. 客户端在自己的文件系统中选择要赠送(上传)的文件
  6. 客户端将文件写入到文件传输进程中(写入网络流中)
  7. 服务器端将传输来的文件在服务器端的文件系统中进行存储
  8. 文件传输完成后,由服务器主动关闭该数据的连接

  4 关闭FTP

  当用户退出FTP时,通关客户端发送退出命令,之后控制连接被关闭,FTP服务结束。

二、从上面的沟通过程中你明白了什么?

  从上面客户端与服务器端的沟通过程中,这里可以概括几点:

  (1)客户端与服务器端进行交互过程中,传输层使用的是TCP协议而不是其他传输层协议

  (2)沟通过程有两条TCP连接——一条是控制连接,即传输命令和响应信息的通道,另一条是数据连接,即传输文件的马路,并且必须先有控制连接才能建立数据连接,因为要进行文件传输首先必须知道客户的IP地址和端口号,这个过程就是通过控制连接传送的命令来告知服务器客户端的IP地址和端口号,之后再在两者之间建立数据连接来传输文件

  (3)在服务器端,控制连接(端口号为21)和数据连接(端口号 为20)使用了不同的端口号

三、赠送礼物的方式?——文件传输模式

  客户端与FTP服务器建立数据连接之后,首先需要告诉服务器采用哪种文件传输模式,FTP提供了两种文件传输模式,一种是主动(Port)模式,另一种是被动(Passive)模式。

  主动模式——服务器向客户端发起数据连接请求,被动模式——客户端向服务器发起数据请求。

然而两种模式有什么相同点和不同点呢?

  相同点: 服务器都使用21号端口进行用户验证和管理

  不同点: 传送文件数据的方式不一样,主动模式的FTP服务器数据端口固定在20,而被动模式的FTP服务器数据端口则在1025~65535之间的随机数。

3.1 主动模式

  主动模式——服务器主动连接客户端,然后传输文件,在这种模式下,FTP客户端先用一个端口N(N>1024)向服务器的21号端口发起控制连接,连接成功后,在发出PORT N+1命令告诉服务器自己监听的端口为N+1;服务器接受到该命令后,用一个新的数据端口(20号端口)与客户端的端口N+1建立连接,然后进行文件传输,而客户端则通过监听N+1端口接受文件数据。

  注意: 采用主动模式存在一个问题,如果客户端安装了防火墙或在内网时,由于防火墙一般不允许接受外部发起的标准端口以外的连接请求,因此外部FTP服务器就无法使用主动模式穿过防火墙主动连接客户端(这里与客户端连接的端口为N+1(N>1024),非标准端口),从而造成无法传送文件数据,此时就需要采用被动模式传送文件了。

3.2 被动模式

  被动模式——服务器被动接受客户端连接请求,即控制连接请求和数据连接请求都是由客户端发起,在这种模式下,FTP客户端先随机开始一个端口N向服务器的21号端口发起控制连接,然后向服务器发送PASV命令。服务器收到该命令后,会用一个新的端口P(P>1024)进行监听,同时将该端口号告诉客户端,客户端接受到响应命令后,再通过新的端口N+1连接服务器的端口P,然后进行文件数据传输。

  注意:采用被动模式与主动模式也存在相同的问题,如果服务器安装了防火墙,客户端同样可能无法与服务器端的端口P建立数据请求,因为该请求可能会被防火墙过滤掉。在实际应用中,服务器一般指定一个端口范围,允许客户端与该范围内的端口建立数据连接,而不再这个范围内的端口会被服务器的防火墙过滤掉,从而在一定程度上消除了针对服务器的恶意攻击。

四、 FTP协议中有哪些命令的?

  协议简单说就是一个规范,就好比打牌一样,制定一个大家都能明白的规则,斗地主的规则被大家都认可的,但是私下我们也可以自定义规则来玩的(例如说三个只能带一个等这样的规则),同样FTP规则也是大家都认可的一个协议,我们当然也可以自定义协议。

  由于.Net平台下目前还没有提供对FTP服务器端开发的类库,因此要实现一个FTP服务器端的应用程序,就必须了解FTP协议的详细内容。

4.1 FTP命令有哪些?

FTP 协议中规定了一些大家都认识的命令和组成。FTP协议中的命令都由3~4个字母组成,命令与参数之间用空格隔开,每个命令用回车换行结束。

(1)访问命令有:

USER命令——格式为:USER <username>, 指定登录的用户名,以便服务器进行身份验证。这个命令通常是控制连接后第一个发出的命令

PASS命令——格式为:PASS <password>, 指定用户密码,该命令必须跟在登录用户名命令之后。

REIN命令——格式为:REIN, 表示重新初始化用户信息,该命令终止当前USER的传输,同时终止正在传输的数据,然后重置所有参数,并打开控制连接,以便客户端再次发生USER命令。

QUIT命令——格式为:QUIT,关闭与服务器的连接

(2)模式设置命令:

PASV命令——格式为:PASV,该命令告诉FTP服务器,让FTP服务器在指定的数据端口进行监听,被动接受客户端的请求。如果未指定任何模式,FTP服务器默认使用PASV模式

PORT命令——格式为:PORT <address>,该命令告诉FTP服务器,客户端监听的端口号是address,让FTP服务器采用主动模式连接客户端。

TYPE命令——格式为: TYPE <data type>,该命令指定要传输的数据类型,有ASCII和BINARY两种类型。

MODE命令——格式为:MODE <mode>,该命令指定传输模式,S表示流,B表示块,C表示压缩。

(3)文件管理命令

CWD命令——格式为:CWD <directory>,该命令是用户可以在不同的目录或数据集下工作而不用改变登录信息,directory一般是目录名或与系统相关的文件集合。

PWD命令——格式为:PWD,该命令返回当前工作目录。

MKD命令——格式为:MKD <directory>,该命令表示在指定路径下创建新目录,directory 表示特定目录的字符串。

CDUP命令——格式为:CDUP,该命令表示回到上层目录

RMD命令——格式为:RMD <directory>,删除指定目录,directory表示特定目录的字符串。

LIST命令——格式为:LIST <name>,该命令返回指定路径下的子目录及文件列表,name 为路径。省略路径时,返回当前路径下的文件列表。

NLIST命令——格式为:NLIST <directory>,该命令返回指定路径下的目录列表,省略路径时,返回当前目录。

RNFR命令——格式为:RNFR <old path>,该命令表示重新命名文件,该命令的下一条命令用RNTO指定新的文件名。

RNTO命令——格式为:RNTO <new path>,该命令和RNFR命令共同完成对文件的重命名。

DELE命令——格式为:DELE <filename>,该命令表示删除指定路径下的文件

(4)文件传输命令:

RETR命令——RETR <filename>,表示下载指定路径的文件

STOR命令——STOR <filename>,表示上传一个指定的文件,并将其存储在指定的位置,如果文件已存在,原文件将被覆盖,如果文件不存在,则创建新文件。

(5)其他命令

SYST命令——格式为:SYST,该命令返回服务器使用的操作系统。

4.2 FTP响应码

  客户端发送FTP命令后,服务器需要返回FTP响应码,响应码即是回答,我们平常聊天中别人问了说了话或者问了问题,另外一方就需要回答,FTP协议中定义以响应码的形式来作为回答,FTP响应码由ASCII编码的3位数字开头,后面接一行文本提示信息,数字和提示信息中有一个空格,如XXX 接收请求。每个响应码同样以回车换行结束。

  FTP响应码的3位数字每位都有特定的意义,具体见下表:

响应码

表示

1

1XX

表示信息已被服务器正确接收,但尚未被处理

2XX

表示信息已被服务器正确处理完毕

3XX

表示信息已被服务器正在接受,并正在处理中

4XX

表示信息处理错误(暂时)

5XX

表示信息处理错误(永久)

2

X0X

表示语法错误

X1X

表示系统状态与信息

X2X

表示与FTP服务器系统连接状态

X3X

表示与用户认证有关的信息

X4X

表示未定义

X5X

表示与文件系统有关的信息

下表列出了常用的响应码所代表的意义:

响应码

意义

响应码

意义

110

重新启动标记应答

332

登陆是需要账户信息

120

服务在指定时间内准备好

350

请求的文件操作需要进一步命令

125

数据连接打开——开始传输

421

服务关闭

150

文件状态良好,将要打开数据连接

425

不能打开数据连接

200

命令成功

426

关闭连接,终止传输

202

命令没有执行

450

文件不可用

211

系统状态回复

451

中止请求操作:有本地错误

212

目录状态回复

452

磁盘空间不足

213

文件状态回复

500

无效命令

214

帮助信息回复

501

语法错误

215

系统类型回复

502

命令未执行

220

服务就绪

503

命令顺序错误

221

服务关闭控制连接,可以退出登陆

504

无效命令参数

225

数据连接打开,无传输正在进行

530

未登陆

226

关闭数据连接,请求的文件操作成功

532

存储文件需要账户信息

227

进入被动模式

550

未执行请求操作

230

用户已登陆

551

请求操作终止:页类型未知

250

请求的文件操作完成

552

请求文件操作终止:超过存储分配

257

创建路径名

553

为执行请求的操作:文件名不合法

331

用户名正确,需要口令

五、实现自定义的FTP服务器

  相信大家看完上面的介绍对FTP协议以及FTP客户端和FTP服务器的交互过程有一定的理解的,这时候大家知道理论后就一定很想知道知道这些之后可以做什么的?答案就是可以制作一个简单的FTP服务器,大家可以根据代码来进一步理解FTP协议。下面是程序中一些核心代码片段:

 // 启动服务器
private void btnFtpServerStartStop_Click(object sender, EventArgs e)
{
if (myTcpListener == null)
{
listenThread = new Thread(ListenClientConnect);
listenThread.IsBackground = true;
listenThread.Start(); lstboxStatus.Enabled = true;
lstboxStatus.Items.Clear();
lstboxStatus.Items.Add("启动Ftp服务...");
btnFtpServerStartStop.Text = "停止";
}
else
{
myTcpListener.Stop();
myTcpListener = null;
listenThread.Abort();
lstboxStatus.Items.Add("Ftp服务已停止!");
lstboxStatus.TopIndex = lstboxStatus.Items.Count - ; btnFtpServerStartStop.Text = "启动";
}
} // 监听端口,处理客户端连接
private void ListenClientConnect()
{
myTcpListener = new TcpListener(IPAddress.Parse(tbxFtpServerIp.Text), int.Parse(tbxFtpServerPort.Text));
// 开始监听传入的请求
myTcpListener.Start();
AddInfo("启动成功!");
AddInfo("Ftp服务运行中...[单机”停止“退出]");
while (true)
{
try
{
// 接收连接请求
TcpClient tcpClient = myTcpListener.AcceptTcpClient();
AddInfo(string.Format("客户端({0})与本机({1})建立Ftp连接", tcpClient.Client.RemoteEndPoint, myTcpListener.LocalEndpoint));
User user = new User();
user.commandSession = new UserSeesion(tcpClient);
user.workDir = tbxFtpRoot.Text;
Thread t = new Thread(UserProcessing);
t.IsBackground = true;
t.Start(user);
}
catch
{
break;
}
}
} // 处理客户端用户请求
private void UserProcessing(object obj)
{
User user = (User)obj;
string sendString = "220 FTP Server v1.0";
RepleyCommandToUser(user, sendString);
while (true)
{
string receiveString = null;
try
{
// 读取客户端发来的请求信息
receiveString = user.commandSession.streamReader.ReadLine();
}
catch(Exception ex)
{
if (user.commandSession.tcpClient.Connected == false)
{
AddInfo(string.Format("客户端({0}断开连接!)", user.commandSession.tcpClient.Client.RemoteEndPoint));
}
else
{
AddInfo("接收命令失败!" + ex.Message);
} break;
} if (receiveString == null)
{
AddInfo("接收字符串为null,结束线程!");
break;
} AddInfo(string.Format("来自{0}:[{1}]", user.commandSession.tcpClient.Client.RemoteEndPoint, receiveString)); // 分解客户端发来的控制信息中的命令和参数
string command = receiveString;
string param = string.Empty;
int index = receiveString.IndexOf(' ');
if (index != -)
{
command = receiveString.Substring(, index).ToUpper();
param = receiveString.Substring(command.Length).Trim();
} // 处理不需登录即可响应的命令(这里只处理QUIT)
if (command == "QUIT")
{
// 关闭TCP连接并释放与其关联的所有资源
user.commandSession.Close();
return;
}
else
{
switch (user.loginOK)
{
// 等待用户输入用户名:
case :
CommandUser(user, command, param);
break; // 等待用户输入密码
case :
CommandPassword(user, command, param);
break; // 用户名和密码验证正确后登陆
case :
switch (command)
{
case "CWD":
CommandCWD(user, param);
break;
case "PWD":
CommandPWD(user);
break;
case "PASV":
CommandPASV(user);
break;
case "PORT":
CommandPORT(user, param);
break;
case "LIST":
CommandLIST(user, param);
break;
case "NLIST":
CommandLIST(user, param);
break;
// 处理下载文件命令
case "RETR":
CommandRETR(user, param);
break;
// 处理上传文件命令
case "STOR":
CommandSTOR(user, param);
break;
// 处理删除命令
case "DELE":
CommandDELE(user, param);
break;
// 使用Type命令在ASCII和二进制模式进行变换
case "TYPE":
CommandTYPE(user, param);
break;
default:
sendString = "502 command is not implemented.";
RepleyCommandToUser(user, sendString);
break;
} break;
}
}
}
}

程序演示截图:

首先在F:\盘下新建文件夹MyFtpServerRoot,在其中创建目录结构并放一些文件资源,例如图片,文档等,程序中演示的目录结构如下图:

这样,本地的FTP服务站点就已经建好了,运行FTP服务器程序,然后点击“启动”按钮后就启动了FTP服务器,运行结果如下图所示:

然后配合上个专题中实现的FTP客户端来完成与FTP服务器的“聊天”演示,因为FTP服务器程序中已经初始化用户名和密码(都为admin),所以FTP客户端中取消选择“匿名复选框”,直接输入用户名和密码为admin后点击“登录”按钮后就完成了用户验证的过程,并与FTP服务器建立了控制连接和数据连接。运行结果如下图:

当然用户可以通过"上传"、“下载”和删除按钮来对FTP服务器上的文件进行操作,这里就不贴出运行图片了, 大家可以下载源码来测试下的。

六、内容的结尾,说说后面的计划吧

这个专题介绍完后,我这个C#网络编程系列也就介绍完了,这个系列中主要介绍网络编程的一些入门知识,对于朋友在留言中经常提到的“打洞”技术以及一些网络编程中一些更难的内容还大家一起努力来学习的,同时我也会在后面和大家分享下一些实际开发过程中的网络编程的内容(在后面的文章打算和大家分享一个下载器的实现),最后,希望这个系列可以让大家对网络协议有一个最初的入门,这样在实际的开发过程中才知道这些实现背后的原理。之后我总结下我这个系列的所有文章的索引,以便让大家更好的阅读和查找关于这个系列的所有文章。

源码下载:http://files.cnblogs.com/zhili/FtpServer.zip,大家如果觉得不错的话,还请大家推荐下,谢谢大家的支持

用来演示的服务器目录:http://files.cnblogs.com/zhili/MyFtpServerRoot.zip

上个专题FTP文件上传下载器源码:http://files.cnblogs.com/zhili/FTPUpDownloader.zip

转自:http://www.cnblogs.com/zhili/archive/2012/10/18/FTPServer.html

专题十二:实现一个简单的FTP服务器的更多相关文章

  1. 转:【专题十二】实现一个简单的FTP服务器

    引言: 休息一个国庆节后好久没有更新文章了,主要是刚开始休息完心态还没有调整过来的, 现在差不多进入状态了, 所以继续和大家分享下网络编程的知识,在本专题中将和大家分享如何自己实现一个简单的FTP服务 ...

  2. 搭建一个简单的FTP服务器

    本文介绍通过win7自带的IIS来搭建一个只能实现基本功能的FTP服务器,第一次装好WIN7后我愣是没整出来,后来查了一下网上资料经过试验后搭建成功,其实原理和步骤与windows前期的版本差不多,主 ...

  3. 实现一个简单的FTP服务器(十四)

    此为一个网络编程的一个系列,后续会把内容补上...

  4. (十二)一个简单的pdf文件体

    %PDF-1.0                     % 文件头,说明符合PDF1.0规范 1 0 obj                          %对象号     产生号(修改次数)  ...

  5. Tomcat剖析(二):一个简单的Servlet服务器

    Tomcat剖析(二):一个简单的Servlet服务器 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三) ...

  6. 快速搭建一个本地的FTP服务器 win10及win7

    快速搭建一个本地的FTP服务器   如果需要开发FTP文件上传下载功能,那么需要在本机上搭建一个本地FTP服务器,方便调试. (win10) 第一步:配置IIS Web服务器 1.1 控制面板中找到“ ...

  7. 快速搭建一个本地的FTP服务器

    快速搭建一个本地的FTP服务器   如果需要开发FTP文件上传下载功能,那么需要在本机上搭建一个本地FTP服务器,方便调试. 第一步:配置IIS Web服务器 1.1 控制面板中找到"程序& ...

  8. 「kuangbin带你飞」专题十二 基础DP

    layout: post title: 「kuangbin带你飞」专题十二 基础DP author: "luowentaoaa" catalog: true tags: mathj ...

  9. linux设备驱动归纳总结(十二):简单的数码相框【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-116926.html linux设备驱动归纳总结(十二):简单的数码相框 xxxxxxxxxxxxxx ...

随机推荐

  1. 洛谷——P1044 栈

    P1044 栈——卡特兰数 题目背景 栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表. 栈有两种最重要的操作,即pop(从栈顶弹出一个元素)和push(将一个元素进栈) ...

  2. int *ptr=(int *)(&a+1)问题的探讨

    从网络上看到这样一道有意思的题目,是关于数组与指针的问题,描述如下: main() { ]={,,,,}; ); printf(),*(ptr-)); } 输出为:2,5 请解释以上代码的输出结果. ...

  3. iis站点内存泄漏问题分析

    在一次上线过程中iis内存飙升,随后跟运维要到站点的dump文件,使用windbg分析了clr的内存分配,找到了问题的症结,先记录如下: 使用windbg加载dump文件 1.打开windbg,Fil ...

  4. Mysql的timestamp类型,自动记录数据的更新时间

    datetime也可以设置自动更新的

  5. Cts框架解析(2)-cts调试环境的搭建

    上一篇文章中说了怎样在windows搭建cts以及执行cts进行測试.这篇文章来讲讲怎样在eclipse中配置源代码,进行debug调试. 下载 cts源代码地址:https://android.go ...

  6. css 實現微信聊天類似的氣泡

    要實現這樣的效果 代碼如下: --------------------------------------- <style> .test{width:300px; padding:30px ...

  7. POJ 3370 Halloween treats(抽屉原理)

    Halloween treats Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 6631   Accepted: 2448 ...

  8. 用yarn替代npm

    最近,从npm拉取vue-cli总失败,后来干脆直接用yarn 全局安装yarn(官网首推用系统包安装,更加安全) npm i yarn -g 用yarn添加全局vue-cli yarn global ...

  9. HDU 5407 CRB and Candies(LCM +最大素因子求逆元)

    [题目链接]pid=5407">click here~~ [题目大意]求LCM(Cn0,Cn1,Cn2....Cnn)%MOD 的值 [思路]来图更直观: 这个究竟是怎样推出的.说实话 ...

  10. 【跟我一步一步学Struts2】——登陆样例

    本篇博客通过一个简单的登陆小样例来入门,简单了解一下struts2是怎样工作的: 第一步引入Jar包: commons-fileupload-1.2.1.jar,文件上传 commons-loggin ...