FTP服务器 详解+源代码.docx

上传人:b****7 文档编号:9572897 上传时间:2023-02-05 格式:DOCX 页数:83 大小:133.15KB
下载 相关 举报
FTP服务器 详解+源代码.docx_第1页
第1页 / 共83页
FTP服务器 详解+源代码.docx_第2页
第2页 / 共83页
FTP服务器 详解+源代码.docx_第3页
第3页 / 共83页
FTP服务器 详解+源代码.docx_第4页
第4页 / 共83页
FTP服务器 详解+源代码.docx_第5页
第5页 / 共83页
点击查看更多>>
下载资源
资源描述

FTP服务器 详解+源代码.docx

《FTP服务器 详解+源代码.docx》由会员分享,可在线阅读,更多相关《FTP服务器 详解+源代码.docx(83页珍藏版)》请在冰豆网上搜索。

FTP服务器 详解+源代码.docx

FTP服务器详解+源代码

一个简单的FTP服务器

实例目标

FTP是网络上共享资源的常用方法,在本章中我们将实现一个简单的FTP服务器。

本章知识点:

●FTP协议

●Socket类和TcpListener类

●System.Threading名称空间

5.1实例功能

本实例实现一个简单的FTP服务器,该服务器是一个控制台程序,编译后的可执行文件为ftpd.exe,在控制台中键入ftpd后就可启动服务器,若要改变ftp服务器的工作目录,可以键入ftpd–r后接绝对路径。

服务器的ftp服务端口采用默认的21。

服务器启动后,用户就可从其他任何一台联网计算机进行访问。

下面是应用的一个例子:

(服务器所在机器的ip为166.111.147.25)

用户在自己计算机的控制台中输入ftp166.111.147.25回车后可以看到服务器传过来的欢迎信息,并要求输入登陆账号(图5-1)。

图5-1登陆ftp

输入用户名和密码后(为简化起见我们在程序中省去了验证过程,任何人都可以登陆),用户的控制台如图5-2所示,在服务器上,也出现了该用户的登陆情况(图5-3)。

图5-2成功登陆图5-3服务端

接下来用户可以使用各种命令进行各种ftp操作,比如列出目录下所有文件和文件夹(ls),下载指定的文件(get),上载文件(put)等等。

下面是客户端(图5-4)和服务端(图5-5)某时刻的运行状态。

图5-4客户端运行情况图5-5服务端运行情况

5.2编程思路

要实现FTP服务器,我们必须对FTP协议有一定的了解,使用符合协议的指令集和网络传输方式,我们将在下一节详细介绍关于FTP协议的基础知识。

另外,我们还采用了TcpListener和Socket编程技术实现数据传输,所以这也是我们需要掌握的内容。

最后,为了同时给多个用户提供服务,FTP服务器还必须支持多线程。

FTP服务器程序的大框架是这样的:

程序运行后,在服务器的某个端口有一个TcpListener一直在监听用户的请求,当有用户请求服务时,服务器立刻创建一个新的线程处理这个请求,我们称开始了一个新的会话。

在会话中,服务器通过Socket接收用户命令,对命令进行分析后采取相应的操作,并将结果返回。

一直到用户退出这个会话,服务器才销毁这个线程。

服务器和客户端的会话方式有两种,一是被动方式(passive),即服务器在某个特定端口有一个TcpListener在不断监听用户命令;二是主动方式,这种情况下,服务器在该客户端有服务请求时,创建一个套接字和它进行数据传输。

5.3关键技术

FTP协议

1)概述

FTP的目标是提高文件的共享性,提供非直接使用远程计算机,使存储介质对用户透明和可靠高效地传送数据。

图5-6是FTP服务示意图,为了让大家更好的理解,我们先解释一下相关的概念。

(1)字节大小,在FTP中字节大小有两个:

逻辑字节大小和用于传输的字节大小。

后者通常是8位,而前者可不一定是多少了。

传输字节不必等于逻辑字节大小,也不必对数据结构进行解释。

(2)控制连接是建立在USER-PIT和SERVER-PI之间用于交换命令与应答的通信链路。

(3)数据连接是传输数据的全双工连接。

传输数据可以发生在服务器DTP和用户DTP之间也可以发生在两个服务器DTP之间。

(4)DTP:

数据传输过程,DTP建立和管理数据连接,可以是主动的也可以是被动的。

(5)EOR代表记录尾。

(6)NTV代表网络虚拟终端。

(7)NVFS代表网络虚拟文件系统。

(8)FTP可以传输非连续的文件,这些文件的一部分称为页。

(9)PI代表协议解释器。

(10)服务器DTP代表一种传输过程,它通常处于“主动”状态,它和侦听端口建立数据连接,它还可以为传输和存储设置参数,并根据PI的指令传输数据。

当然,DTP也可以转入“被动”状态。

(11)服务器FTP进程,它是和用户FTP进程一起工作的,它由PI和DTP组成。

至于用户FTP进程则是由PI,DTP和用户接口组成的。

图5-6FTP服务示意图

注意:

数据连接是双向的,它不用整个时间都存在。

上图中用户PI开始控制连接,控制连接与Telnet协议很象。

在开始阶段,标准FTP命令由用户PI产生并通过控制连接传送到服务器进程。

服务器PI向用户PI返回标准应答。

FTP命令指定数据连接参数和文件系统操作。

用户DTP在特定数据端口侦听,服务器开始数据连接并以指定的参数开始数据传输。

数据端口不必在开始FTP命令的机器上,但用户或用户FTP进程必须确定它在指定的数据端口上侦听。

这个数据连接是全双工的。

在另外一种情况下,用户或许希望在两个主机间传送文件,不是两个本地主机。

用户在两台主机间建立控制连接,然后规划数据连接。

用这种方式,控制信息由用户PI获得,但是数据在服务器DTP之间传送。

图5-7就是一个例子:

图5-7

协议要求数据传输在处理时打开控制连接。

在完成FTP服务后由用户中止控制连接,而服务器具体操作。

如果在未接收命令时关闭了控制连接,服务器也会关闭数据传输。

数据传输功能

数据连接只传输数据,控制连接传送命令和响应。

几个命令是关于在主机间传输数据的,数据传输基本上独立于物理结构的,但是如果在压缩传输模式下流式传输与文件结构有关,文件的属性与表示类型有关。

❑数据表示与保存

数据是在主机间的存储设置间传送的。

因为两个系统的数据存储方式不同,因此需要对它进行转换,在传送文本时会有对ASCII表示的问题,在进行二进制传送的时候,会有不同系统对字节长度规定不同的问题,有的系统是7位,有的系统可能是32位,这也需要进行转换。

需要提供数据表示与传输模型函数,但是FTP提供这方面的功能不多,超过FTP提供功能的那一部分要用户自己实现。

数据表示是由用户指定的表示类型,它可以是隐含的,也可以是用户指定的。

请一定注意:

逻辑字节长度与物理字节长度是不同的。

ASCII类型:

这是所有FTP必须实现的默认类型,用于传送文本文件,当在主机间使用EBCDIC传送时更方便,则不使用ASCII类型。

发送方将内部表示转换为NVT-ASCII格式,接收方则进行相反的过程接收数据。

根据NVT标准,要在行结束处使用序列。

NVT-ASCII是8位的。

ASCII和EBCDIC的格式参数在下面讨论。

EBCDIC类型:

它是作为ASCII的另一种方法在主机间传送数据的数据类型。

EBCDIC和ASCII很象,仅在类型的功能描述上有一些差别。

行结束符使用很少。

图象类型:

在此类型下传送的数据被看作连续的位,发送方将数据打包到8位传输字节中传送。

因为结构的需要要对传送数据进行填充,填充字节全部为0,填充必须在文件结构时使用,而且要标记出以便接收方过滤掉。

它用于传送二进制数据和有效地传送和存储文件,因此所有FTP也必须实现。

本地类型:

也可以以十进制指定逻辑字节大小。

如果物理字节大小和逻辑字节大小不同,直接将物理数据打包为逻辑字节,不用什么填充。

接收方根据逻辑字节大小进行和本机的存储特点进行转换。

传输必须是可重复的,也就是说,相同的文件相同的参数,那内容必须是一样的。

数据结构

除了有不同的数据类型外,FTP还允许有不同的文件结构,下面是三种文件结构:

文件式结构:

文件中没有内部结构,文件被看作是二进制流;记录结构:

文件是由一系列记录组成的;页结构:

文件是由不同的索引页组成的。

如果未使用STRU命令,文件结构是默认值。

文件的结构会影响传输模型,存储和数据表示。

文件本来的属性和保存它的主机有关,不同的机器会以自己的方式保存文件。

在不同主机间传送文件时必须使主机能够识别相互的表示。

有些主机上的文件是面向字节的,有些是面向记录的,在传送时就会出现问题。

那就要在接收方进行内部转换。

在进行转换的时候,需要区别记录的边界,在ASCII中使用,在EBCDIC中使用作为分隔符。

采用这种实现方法的必须保证转换是可逆的。

文件结构:

如果未使用STRU命令,文件结构是默认值。

文件结构中没有默认值,文件被看作是连续的字节串。

记录结构:

对于文本文件,记录结构必须是所有FTP实现必须有的。

记录结构文件是由连续的记录构成的。

页结构:

文件是非连续时使用页结构。

这种文件称为随机访问文件。

这些文件中有时会的和文件整体或部分相关的信息出现。

在FTP中,文件的一个部分称为页。

建立数据连接

传送数据机制包括建立连接选择数据参数。

用户和服务器DTP有默认数据端口。

用户进程默认数据端口和控制连接端口相同。

服务器进程默认数据端口和控制连接端口相邻。

传输字节大小是8位字节。

此字节是实际传输字节,但不代表主机内的数据表示。

被动数据传输进程在数据端口接收数据,FTP请求命令决定数据传输的方向。

服务器在接收到请求以后,将初始化端口的数据连接。

当连接建立后,传输在DTP之间传送,服务器PI对用户PI返回应答。

FTP实现运行一个默认数据端口,用户PI才能改变默认端口。

通过PORT命令可能改变端口,用户可能希望数据在第三方主机上进行其它操作,用户PI需要在两个服务器PI上建立连接。

一个服务器被告知侦听另一服务器的请求。

用户PI通过PORT命令通知另一服务器的数据端口。

最后双方发送相应的传送命令。

通常,服务器负责支持数据连接,初始化并关闭它,除非用户DTP在传输模式下要求关闭连接。

服务器在下面情况下关闭数据连接:

❑服务器结束发送数据,通过EOF要求中止传送;

❑用户发送ABORT命令;

❑用户改变端口;

❑控制连接关闭;

❑发生不可恢复错误。

数据连接管理

默认数据连接端口:

所有FTP必须支持默认数据连接,只有用户PI能够初始化非默认端口的使用。

确定非默认数据端口:

用户PI可以使用PORT命令指定非默认端口,它要求服务器方以PASV确定非默认数据端口。

连接是由双方地址确定的,因此改变一方地址就改变了连接。

数据连接的重用:

在使用流式数据传输模型时,文件结束通过关闭连接指示。

如果要传送多个文件时就会出麻烦,解决的方法有两个,一个是确定非默认端口,另一个是使用另一种传输模式。

就传输模式而言,流传输模式是不安全的,因此无法确定连接是暂时还是永久关闭。

其它传输模式不通过关闭连接表示文件结构,它们可以通过FTP命令决定传送结构。

因此使用这些传输模式可以在保持连接的情况下传送多个文件。

传输模式

有三种传输模式:

一种将数据格式化并考虑重新开始过程;一种压缩数据;一种是不经过处理(少量处理)传送。

所有数据传输必须以一个EOF结束,它可以显式给出,也可以通过关闭连接隐式给出。

对于记录文件,所有EOR是显式的,包括最后一个记录。

对于以页结构传送的文件,使用“最后一页”表示结束。

从这里开始,下文中我们提到的字节指的是“传输字节”。

为了进行标准化传送,传送主机必须把行结束或记录结束的内部表示转化为传输模式和文件结构指定的形式传送,接收方则进行相反的工作。

IBM大型机的记录计数域可能不能为其它主机识别,所以记录结束标记在流模式下以双字节控制码传送,在块或压缩模式下以标记位传送。

而ASCII或EBCDIC的行结束则则指示。

这样的转换需要时间,所以相同的系统在传送文本文件时采用二进制或流表示比较合适。

下面是FTP定义的传输模式:

流模式:

数据以字节流的形式传送。

使用的表示类型没有限制,允许记录结构。

在记录结构文件EOR和EOF表示为双字节控制码。

第一字节全为0,后一字节为转义字符。

当第二位值为1时表示EOR,为2时表示EOF,如果要同时表示EOR和EOF,值为3。

全1字节作为数据发送时必须使用双字节传送,其中数据保存在第二个字节内。

如果是文件结构,通过发送方关闭连接表示EOF,接收到的所有数据就是文件内容。

块模式:

文件以块形式传送,块带有自己的头部分。

头字节包括计数域和描述子代码。

关于FTP协议的基本知识我们先介绍这些,在以后的代码分析中我们将结合实例,做更深入的讨论。

基于TCP协议的网络通讯

TCP协议是一个基本的网络协议,基本上所有的网络服务都是基于TCP协议的,FTP也是,所以必须了解基于TCP协议的编程。

然而TCP协议是一个庞杂的体系,要彻底的弄清楚它的实现不是这么容易,不过在.NETFramework环境下,我们不必要去追究TCP协议底层的实现,一样可以很方便的编写出基于TCP协议进行网络通讯的程序。

在这章里,我们主要通过两个类编写FTP服务程序,这两个类是:

TcpListener类和Socket类,它们都属于System.Net.Sockets名称空间。

❑Socket类简介

Socket(套接字)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。

可以将Socket看作不同主机间的进程进行双向通信的端点,它构成了单个主机内及整个网络间的编程界面。

Socket存在于通信域中,通信域是为了处理一般的线程通过Socket通信而引进的一种抽象概念。

Socket通常和同一个域中的Socket交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序)。

各种进程使用这个相同的域互相之间用Internet协议簇来进行通信。

Socket可以根据通信性质分类,这种性质对于用户是可见的。

应用程序一般仅在同一类的Socket间进行通信。

不过只要底层的通信协议允许,不同类型的Socket间也照样可以通信。

Socket有两种不同的类型:

流Socket和数据报Socket。

Socket工作原理:

要通过互联网进行通信,你至少需要一对Socket,其中一个运行于客户机端,我们称之为ClientSocket,另一个运行于服务器端,我们称之为ServerSocket。

根据连接启动的方式以及本地Socket要连接的目标,Socket之间的连接过程可以分为三个步骤:

服务器监听,客户端请求,连接确认。

所谓服务器监听,是服务器端Socket并不定位具体的客户端Socket,而是处于等待连接的状态,实时监控网络状态。

所谓客户端请求,是指由客户端的Socket提出连接请求,要连接的目标是服务器端的Socket。

为此,客户端的Socket必须首先描述它要连接的服务器的Socket,指出服务器端Socket的地址和端口号,然后就向服务器端Socket提出连接请求。

所谓连接确认,是指当服务器端Socket监听到或者说接收到客户端Socket的连接请求,它就响应客户端Socket的请求,建立一个新的线程,把服务器端Socket的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。

而服务器端Socket继续处于监听状态,继续接收其他客户端Socket的连接请求。

❑TcpListener和TcpClient

TcpClient类是基于TCP协议的客户端类,而TcpListener是服务器端,监听(Listen)客户端传来的连接请求。

TcpClient类通过TCP协议与服务器进行通讯并获取信息,它的内部封装了一个Socket类的实例,这个Socket对象被用来使用TCP协议向服务器请求和获取数据。

因为与远程主机的交互是以数据流的形式出现的,所以传输的数据可以使用.netframework中流处理技术读写。

多线程

操作系统使用进程将它们正在执行的不同应用程序分开。

线程是操作系统分配处理器时间的基本单元,并且该进程中可以有多个线程同时执行代码。

每个线程都维护异常处理程序、调度优先级和一组系统用于在调度该线程前保存线程上下文的结构。

线程上下文包括为使线程在线程的宿主进程地址空间中无缝地继续执行所需的所有信息,包括线程的CPU寄存器组和堆栈。

.NET框架将操作系统进程进一步细分为由System.AppDomain表示的称为应用程序域的轻量托管子进程。

一个或多个托管线程(由System.Threading.Thread表示)可以在同一个非托管进程中的一个或任意数目的应用程序域中运行。

虽然每个应用程序域都是用单个线程启动的,但该应用程序域中的代码可以创建附加应用程序域和附加线程。

其结果是托管线程可以在同一个非托管进程中的应用程序域之间自由移动。

支持抢先多任务处理的操作系统可以创建多个进程中的多个线程同时执行的效果。

它通过以下方式实现这一点:

在需要处理器时间的线程之间分割可用处理器时间,并轮流为每个线程分配处理器时间片。

当前执行的线程在其时间片结束时被挂起,而另一个线程继续运行。

当系统从一个线程切换到另一个线程时,它将保存被抢先的线程的线程上下文,并重新加载线程队列中下一个线程的已保存线程上下文。

时间片的长度取决于操作系统和处理器。

由于每个时间片都很小,因此即使只有一个处理器,多个线程看起来似乎是在同时执行。

要提高对用户的响应速度并且处理所需数据以便几乎同时完成工作,使用多个线程是一种最为强大的技术。

在具有一个处理器的计算机上,多个线程可以通过利用用户事件之间很小的时间段在后台处理数据来达到这种效果。

例如,在另一个线程正在重新计算同一应用程序中的电子表格的其他部分时,用户可以编辑该电子表格。

在C#中,System.Threading名称空间提供了对多线程编程的支持,ThreadPool类可以管理同时存在的一组线程,Mutex类解决线程同步的问题,还有其它的一些类,可以解决多线程编程中遇到的各种问题,像死锁(两个线程都停止响应,并且都在等待对方完成)和争用条件(由于意外地出现对两个事件的执行时间的临界依赖性而发生反常的结果)等等。

5.4实现步骤

5.4.1步骤1-建立程序框架

本实例是控制台程序,在File->New->Project内选择建立C#CosoleApplication,名为ftpd,如图5-8所示。

工程建立之后,在SolutionExplorer中将系统为我们建立的默认类文件Class1.cs改名为ftpd.cs,并把该文件中原来的所有内容删掉。

建立我们自己的名称空间,添加引用:

namespaceFTPD.NET

{

usingSystem;

usingSystem.IO;

usingSystem.Net;

usingSystem.Net.Sockets;

usingSystem.Threading;

}

在这个例子中,我们要创建的类有:

主程序类MainApp,负责启动服务器,是程序运行的入口;Ftpd类,创建服务线程,处理用户请求,最主要的工作在这里完成。

SessionInfo类,用于管理会话;另外我们还有一个PassiveInfo结构,用于管理被动模式下的连接设置。

后面两个数据类型非常简单,只含有若干数据项,而没有任何方法。

图5-8创建工程

5.4.2步骤2-实现MainApp

步骤描述

启动FTP服务器,用户可以设置FTP服务的目录。

功能实现

在ftpd.cs文件中加入下面的代码:

publicclassMainApp

{

publicconstStringVersion="0.0.1";

publicstaticStringszFtpRoot=@"E:

\incoming";

publicstaticboolfDebug=true;

//服务器管理界面:

publicstaticvoidUsage()

{

Console.WriteLine("ftpdusage:

");

Console.WriteLine();

//改变ftp目录

Console.WriteLine("-r[path]\t:

\tSpecifiesFTPRoot");

//帮助

Console.WriteLine("-?

\t:

\tPrintsthishelp");

return;

}

publicstaticintMain(String[]Arguments)

{

for(inti=0;i

{

switch(Arguments[i])

{

//设定ftp目录

case"-r":

szFtpRoot=Arguments[i+1];

break;

case"-?

":

Usage();

return0;

default:

//无参数运行时ftp目录设为当前目录

szFtpRoot=Directory.GetCurrentDirectory();

break;

}

}

//创建一个Ftpd对象实例

FtpdpFtpd=newFtpd();

//启动ftp服务

if(pFtpd.StartServer()==false)

{

Console.WriteLine("FailedtostartFTPServer.");

return-1;

}

return0;

}

}

代码分析

Main()函数的有两种参数形式:

无参数和string数组表示的命令行参数,即staticvoidMain()或staticvoidMain(string[]args),后者接受命令行参数,并且将用户输入的参数放到args字符数组中(程序名称不算是第一个参数,这一点和VC++不同)。

在我们的程序里,我们首先对命令行参数进行分析,如果第一个参数是-r则将第二个参数读入做为用户指定的FTP服务目录,如果第一个参数是-?

,则显示调用函数usage(),显示帮助信息。

如果没有参数,则将当前目录设为FTP的目录,在这里,我们使用静态类Directory的GetCurrentDirectory()方法得到当前工作目录:

szFtpRoot=Directory.GetCurrentDirectory();

Directory类属于System.IO名称空间,提供了有关移动,创建目录,返回本目录下文件和子目录等的方法。

publicsealedclassDirectory

由于它是静态类,所以我们不必实例化就可以直接调用它的方法。

它的函数方法有:

1)publicstaticDirectoryInfoCreateDirectory(stringpath);

path参数为要建立的目录名,下面是一些合法目录名的例子:

"c:

\\MyDir","c:

\\MyDir","MyDir\\MySubdir","\\\\MyServer\\MyShare"。

而像"c:

\temp”,”c:

\windows"则是不合法的。

方法返回一个DirectoryInfo类对象,DirectoryInfo类和Directory类的区别只在于,前者的方法都不是静态方法,必须实例化后才能调用它的函数,但它和Directory类一样可以创建,移动,浏览目录。

2)publicstaticvoidDelete(string);

删除指定的空目录;

3)publicstaticvoidDelete(string,bool);

如果第二个参数为True,则删除该目录下所有的文件和子目录,否则只删除文件。

4)publicstaticstringGetCurrentDirectory();

返回当前的工作目录。

5)publicstaticstring[]GetFiles(string);

publicstaticstring[]GetFiles(string,string);

前者返回指定目录下所有的文件的名称,放到一个string数组中。

后者查找指定目录下(

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 表格模板 > 调查报告

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1