# 计网课设 **Repository Path**: lw-02/ftp ## Basic Information - **Project Name**: 计网课设 - **Description**: 计网课设 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-03-12 - **Last Updated**: 2024-04-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # FTP服务器 ### 需求分析 #### 设计目的 设计并实现一个 FTP 服务器的程序,了解 FTP 协议的基本原理和工作过程,加深对 TCP 协议和流式套接字编程的理解 #### 设计要求 在后台运行服务器程序。 支持多个客户端同时连接服务器。 要求在 FTP 服务器程序中至少实现目录变换(CWD),列表(LIST),下载(RETR)功能。 输出内容:使用 Windows 或 Linux 命令行下的 FTP 客户端程序或其他的 FTP客户端与 FTP 服务器交互,运行“cd”、“ls”、“get”等命令显示和下载文件。 ### 设计方案 (程序功能模块的划分、模块间的联系、数据结构和关键问题的解决方法等,可结合图表说明) #### 开发环境 语言:python IDE:pycharm 操作系统:Windows 10 用到的库: - os - sys - socket - hashlib - subprocess - pathlib - queue - threading.Thread - configparser #### 功能模块的划分 ![](./%E5%9B%BE%E7%89%87/image-20240326173628023.png) #### 模块间的联系 ![](./%E5%9B%BE%E7%89%87/image-20240326173709359.png) #### 数据结构 ##### 队列(Queue) 存储多用户并发的处理线程 ##### 字典(dict) 存储多用户情景下的会话目录 #### 关键问题的解决方法 ##### 界面交互的选择 使用命令行方式对服务器进行交互,主要写一个提示字符串,提示用户输入,并建立一个字典来存放命令的各种情况,执行用户输入的命令 ![](./%E5%9B%BE%E7%89%87/image-20240326124528995.png) 并实现start_ftp和create_user这两个函数 ![](./%E5%9B%BE%E7%89%87/image-20240326124442983.png) ##### 如何添加服务器用户 维护一个用户的配置文件accounts.ini,使用configparser库来读取和写入用户信息。 配置文件中主要存放用户名、密码、家目录,如下: ![](./%E5%9B%BE%E7%89%87/image-20240326123440858.png) 同时用到了hashlib的md5函数对密码进行加密存储 ##### 对ftp登录流程的详解 在ftp协议中,当服务器开启监听并接收到一个客户端连接之后,首先需要对客户端返回响应码220,告诉客户端已经打开控制通道。此处若有配置需求,客户端还会发送配置命令,例如`OPTS UTF8 ON`这个命令会请求服务器工作在UTF-8环境下。此时依然需要对命令进行处理并响应客户端。 考虑到ftp客户端当连接上服务器之后会立即请求用户登录,所以单独设置了一个登陆验证模块来处理用户认证的过程。此时客户端就会发送登录请求,命令为`USER username`。获取用户名并且从用户配置文件中读取相应的信息。如果是匿名用户(ftp和anonymous),则登陆成功;如果名字存在,则会进一步判断密码时候正确;错误的话会返回失败,等待用户登录。 判断密码时,客户端送入的指令为`PASS password`,先把password进行md5加密,并把这个加密结果与上面获取的用户信息进行对比,如果密码正确,则返回登录成功;反之,则登陆失败,会等待用户登录。 为了便于对目录进行管理,当用户登录成功后,会给当前用户地址与该用户家目录建立一个字典,用于维护当前用户的会话目录。 ##### 如何实现多用户并发 单独开辟一个线程函数,当监听到用户时,就启动该线程,并把用户的控制通道和地址作为参数传递下去,同时为了控制用户的并发数,用一个队列来接收线程对象,借此实现同一时间用户数目不会大于预设的数量`MAX_CONCURRENT_COUNT`。当用户断开连接时,就把队列中的对象去掉一个,保证了最多能够有`MAX_CONCURRENT_COUNT`个用户同时在线。 ##### 多用户下会话目录的维护 为了便于对目录变换的操作,原本使用一个字符串变量来`current_path`来存储当前用户的会话目录,需要对路径操作时,就使用os模块下的`abspath`和`join`函数来拼接路径。但是这里因为current_path是一个字符变量,在多用户情况下,后来的用户的会话目录会覆盖先前的用户。面对这种情况,考虑建立一个字典来索引每个用户的会话目录,字典存放的是用户地址到用户当前会话目录的对应关系。 ##### 用户指令的获取、解析与执行 用户指令的交互放在一个while循环中进行,每当接收到一个指令时,首先需要对指令进行解码,此处工作在`UTF-8`环境下,所以进行`decode('utf-8')`的解码,然后使用`split`对指令进行分割,转化成一个列表。 指令格式可以参考下面的资料,在获取指令之后,就需要执行相应的功能函数,此处同时传递conn和client_addr给函数调用。功能函数使用了指令名字来命名,比如客户端传过来RETR指令,我们就有一个RETR函数,然后调用python内置函数`hasattr`和`getattr`来执行相应的函数。 特殊的是PORT指令,这是主动模式下客户端告诉服务器可以连接的端口,该端口用于数据的传输,主要服务于ls、get和put指令。此时需要传递的参数为三个,相比上面多了一个`data_socket`,该参数是数据通道。 ##### PORT指令的实现 当服务器接收到PORT指令之后,首先要解析PORT后面的6个参数,运算得出地址和端口号,其中地址为前四位,端口号等于第5为乘以255加上第6位。然后用这个地址打开数据通道,要调用通信模块的函数`data_open`。此时新创建一个套接字,并连接传过来的ip,返回这个套接字。 打开数据通道后,需要返回状态码200,告诉客户端PORT指令执行完毕。此时客户端会把需要执行的指令传递过来,对于需要数据通道的指令,用一个`data_cmds`来接收指令列表。然后接收指令并调用对应的功能函数。最后会关闭这个数据通道。 ##### RETR指令的实现 该指令用于客户端向服务器请求下载文件,首先需要对传递的文件路径进行处理,并结合该用户家目录、当前会话目录、传递的指令参数来构造在服务器下的文件路径`file_path`,然后判断文件是否可以访问,并判断文件是否处于该用户家目录下面,如果都符合就开始传输数据,不然返回错误信息。 ##### STOR指令的实现 上传文件时先把文件名提取出来,然后构造文件的路径,接着告诉客户端开始传输数据。 ##### NLST指令的实现 使用`subprocess`的`Popen`函数来单独执行目录列表的查询信息,传递的参数为`{dir 目录的路径}`,然后把获取的信息传递给客户端。 ##### CWD指令的实现 ##### XMKD指令的实现 ##### XRMD指令的实现 ##### XPWD指令的实现 ##### DELE指令的实现 ##### QUIT指令的实现 ### 程序结构和流程图 程序结构如下: 流程图如下: ### 测试方案 # 资料 ```python """ ftp 客户端命令 OPTS UTF8 ON # 表示服务器需要切换到UTF8字符集进行工作。 USER ftp # 指定用户ftp(匿名用户,anonymous也是匿名用户) PASS # 表示输入密码,FTP规定匿名用户可以选择需要输入自己的邮箱,但是这里可以省略不写。 PORT 192,168,10,103,211,216 # PORT,这里用于指定客户端的IP地址和端口 LIST # 表示列出当前工作目录下的文件 CWD pub # CWD,表示切换到pub目录下。 STOR test.c # STOP 表示上传文件,文件名为 test.c RETR test.file # RETR 下载文件。 QUIT # 退出 ABOR (ABORT)此命令使服务器终止前一个FTP服务命令以及任何相关数据传输。 ACCT (ACCOUNT)此命令的参数部分使用一个Telnet字符串来指明用户的账户。 ADAT (AUTHENTICATION/SECURITY DATA)认证/安全数据 ALLO 为接收一个文件分配足够的磁盘空间 APPE 增加 AUTH 认证/安全机制 CCC 清除命令通道 CDUP 改变到父目录 CONF 机密性保护命令 CWD 改变工作目录 DELE 删除文件 ENC 隐私保护通道 EPRT 为服务器指定要连接的扩展地址和端口 EPSV 进入扩展被动模式 FEAT 获得服务器支持的特性列表 HELP 如果指定了命令,返回命令使用文档;否则返回一个通用帮助文档 LANG 语言协商 LIST 如果指定了文件或目录,返回其信息;否则返回当前工作目录的信息 LPRT 为服务器指定要连接的长地址和端口 LPSV 进入长被动模式 MDTM 返回指定文件的最后修改时间 MIC 完整性保护命令 MKD 创建目录 MLSD 如果目录被命名,列出目录的内容 MLST 提供命令行指定的对象的数据 MODE 设定传输模式(流、块或压缩) NLST 返回指定目录的文件名列表 NOOP 无操作(哑包;通常用来保活) OPTS 为特性选择选项 PASS 认证密码 PASV 进入被动模式 PBSZ 保护缓冲大小 PORT 指定服务器要连接的地址和端口 PROT 数据通道保护级别 PWD 打印工作目录,返回主机的当前目录 QUIT 断开连接 REIN 重新初始化连接 REST 从指定点重新开始传输 RETR 传输文件副本 RMD 删除目录 RNFR 从...重命名 RNTO 重命名到... SITE 发送站点特殊命令到远端服务器 SIZE 返回文件大小 SMNT 挂载文件结构 STAT 返回当前状态 STOR 接收数据并且在服务器站点保存为文件 STOU 唯一地保存文件 STRU 设定文件传输结构 SYST 返回系统类型 TYPE 设定传输模式(ASCII/二进制). USER 认证用户名 XCUP 改变之当前工作目录的父目录 XMKD 创建目录 XPWD 打印当前工作目录 XRCP XRMD 删除目录 XRSQ XSEM 发送,否则邮件 XSEN 发送到终端 """ """ ftp响应码 1xx - 肯定的初步答复 这些状态代码指示一项操作已经成功开始,但客户端希望在继续操作新命令前得到另一个答复。 • 110 重新启动标记答复。 • 120 服务已就绪,在 nnn 分钟后开始。 • 125 数据连接已打开,正在开始传输。 • 150 文件状态正常,准备打开数据连接。 2xx - 肯定的完成答复 一项操作已经成功完成。客户端可以执行新命令。 • 200 命令确定。 • 202 未执行命令,站点上的命令过多。 • 211 系统状态,或系统帮助答复。 • 212 目录状态。 • 213 文件状态。 • 214 帮助消息。 • 215 NAME 系统类型,其中,NAME 是 Assigned Numbers 文档中所列的正式系统名称。 • 220 服务就绪,可以执行新用户的请求。 • 221 服务关闭控制连接。如果适当,请注销。 • 225 数据连接打开,没有进行中的传输。 • 226 关闭数据连接。请求的文件操作已成功(例如,传输文件或放弃文件)。 • 227 进入被动模式 (h1,h2,h3,h4,p1,p2)。 • 230 用户已登录,继续进行。 • 250 请求的文件操作正确,已完成。 • 257 已创建“PATHNAME”。 3xx - 肯定的中间答复 该命令已成功,但服务器需要更多来自客户端的信息以完成对请求的处理。 • 331 用户名正确,需要密码。 • 332 需要登录帐户。 • 350 请求的文件操作正在等待进一步的信息。 4xx - 瞬态否定的完成答复 该命令不成功,但错误是暂时的。如果客户端重试命令,可能会执行成功。 • 421 服务不可用,正在关闭控制连接。如果服务确定它必须关闭,将向任何命令发送这一应答。 • 425 无法打开数据连接。 • 426 Connection closed; transfer aborted. • 450 未执行请求的文件操作。文件不可用(例如,文件繁忙)。 • 451 请求的操作异常终止:正在处理本地错误。 • 452 未执行请求的操作。系统存储空间不够。 5xx - 永久性否定的完成答复 该命令不成功,错误是永久性的。如果客户端重试命令,将再次出现同样的错误。 • 500 语法错误,命令无法识别。这可能包括诸如命令行太长之类的错误。 • 501 在参数中有语法错误。 • 502 未执行命令。 • 503 错误的命令序列。 • 504 未执行该参数的命令。 • 530 未登录。 • 532 存储文件需要帐户。 • 550 未执行请求的操作。文件不可用(例如,未找到文件,没有访问权限)。 • 551 请求的操作异常终止:未知的页面类型。 • 552 请求的文件操作异常终止:超出存储分配(对于当前目录或数据集)。 • 553 未执行请求的操作。不允许的文件名。 常见的 FTP 状态代码及其原因 • 150 - FTP 使用两个端口:21 用于发送命令,20 用于发送数据。状态代码 150 表示服务器准备在端口 20 上打开新连接,发送一些数据。 • 226 - 命令在端口 20 上打开数据连接以执行操作,如传输文件。该操作成功完成,数据连接已关闭。 • 230 - 客户端发送正确的密码后,显示该状态代码。它表示用户已成功登录。 • 331 - 客户端发送用户名后,显示该状态代码。无论所提供的用户名是否为系统中的有效帐户,都将显示该状态代码。 • 426 - 命令打开数据连接以执行操作,但该操作已被取消,数据连接已关闭。 • 530 - 该状态代码表示用户无法登录,因为用户名和密码组合无效。如果使用某个用户帐户登录,可能键入错误的用户名或密码,也可能选择只允许匿名访问。如果使用匿名帐户登录,IIS 的配置可能拒绝匿名访问。 • 550 - 命令未被执行,因为指定的文件不可用。例如,要 GET 的文件并不存在,或试图将文件 PUT 到您没有写入权限的目录。 —— 正面初步回答 120 服务不久即将就绪 125 数据连接打开;数据传输不久即将开始 150 文件状态是OK —— 正面完成回答 200 命令OK 211 系统状态或求助回答 212 目录状态 213 文件状态 214 求助报文 215 命名系统类型(操作系统) 220 服务就绪 221 服务关闭 225 数据连接打开 226 关闭数据连接 227 进入被动方式,服务器发送IP地址和端口号 230 用户登录OK 250 请求文件动作OK —— 正面中间回答 331 用户名OK:需要口令 332 需要登录账号 350 文件动作在进行中:需要更多的信息 —— 过渡负面完成回答 425 不能打开数据连接 426 连接关闭:不能识别的命令 450 未采取文件动作:文件不可用 451 动作异常终止:本地差错 452 动作异常终止:存储器不足 —— 永久负面完成回答 500 语法差错:不能识别的命令 501 参数或变量的语法差错 502 命令未实现 503 不良命令序列 504 命令参数未实现 530 用户未登录 532 存储文件需要账号 550 动作未完成:文件不可用 552 请求的动作异常终止:超过分配的存储器空间 553 未采取请求动作:文件名不允许 """ ```