Add initial implementation of FTP server with configuration, session handling, and basic commands support
This commit is contained in:
		
							parent
							
								
									950ecbf580
								
							
						
					
					
						commit
						e51829abfa
					
				
							
								
								
									
										49
									
								
								config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								config.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					FTP Server Configuration
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Server settings
 | 
				
			||||||
 | 
					FTP_HOST = '127.0.0.1'
 | 
				
			||||||
 | 
					FTP_PORT = 2121  # Use non-privileged port (21 requires admin rights)
 | 
				
			||||||
 | 
					DATA_PORT_RANGE = (20000, 20100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Server directories
 | 
				
			||||||
 | 
					SERVER_ROOT = './ftp_root'
 | 
				
			||||||
 | 
					TEST_FILES_DIR = './test_files'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# User credentials (in production, this should be in a database)
 | 
				
			||||||
 | 
					USERS = {
 | 
				
			||||||
 | 
					    'admin': 'admin123',
 | 
				
			||||||
 | 
					    'user': 'password',
 | 
				
			||||||
 | 
					    'test': 'test123',
 | 
				
			||||||
 | 
					    'guest': 'guest'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# FTP Response codes
 | 
				
			||||||
 | 
					FTP_RESPONSES = {
 | 
				
			||||||
 | 
					    '150': '150 File status okay; about to open data connection.',
 | 
				
			||||||
 | 
					    '200': '200 Command okay.',
 | 
				
			||||||
 | 
					    '220': '220 Service ready for new user.',
 | 
				
			||||||
 | 
					    '221': '221 Service closing control connection.',
 | 
				
			||||||
 | 
					    '226': '226 Closing data connection.',
 | 
				
			||||||
 | 
					    '227': '227 Entering Passive Mode',
 | 
				
			||||||
 | 
					    '230': '230 User logged in, proceed.',
 | 
				
			||||||
 | 
					    '250': '250 Requested file action okay, completed.',
 | 
				
			||||||
 | 
					    '331': '331 User name okay, need password.',
 | 
				
			||||||
 | 
					    '425': '425 Can\'t open data connection.',
 | 
				
			||||||
 | 
					    '426': '426 Connection closed; transfer aborted.',
 | 
				
			||||||
 | 
					    '450': '450 Requested file action not taken.',
 | 
				
			||||||
 | 
					    '500': '500 Syntax error, command unrecognized.',
 | 
				
			||||||
 | 
					    '501': '501 Syntax error in parameters or arguments.',
 | 
				
			||||||
 | 
					    '502': '502 Command not implemented.',
 | 
				
			||||||
 | 
					    '503': '503 Bad sequence of commands.',
 | 
				
			||||||
 | 
					    '530': '530 Not logged in.',
 | 
				
			||||||
 | 
					    '550': '550 Requested action not taken.',
 | 
				
			||||||
 | 
					    '553': '553 Requested action not taken.'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Supported commands
 | 
				
			||||||
 | 
					SUPPORTED_COMMANDS = [
 | 
				
			||||||
 | 
					    'USER', 'PASS', 'QUIT', 'PWD', 'CWD', 'LIST', 'NLST', 
 | 
				
			||||||
 | 
					    'RETR', 'PASV', 'PORT', 'TYPE', 'SYST', 'NOOP'
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
							
								
								
									
										490
									
								
								design_report.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										490
									
								
								design_report.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,490 @@
 | 
				
			|||||||
 | 
					# FTP 服务器设计与实现报告
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 项目概述
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 设计题目
 | 
				
			||||||
 | 
					**编程实现 FTP 服务器 ★★★**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 设计要求
 | 
				
			||||||
 | 
					1. 客户端通过 Windows 的命令行访问 FTP 服务器
 | 
				
			||||||
 | 
					2. FTP 服务器可以并发地服务多个客户
 | 
				
			||||||
 | 
					3. 至少实现对 FTP 命令 user、pass、dir、get 的支持
 | 
				
			||||||
 | 
					4. FTP 服务器必须对出现的问题或错误做出响应
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 技术栈
 | 
				
			||||||
 | 
					- **编程语言**: Python 3.13+
 | 
				
			||||||
 | 
					- **网络编程**: Socket 编程
 | 
				
			||||||
 | 
					- **并发处理**: Threading 多线程
 | 
				
			||||||
 | 
					- **协议标准**: RFC 959 FTP 协议
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 系统架构设计
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 整体架构
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```mermaid
 | 
				
			||||||
 | 
					graph TB
 | 
				
			||||||
 | 
					    A[Windows FTP Client] --> B[FTP Server Main]
 | 
				
			||||||
 | 
					    B --> C[Control Connection Handler]
 | 
				
			||||||
 | 
					    B --> D[Threading Manager]
 | 
				
			||||||
 | 
					    C --> E[Command Parser]
 | 
				
			||||||
 | 
					    C --> F[Data Connection Manager]
 | 
				
			||||||
 | 
					    E --> G[User Authentication]
 | 
				
			||||||
 | 
					    E --> H[File Operations]
 | 
				
			||||||
 | 
					    F --> I[Active Mode]
 | 
				
			||||||
 | 
					    F --> J[Passive Mode]
 | 
				
			||||||
 | 
					    D --> K[Client Thread 1]
 | 
				
			||||||
 | 
					    D --> L[Client Thread 2]
 | 
				
			||||||
 | 
					    D --> M[Client Thread N]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    style A fill:#e1f5fe
 | 
				
			||||||
 | 
					    style B fill:#f3e5f5
 | 
				
			||||||
 | 
					    style C fill:#e8f5e8
 | 
				
			||||||
 | 
					    style D fill:#fff3e0
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 类设计图
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```mermaid
 | 
				
			||||||
 | 
					classDiagram
 | 
				
			||||||
 | 
					    class FTPServer {
 | 
				
			||||||
 | 
					        +host: str
 | 
				
			||||||
 | 
					        +port: int
 | 
				
			||||||
 | 
					        +server_socket: socket
 | 
				
			||||||
 | 
					        +running: bool
 | 
				
			||||||
 | 
					        +client_threads: list
 | 
				
			||||||
 | 
					        +start_server()
 | 
				
			||||||
 | 
					        +handle_client()
 | 
				
			||||||
 | 
					        +stop_server()
 | 
				
			||||||
 | 
					        +cleanup_threads()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    class FTPSession {
 | 
				
			||||||
 | 
					        +client_socket: socket
 | 
				
			||||||
 | 
					        +client_address: tuple
 | 
				
			||||||
 | 
					        +data_socket: socket
 | 
				
			||||||
 | 
					        +authenticated: bool
 | 
				
			||||||
 | 
					        +username: str
 | 
				
			||||||
 | 
					        +current_directory: str
 | 
				
			||||||
 | 
					        +handle_session()
 | 
				
			||||||
 | 
					        +handle_command()
 | 
				
			||||||
 | 
					        +send_response()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    class CommandHandler {
 | 
				
			||||||
 | 
					        +handle_user()
 | 
				
			||||||
 | 
					        +handle_pass()
 | 
				
			||||||
 | 
					        +handle_list()
 | 
				
			||||||
 | 
					        +handle_retr()
 | 
				
			||||||
 | 
					        +handle_pwd()
 | 
				
			||||||
 | 
					        +handle_cwd()
 | 
				
			||||||
 | 
					        +handle_pasv()
 | 
				
			||||||
 | 
					        +handle_port()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    FTPServer --> FTPSession
 | 
				
			||||||
 | 
					    FTPSession --> CommandHandler
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 数据流图
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```mermaid
 | 
				
			||||||
 | 
					sequenceDiagram
 | 
				
			||||||
 | 
					    participant Client as Windows FTP Client
 | 
				
			||||||
 | 
					    participant Server as FTP Server
 | 
				
			||||||
 | 
					    participant Session as FTP Session
 | 
				
			||||||
 | 
					    participant Auth as Authentication
 | 
				
			||||||
 | 
					    participant FileOp as File Operations
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Client->>Server: TCP Connection (Port 21)
 | 
				
			||||||
 | 
					    Server->>Session: Create New Session
 | 
				
			||||||
 | 
					    Session->>Client: 220 Service Ready
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Client->>Session: USER username
 | 
				
			||||||
 | 
					    Session->>Auth: Validate User
 | 
				
			||||||
 | 
					    Auth->>Session: User OK
 | 
				
			||||||
 | 
					    Session->>Client: 331 Password Required
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Client->>Session: PASS password
 | 
				
			||||||
 | 
					    Session->>Auth: Authenticate
 | 
				
			||||||
 | 
					    Auth->>Session: Login Success
 | 
				
			||||||
 | 
					    Session->>Client: 230 User Logged In
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Client->>Session: LIST
 | 
				
			||||||
 | 
					    Session->>Client: 150 Opening Data Connection
 | 
				
			||||||
 | 
					    Session->>FileOp: Get Directory Listing
 | 
				
			||||||
 | 
					    FileOp->>Session: File List
 | 
				
			||||||
 | 
					    Session->>Client: Directory Data
 | 
				
			||||||
 | 
					    Session->>Client: 226 Transfer Complete
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Client->>Session: RETR filename
 | 
				
			||||||
 | 
					    Session->>Client: 150 Opening Data Connection
 | 
				
			||||||
 | 
					    Session->>FileOp: Read File
 | 
				
			||||||
 | 
					    FileOp->>Session: File Data
 | 
				
			||||||
 | 
					    Session->>Client: File Data
 | 
				
			||||||
 | 
					    Session->>Client: 226 Transfer Complete
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## FTP 协议分析
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### RFC 959 标准
 | 
				
			||||||
 | 
					FTP (File Transfer Protocol) 是基于 TCP 的应用层协议,使用两个连接:
 | 
				
			||||||
 | 
					- **控制连接** (端口 21): 传输命令和响应
 | 
				
			||||||
 | 
					- **数据连接**: 传输文件数据和目录列表
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 支持的 FTP 命令
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 命令 | 功能 | 实现状态 |
 | 
				
			||||||
 | 
					|------|------|----------|
 | 
				
			||||||
 | 
					| USER | 用户身份验证 | ✅ 已实现 |
 | 
				
			||||||
 | 
					| PASS | 密码验证 | ✅ 已实现 |
 | 
				
			||||||
 | 
					| LIST | 目录列表 (对应 DIR) | ✅ 已实现 |
 | 
				
			||||||
 | 
					| NLST | 简单文件列表 | ✅ 已实现 |
 | 
				
			||||||
 | 
					| RETR | 文件下载 (对应 GET) | ✅ 已实现 |
 | 
				
			||||||
 | 
					| PWD | 显示当前目录 | ✅ 已实现 |
 | 
				
			||||||
 | 
					| CWD | 改变目录 | ✅ 已实现 |
 | 
				
			||||||
 | 
					| PASV | 被动模式 | ✅ 已实现 |
 | 
				
			||||||
 | 
					| PORT | 主动模式 | ✅ 已实现 |
 | 
				
			||||||
 | 
					| TYPE | 传输类型 | ✅ 已实现 |
 | 
				
			||||||
 | 
					| SYST | 系统信息 | ✅ 已实现 |
 | 
				
			||||||
 | 
					| QUIT | 退出连接 | ✅ 已实现 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### FTP 响应码
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```mermaid
 | 
				
			||||||
 | 
					graph LR
 | 
				
			||||||
 | 
					    A[FTP 响应码] --> B[2xx 成功]
 | 
				
			||||||
 | 
					    A --> C[3xx 需要更多信息]
 | 
				
			||||||
 | 
					    A --> D[4xx 临时错误]
 | 
				
			||||||
 | 
					    A --> E[5xx 永久错误]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    B --> B1[220 服务就绪]
 | 
				
			||||||
 | 
					    B --> B2[230 用户登录成功]
 | 
				
			||||||
 | 
					    B --> B3[226 传输完成]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    C --> C1[331 需要密码]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    D --> D1[425 无法打开数据连接]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    E --> E1[530 未登录]
 | 
				
			||||||
 | 
					    E --> E2[550 文件不存在]
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 核心模块设计
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 1. 配置模块 (config.py)
 | 
				
			||||||
 | 
					```python
 | 
				
			||||||
 | 
					# 服务器配置
 | 
				
			||||||
 | 
					FTP_HOST = '127.0.0.1'
 | 
				
			||||||
 | 
					FTP_PORT = 21
 | 
				
			||||||
 | 
					DATA_PORT_RANGE = (20000, 20100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 用户认证
 | 
				
			||||||
 | 
					USERS = {
 | 
				
			||||||
 | 
					    'admin': 'admin123',
 | 
				
			||||||
 | 
					    'user': 'password',
 | 
				
			||||||
 | 
					    'test': 'test123',
 | 
				
			||||||
 | 
					    'guest': 'guest'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# FTP 响应码
 | 
				
			||||||
 | 
					FTP_RESPONSES = {
 | 
				
			||||||
 | 
					    '220': '220 Service ready for new user.',
 | 
				
			||||||
 | 
					    '230': '230 User logged in, proceed.',
 | 
				
			||||||
 | 
					    '331': '331 User name okay, need password.',
 | 
				
			||||||
 | 
					    # ... 更多响应码
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2. 主服务器模块 (ftp_server.py)
 | 
				
			||||||
 | 
					- **多线程处理**: 每个客户端连接创建独立线程
 | 
				
			||||||
 | 
					- **信号处理**: 优雅关闭服务器
 | 
				
			||||||
 | 
					- **连接管理**: 管理活跃连接和线程清理
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 3. 会话处理模块 (ftp_session.py)
 | 
				
			||||||
 | 
					- **命令解析**: 解析和路由 FTP 命令
 | 
				
			||||||
 | 
					- **状态管理**: 维护用户认证状态和当前目录
 | 
				
			||||||
 | 
					- **数据传输**: 处理被动/主动模式数据连接
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 关键技术实现
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 1. 多线程并发处理
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```mermaid
 | 
				
			||||||
 | 
					graph TD
 | 
				
			||||||
 | 
					    A[主服务器线程] --> B[监听端口 21]
 | 
				
			||||||
 | 
					    B --> C[接受客户端连接]
 | 
				
			||||||
 | 
					    C --> D[创建客户端线程]
 | 
				
			||||||
 | 
					    D --> E[FTP 会话处理]
 | 
				
			||||||
 | 
					    E --> F[命令处理循环]
 | 
				
			||||||
 | 
					    F --> G[响应客户端]
 | 
				
			||||||
 | 
					    G --> F
 | 
				
			||||||
 | 
					    F --> H[连接关闭]
 | 
				
			||||||
 | 
					    H --> I[线程结束]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    style A fill:#ffeb3b
 | 
				
			||||||
 | 
					    style D fill:#4caf50
 | 
				
			||||||
 | 
					    style E fill:#2196f3
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2. 用户认证机制
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```mermaid
 | 
				
			||||||
 | 
					stateDiagram-v2
 | 
				
			||||||
 | 
					    [*] --> 未认证
 | 
				
			||||||
 | 
					    未认证 --> 用户名验证 : USER command
 | 
				
			||||||
 | 
					    用户名验证 --> 密码验证 : PASS command
 | 
				
			||||||
 | 
					    密码验证 --> 已认证 : 认证成功
 | 
				
			||||||
 | 
					    密码验证 --> 未认证 : 认证失败
 | 
				
			||||||
 | 
					    已认证 --> 未认证 : QUIT command
 | 
				
			||||||
 | 
					    已认证 --> [*] : 连接断开
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 3. 数据连接管理
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 被动模式 (PASV)
 | 
				
			||||||
 | 
					```python
 | 
				
			||||||
 | 
					def handle_pasv(self):
 | 
				
			||||||
 | 
					    # 创建数据监听套接字
 | 
				
			||||||
 | 
					    self.passive_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # 绑定到可用端口
 | 
				
			||||||
 | 
					    for port in range(DATA_PORT_RANGE[0], DATA_PORT_RANGE[1]):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.passive_socket.bind(('127.0.0.1', port))
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					        except OSError:
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # 返回 PASV 响应
 | 
				
			||||||
 | 
					    p1 = port // 256
 | 
				
			||||||
 | 
					    p2 = port % 256
 | 
				
			||||||
 | 
					    self.send_response('227', f'Entering Passive Mode (127,0,0,1,{p1},{p2})')
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 主动模式 (PORT)
 | 
				
			||||||
 | 
					```python
 | 
				
			||||||
 | 
					def handle_port(self, args):
 | 
				
			||||||
 | 
					    # 解析 PORT 命令参数
 | 
				
			||||||
 | 
					    parts = args.split(',')
 | 
				
			||||||
 | 
					    ip = '.'.join(parts[:4])
 | 
				
			||||||
 | 
					    port = int(parts[4]) * 256 + int(parts[5])
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # 保存客户端数据连接地址
 | 
				
			||||||
 | 
					    self.data_address = (ip, port)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 4. 文件传输实现
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 目录列表 (LIST/DIR)
 | 
				
			||||||
 | 
					```python
 | 
				
			||||||
 | 
					def handle_list(self, command):
 | 
				
			||||||
 | 
					    # 建立数据连接
 | 
				
			||||||
 | 
					    if not self.establish_data_connection():
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # 获取目录内容
 | 
				
			||||||
 | 
					    files = os.listdir(self.current_directory)
 | 
				
			||||||
 | 
					    listing = []
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for filename in files:
 | 
				
			||||||
 | 
					        if command == 'LIST':
 | 
				
			||||||
 | 
					            # 详细列表格式
 | 
				
			||||||
 | 
					            stat = os.stat(filepath)
 | 
				
			||||||
 | 
					            listing.append(f'{permissions} {size} {mtime} {filename}')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # 简单列表格式
 | 
				
			||||||
 | 
					            listing.append(filename)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # 发送数据
 | 
				
			||||||
 | 
					    data = '\r\n'.join(listing) + '\r\n'
 | 
				
			||||||
 | 
					    self.data_socket.send(data.encode('utf-8'))
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 文件下载 (RETR/GET)
 | 
				
			||||||
 | 
					```python
 | 
				
			||||||
 | 
					def handle_retr(self, filename):
 | 
				
			||||||
 | 
					    # 安全检查
 | 
				
			||||||
 | 
					    filepath = os.path.normpath(os.path.join(self.current_directory, filename))
 | 
				
			||||||
 | 
					    if not filepath.startswith(SERVER_ROOT):
 | 
				
			||||||
 | 
					        self.send_response('550', 'Permission denied')
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # 建立数据连接
 | 
				
			||||||
 | 
					    if not self.establish_data_connection():
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # 传输文件
 | 
				
			||||||
 | 
					    with open(filepath, 'rb') as f:
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            data = f.read(8192)
 | 
				
			||||||
 | 
					            if not data:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					            self.data_socket.send(data)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 安全性设计
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 1. 路径安全
 | 
				
			||||||
 | 
					- **路径规范化**: 使用 `os.path.normpath()` 防止路径遍历
 | 
				
			||||||
 | 
					- **根目录限制**: 确保所有文件操作在服务器根目录内
 | 
				
			||||||
 | 
					- **权限检查**: 验证文件访问权限
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2. 用户认证
 | 
				
			||||||
 | 
					- **密码验证**: 简单的用户名/密码认证机制
 | 
				
			||||||
 | 
					- **会话管理**: 维护用户登录状态
 | 
				
			||||||
 | 
					- **权限控制**: 未认证用户无法执行文件操作
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 3. 错误处理
 | 
				
			||||||
 | 
					- **异常捕获**: 全面的异常处理机制
 | 
				
			||||||
 | 
					- **错误响应**: 标准 FTP 错误码响应
 | 
				
			||||||
 | 
					- **资源清理**: 确保连接和文件句柄正确关闭
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 测试方案
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 1. 功能测试
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Windows 命令行测试
 | 
				
			||||||
 | 
					```cmd
 | 
				
			||||||
 | 
					# 连接到 FTP 服务器
 | 
				
			||||||
 | 
					ftp 127.0.0.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 用户认证
 | 
				
			||||||
 | 
					USER admin
 | 
				
			||||||
 | 
					PASS admin123
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 目录操作
 | 
				
			||||||
 | 
					PWD
 | 
				
			||||||
 | 
					DIR
 | 
				
			||||||
 | 
					CD documents
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 文件下载
 | 
				
			||||||
 | 
					GET readme.txt
 | 
				
			||||||
 | 
					GET sample.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 退出
 | 
				
			||||||
 | 
					QUIT
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2. 并发测试
 | 
				
			||||||
 | 
					- 多个客户端同时连接
 | 
				
			||||||
 | 
					- 并发文件传输测试
 | 
				
			||||||
 | 
					- 线程安全验证
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 3. 错误处理测试
 | 
				
			||||||
 | 
					- 无效用户名/密码
 | 
				
			||||||
 | 
					- 不存在的文件/目录
 | 
				
			||||||
 | 
					- 网络连接中断
 | 
				
			||||||
 | 
					- 权限拒绝场景
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 项目文件结构
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					CN-design-ftp/
 | 
				
			||||||
 | 
					├── main.py                 # 程序入口
 | 
				
			||||||
 | 
					├── ftp_server.py          # 主服务器类
 | 
				
			||||||
 | 
					├── ftp_session.py         # 会话处理类
 | 
				
			||||||
 | 
					├── config.py              # 配置文件
 | 
				
			||||||
 | 
					├── design_report.md       # 设计报告
 | 
				
			||||||
 | 
					├── ftp_root/              # FTP 服务器根目录
 | 
				
			||||||
 | 
					│   ├── readme.txt         # 说明文件
 | 
				
			||||||
 | 
					│   ├── sample.txt         # 示例文本文件
 | 
				
			||||||
 | 
					│   ├── data.json          # JSON 数据文件
 | 
				
			||||||
 | 
					│   └── documents/         # 子目录
 | 
				
			||||||
 | 
					│       └── test_doc.txt   # 测试文档
 | 
				
			||||||
 | 
					└── test_files/            # 测试文件目录
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 使用说明
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 1. 启动服务器
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					python main.py
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2. Windows 客户端连接
 | 
				
			||||||
 | 
					```cmd
 | 
				
			||||||
 | 
					ftp 127.0.0.1
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 3. 测试用户账号
 | 
				
			||||||
 | 
					| 用户名 | 密码 |
 | 
				
			||||||
 | 
					|--------|------|
 | 
				
			||||||
 | 
					| admin | admin123 |
 | 
				
			||||||
 | 
					| user | password |
 | 
				
			||||||
 | 
					| test | test123 |
 | 
				
			||||||
 | 
					| guest | guest |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 4. 支持的命令映射
 | 
				
			||||||
 | 
					| Windows FTP 命令 | FTP 协议命令 | 功能 |
 | 
				
			||||||
 | 
					|------------------|--------------|------|
 | 
				
			||||||
 | 
					| USER username | USER | 用户登录 |
 | 
				
			||||||
 | 
					| PASS password | PASS | 密码验证 |
 | 
				
			||||||
 | 
					| DIR | LIST | 目录列表 |
 | 
				
			||||||
 | 
					| LS | NLST | 简单列表 |
 | 
				
			||||||
 | 
					| GET filename | RETR | 下载文件 |
 | 
				
			||||||
 | 
					| PWD | PWD | 当前目录 |
 | 
				
			||||||
 | 
					| CD dirname | CWD | 改变目录 |
 | 
				
			||||||
 | 
					| QUIT | QUIT | 退出连接 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 性能特点
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 1. 并发性能
 | 
				
			||||||
 | 
					- **多线程架构**: 支持多客户端并发访问
 | 
				
			||||||
 | 
					- **线程池管理**: 自动清理完成的线程
 | 
				
			||||||
 | 
					- **资源优化**: 合理的端口范围和连接管理
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2. 可扩展性
 | 
				
			||||||
 | 
					- **模块化设计**: 清晰的模块分离
 | 
				
			||||||
 | 
					- **配置化**: 易于修改服务器参数
 | 
				
			||||||
 | 
					- **命令扩展**: 容易添加新的 FTP 命令
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 3. 稳定性
 | 
				
			||||||
 | 
					- **异常处理**: 全面的错误处理机制
 | 
				
			||||||
 | 
					- **优雅关闭**: 信号处理和资源清理
 | 
				
			||||||
 | 
					- **状态管理**: 可靠的会话状态维护
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 技术难点与解决方案
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 1. FTP 协议的双连接机制
 | 
				
			||||||
 | 
					**难点**: FTP 使用控制连接和数据连接分离的设计
 | 
				
			||||||
 | 
					**解决方案**: 
 | 
				
			||||||
 | 
					- 实现被动模式 (PASV) 和主动模式 (PORT)
 | 
				
			||||||
 | 
					- 动态端口分配和连接管理
 | 
				
			||||||
 | 
					- 数据传输完成后及时关闭数据连接
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2. 多线程并发处理
 | 
				
			||||||
 | 
					**难点**: 多客户端并发访问的线程安全
 | 
				
			||||||
 | 
					**解决方案**:
 | 
				
			||||||
 | 
					- 每个客户端独立线程处理
 | 
				
			||||||
 | 
					- 线程安全的资源管理
 | 
				
			||||||
 | 
					- 优雅的线程生命周期管理
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 3. Windows 兼容性
 | 
				
			||||||
 | 
					**难点**: 确保与 Windows FTP 客户端完全兼容
 | 
				
			||||||
 | 
					**解决方案**:
 | 
				
			||||||
 | 
					- 严格遵循 RFC 959 标准
 | 
				
			||||||
 | 
					- 正确的响应码和消息格式
 | 
				
			||||||
 | 
					- 支持 Windows FTP 客户端的命令映射
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 总结
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					本项目成功实现了一个功能完整的 FTP 服务器,满足了所有设计要求:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. ✅ **Windows 命令行兼容**: 完全支持 Windows FTP 客户端
 | 
				
			||||||
 | 
					2. ✅ **并发处理**: 多线程架构支持多客户端同时访问
 | 
				
			||||||
 | 
					3. ✅ **核心命令支持**: 实现了 USER、PASS、DIR、GET 等关键命令
 | 
				
			||||||
 | 
					4. ✅ **错误处理**: 完善的错误响应和异常处理机制
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 技术亮点
 | 
				
			||||||
 | 
					- 严格遵循 RFC 959 FTP 协议标准
 | 
				
			||||||
 | 
					- 模块化和可扩展的代码架构
 | 
				
			||||||
 | 
					- 全面的安全性考虑
 | 
				
			||||||
 | 
					- 详细的文档和测试方案
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 学习收获
 | 
				
			||||||
 | 
					- 深入理解了 FTP 协议的工作原理
 | 
				
			||||||
 | 
					- 掌握了 Python Socket 网络编程
 | 
				
			||||||
 | 
					- 学会了多线程并发编程技术
 | 
				
			||||||
 | 
					- 提高了系统设计和架构能力
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					该 FTP 服务器可以作为计算机网络课程设计的优秀示例,展示了网络协议实现的完整过程。
 | 
				
			||||||
							
								
								
									
										33
									
								
								ftp_root/data.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								ftp_root/data.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "project": "FTP Server Implementation",
 | 
				
			||||||
 | 
					  "course": "Computer Networks Design",
 | 
				
			||||||
 | 
					  "language": "Python",
 | 
				
			||||||
 | 
					  "features": [
 | 
				
			||||||
 | 
					    "Multi-threaded server",
 | 
				
			||||||
 | 
					    "User authentication",
 | 
				
			||||||
 | 
					    "File transfer (RETR/GET)",
 | 
				
			||||||
 | 
					    "Directory listing (LIST/DIR)",
 | 
				
			||||||
 | 
					    "Passive mode support",
 | 
				
			||||||
 | 
					    "Error handling"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "supported_commands": [
 | 
				
			||||||
 | 
					    "USER",
 | 
				
			||||||
 | 
					    "PASS",
 | 
				
			||||||
 | 
					    "PWD",
 | 
				
			||||||
 | 
					    "CWD",
 | 
				
			||||||
 | 
					    "LIST",
 | 
				
			||||||
 | 
					    "NLST",
 | 
				
			||||||
 | 
					    "RETR",
 | 
				
			||||||
 | 
					    "PASV",
 | 
				
			||||||
 | 
					    "PORT",
 | 
				
			||||||
 | 
					    "TYPE",
 | 
				
			||||||
 | 
					    "SYST",
 | 
				
			||||||
 | 
					    "QUIT"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "test_users": {
 | 
				
			||||||
 | 
					    "admin": "admin123",
 | 
				
			||||||
 | 
					    "user": "password",
 | 
				
			||||||
 | 
					    "test": "test123",
 | 
				
			||||||
 | 
					    "guest": "guest"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								ftp_root/documents/test_doc.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								ftp_root/documents/test_doc.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					FTP Server Test Document
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This document is located in the documents subdirectory.
 | 
				
			||||||
 | 
					You can navigate to this directory using the CWD command:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CWD documents
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Then list the files using:
 | 
				
			||||||
 | 
					LIST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					And download this file using:
 | 
				
			||||||
 | 
					RETR test_doc.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This tests the directory navigation functionality of the FTP server.
 | 
				
			||||||
							
								
								
									
										21
									
								
								ftp_root/readme.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								ftp_root/readme.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					Welcome to the FTP Server!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This is the root directory of the FTP server.
 | 
				
			||||||
 | 
					You can download files from here using the GET command.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Available commands:
 | 
				
			||||||
 | 
					- USER <username> : Login with username
 | 
				
			||||||
 | 
					- PASS <password> : Provide password
 | 
				
			||||||
 | 
					- PWD : Show current directory
 | 
				
			||||||
 | 
					- CWD <path> : Change directory
 | 
				
			||||||
 | 
					- LIST : List files in current directory
 | 
				
			||||||
 | 
					- RETR <filename> : Download a file (GET command)
 | 
				
			||||||
 | 
					- QUIT : Disconnect from server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Test users:
 | 
				
			||||||
 | 
					- admin / admin123
 | 
				
			||||||
 | 
					- user / password
 | 
				
			||||||
 | 
					- test / test123
 | 
				
			||||||
 | 
					- guest / guest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Enjoy testing the FTP server!
 | 
				
			||||||
							
								
								
									
										9
									
								
								ftp_root/sample.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								ftp_root/sample.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					This is a sample text file for testing FTP downloads.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					File contents:
 | 
				
			||||||
 | 
					- Line 1: Hello World
 | 
				
			||||||
 | 
					- Line 2: FTP Server Test
 | 
				
			||||||
 | 
					- Line 3: Computer Networks Design Project
 | 
				
			||||||
 | 
					- Line 4: Python Implementation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can download this file using the RETR command.
 | 
				
			||||||
							
								
								
									
										155
									
								
								ftp_server.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								ftp_server.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,155 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					FTP Server - Main server class that handles multiple concurrent connections
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import socket
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import signal
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					from config import FTP_HOST, FTP_PORT, SERVER_ROOT
 | 
				
			||||||
 | 
					from ftp_session import FTPSession
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FTPServer:
 | 
				
			||||||
 | 
					    def __init__(self, host=FTP_HOST, port=FTP_PORT):
 | 
				
			||||||
 | 
					        self.host = host
 | 
				
			||||||
 | 
					        self.port = port
 | 
				
			||||||
 | 
					        self.server_socket = None
 | 
				
			||||||
 | 
					        self.running = False
 | 
				
			||||||
 | 
					        self.client_threads = []
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Ensure server root directory exists
 | 
				
			||||||
 | 
					        if not os.path.exists(SERVER_ROOT):
 | 
				
			||||||
 | 
					            os.makedirs(SERVER_ROOT)
 | 
				
			||||||
 | 
					            print(f"Created server root directory: {SERVER_ROOT}")
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Setup signal handlers for graceful shutdown
 | 
				
			||||||
 | 
					        signal.signal(signal.SIGINT, self.signal_handler)
 | 
				
			||||||
 | 
					        signal.signal(signal.SIGTERM, self.signal_handler)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def signal_handler(self, signum, frame):
 | 
				
			||||||
 | 
					        """Handle shutdown signals"""
 | 
				
			||||||
 | 
					        print(f"\nReceived signal {signum}, shutting down server...")
 | 
				
			||||||
 | 
					        self.stop_server()
 | 
				
			||||||
 | 
					        sys.exit(0)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def start_server(self):
 | 
				
			||||||
 | 
					        """Start the FTP server"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # Create server socket
 | 
				
			||||||
 | 
					            self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
				
			||||||
 | 
					            self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Bind to address and port
 | 
				
			||||||
 | 
					            self.server_socket.bind((self.host, self.port))
 | 
				
			||||||
 | 
					            self.server_socket.listen(5)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            self.running = True
 | 
				
			||||||
 | 
					            print(f"FTP Server started on {self.host}:{self.port}")
 | 
				
			||||||
 | 
					            print(f"Server root directory: {os.path.abspath(SERVER_ROOT)}")
 | 
				
			||||||
 | 
					            print("Waiting for connections...")
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            while self.running:
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    # Accept client connection
 | 
				
			||||||
 | 
					                    client_socket, client_address = self.server_socket.accept()
 | 
				
			||||||
 | 
					                    print(f"New connection from {client_address}")
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    # Create new thread for client
 | 
				
			||||||
 | 
					                    client_thread = threading.Thread(
 | 
				
			||||||
 | 
					                        target=self.handle_client,
 | 
				
			||||||
 | 
					                        args=(client_socket, client_address),
 | 
				
			||||||
 | 
					                        daemon=True
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    client_thread.start()
 | 
				
			||||||
 | 
					                    self.client_threads.append(client_thread)
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    # Clean up finished threads
 | 
				
			||||||
 | 
					                    self.cleanup_threads()
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                except OSError:
 | 
				
			||||||
 | 
					                    if self.running:
 | 
				
			||||||
 | 
					                        print("Error accepting connection")
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					                except Exception as e:
 | 
				
			||||||
 | 
					                    print(f"Error in server loop: {e}")
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            print(f"Error starting server: {e}")
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            self.stop_server()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_client(self, client_socket, client_address):
 | 
				
			||||||
 | 
					        """Handle individual client connection"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # Create FTP session for this client
 | 
				
			||||||
 | 
					            session = FTPSession(client_socket, client_address)
 | 
				
			||||||
 | 
					            session.handle_session()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            print(f"Error handling client {client_address}: {e}")
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                client_socket.close()
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def cleanup_threads(self):
 | 
				
			||||||
 | 
					        """Remove finished threads from the list"""
 | 
				
			||||||
 | 
					        self.client_threads = [t for t in self.client_threads if t.is_alive()]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def stop_server(self):
 | 
				
			||||||
 | 
					        """Stop the FTP server"""
 | 
				
			||||||
 | 
					        if not self.running:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        print("Stopping FTP server...")
 | 
				
			||||||
 | 
					        self.running = False
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Close server socket
 | 
				
			||||||
 | 
					        if self.server_socket:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                self.server_socket.close()
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Wait for client threads to finish (with timeout)
 | 
				
			||||||
 | 
					        for thread in self.client_threads:
 | 
				
			||||||
 | 
					            if thread.is_alive():
 | 
				
			||||||
 | 
					                thread.join(timeout=2.0)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        print("FTP server stopped")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def get_server_info(self):
 | 
				
			||||||
 | 
					        """Get server information"""
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'host': self.host,
 | 
				
			||||||
 | 
					            'port': self.port,
 | 
				
			||||||
 | 
					            'running': self.running,
 | 
				
			||||||
 | 
					            'active_connections': len([t for t in self.client_threads if t.is_alive()]),
 | 
				
			||||||
 | 
					            'server_root': os.path.abspath(SERVER_ROOT)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    """Main function to start the FTP server"""
 | 
				
			||||||
 | 
					    print("=" * 50)
 | 
				
			||||||
 | 
					    print("FTP Server - Computer Networks Design Project")
 | 
				
			||||||
 | 
					    print("=" * 50)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Create and start server
 | 
				
			||||||
 | 
					    server = FTPServer()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        server.start_server()
 | 
				
			||||||
 | 
					    except KeyboardInterrupt:
 | 
				
			||||||
 | 
					        print("\nShutdown requested by user")
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        print(f"Server error: {e}")
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        server.stop_server()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
							
								
								
									
										403
									
								
								ftp_session.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										403
									
								
								ftp_session.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,403 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					FTP Session Handler - Manages individual client connections
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import socket
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import random
 | 
				
			||||||
 | 
					from config import FTP_RESPONSES, USERS, SERVER_ROOT, DATA_PORT_RANGE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FTPSession:
 | 
				
			||||||
 | 
					    def __init__(self, client_socket, client_address):
 | 
				
			||||||
 | 
					        self.client_socket = client_socket
 | 
				
			||||||
 | 
					        self.client_address = client_address
 | 
				
			||||||
 | 
					        self.data_socket = None
 | 
				
			||||||
 | 
					        self.data_address = None
 | 
				
			||||||
 | 
					        self.passive_socket = None
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Session state
 | 
				
			||||||
 | 
					        self.authenticated = False
 | 
				
			||||||
 | 
					        self.username = None
 | 
				
			||||||
 | 
					        self.current_directory = SERVER_ROOT
 | 
				
			||||||
 | 
					        self.transfer_mode = 'I'  # Binary mode by default
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Ensure server root directory exists
 | 
				
			||||||
 | 
					        if not os.path.exists(SERVER_ROOT):
 | 
				
			||||||
 | 
					            os.makedirs(SERVER_ROOT)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        print(f"New FTP session started for {client_address}")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def send_response(self, code, message=None):
 | 
				
			||||||
 | 
					        """Send FTP response to client"""
 | 
				
			||||||
 | 
					        if message is None:
 | 
				
			||||||
 | 
					            response = FTP_RESPONSES.get(code, f"{code} Unknown response code.")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            response = f"{code} {message}"
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.client_socket.send((response + '\r\n').encode('utf-8'))
 | 
				
			||||||
 | 
					            print(f"Sent to {self.client_address}: {response}")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            print(f"Error sending response to {self.client_address}: {e}")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_session(self):
 | 
				
			||||||
 | 
					        """Main session handler"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # Send welcome message
 | 
				
			||||||
 | 
					            self.send_response('220', 'FTP Server Ready')
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            while True:
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    # Receive command from client
 | 
				
			||||||
 | 
					                    data = self.client_socket.recv(1024).decode('utf-8').strip()
 | 
				
			||||||
 | 
					                    if not data:
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    print(f"Received from {self.client_address}: {data}")
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    # Parse command
 | 
				
			||||||
 | 
					                    parts = data.split(' ', 1)
 | 
				
			||||||
 | 
					                    command = parts[0].upper()
 | 
				
			||||||
 | 
					                    args = parts[1] if len(parts) > 1 else ''
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    # Handle command
 | 
				
			||||||
 | 
					                    self.handle_command(command, args)
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                except ConnectionResetError:
 | 
				
			||||||
 | 
					                    print(f"Client {self.client_address} disconnected")
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					                except Exception as e:
 | 
				
			||||||
 | 
					                    print(f"Error handling command from {self.client_address}: {e}")
 | 
				
			||||||
 | 
					                    self.send_response('500', 'Internal server error')
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            print(f"Session error for {self.client_address}: {e}")
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            self.cleanup()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_command(self, command, args):
 | 
				
			||||||
 | 
					        """Route commands to appropriate handlers"""
 | 
				
			||||||
 | 
					        if command == 'USER':
 | 
				
			||||||
 | 
					            self.handle_user(args)
 | 
				
			||||||
 | 
					        elif command == 'PASS':
 | 
				
			||||||
 | 
					            self.handle_pass(args)
 | 
				
			||||||
 | 
					        elif command == 'QUIT':
 | 
				
			||||||
 | 
					            self.handle_quit()
 | 
				
			||||||
 | 
					        elif command == 'PWD':
 | 
				
			||||||
 | 
					            self.handle_pwd()
 | 
				
			||||||
 | 
					        elif command == 'CWD':
 | 
				
			||||||
 | 
					            self.handle_cwd(args)
 | 
				
			||||||
 | 
					        elif command == 'LIST' or command == 'NLST':
 | 
				
			||||||
 | 
					            self.handle_list(command)
 | 
				
			||||||
 | 
					        elif command == 'RETR':
 | 
				
			||||||
 | 
					            self.handle_retr(args)
 | 
				
			||||||
 | 
					        elif command == 'PASV':
 | 
				
			||||||
 | 
					            self.handle_pasv()
 | 
				
			||||||
 | 
					        elif command == 'PORT':
 | 
				
			||||||
 | 
					            self.handle_port(args)
 | 
				
			||||||
 | 
					        elif command == 'TYPE':
 | 
				
			||||||
 | 
					            self.handle_type(args)
 | 
				
			||||||
 | 
					        elif command == 'SYST':
 | 
				
			||||||
 | 
					            self.handle_syst()
 | 
				
			||||||
 | 
					        elif command == 'NOOP':
 | 
				
			||||||
 | 
					            self.send_response('200')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.send_response('500', f'Command {command} not recognized')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_user(self, username):
 | 
				
			||||||
 | 
					        """Handle USER command"""
 | 
				
			||||||
 | 
					        if not username:
 | 
				
			||||||
 | 
					            self.send_response('501', 'Username required')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.username = username
 | 
				
			||||||
 | 
					        self.authenticated = False
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if username in USERS:
 | 
				
			||||||
 | 
					            self.send_response('331', f'User {username} OK. Password required')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.send_response('331', 'Password required')  # Don't reveal if user exists
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_pass(self, password):
 | 
				
			||||||
 | 
					        """Handle PASS command"""
 | 
				
			||||||
 | 
					        if not self.username:
 | 
				
			||||||
 | 
					            self.send_response('503', 'Login with USER first')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if not password:
 | 
				
			||||||
 | 
					            self.send_response('501', 'Password required')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if self.username in USERS and USERS[self.username] == password:
 | 
				
			||||||
 | 
					            self.authenticated = True
 | 
				
			||||||
 | 
					            self.send_response('230', f'User {self.username} logged in')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.authenticated = False
 | 
				
			||||||
 | 
					            self.send_response('530', 'Login incorrect')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_quit(self):
 | 
				
			||||||
 | 
					        """Handle QUIT command"""
 | 
				
			||||||
 | 
					        self.send_response('221', 'Goodbye')
 | 
				
			||||||
 | 
					        self.client_socket.close()
 | 
				
			||||||
 | 
					        return True  # Signal to exit the session loop
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_pwd(self):
 | 
				
			||||||
 | 
					        """Handle PWD command"""
 | 
				
			||||||
 | 
					        if not self.authenticated:
 | 
				
			||||||
 | 
					            self.send_response('530', 'Not logged in')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Return relative path from server root
 | 
				
			||||||
 | 
					        rel_path = os.path.relpath(self.current_directory, SERVER_ROOT)
 | 
				
			||||||
 | 
					        if rel_path == '.':
 | 
				
			||||||
 | 
					            rel_path = '/'
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            rel_path = '/' + rel_path.replace('\\', '/')
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.send_response('257', f'"{rel_path}" is current directory')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_cwd(self, path):
 | 
				
			||||||
 | 
					        """Handle CWD command"""
 | 
				
			||||||
 | 
					        if not self.authenticated:
 | 
				
			||||||
 | 
					            self.send_response('530', 'Not logged in')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if not path:
 | 
				
			||||||
 | 
					            self.send_response('501', 'Path required')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Handle absolute and relative paths
 | 
				
			||||||
 | 
					        if path.startswith('/'):
 | 
				
			||||||
 | 
					            new_path = os.path.join(SERVER_ROOT, path.lstrip('/'))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            new_path = os.path.join(self.current_directory, path)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        new_path = os.path.normpath(new_path)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Security check: ensure path is within server root
 | 
				
			||||||
 | 
					        if not new_path.startswith(SERVER_ROOT):
 | 
				
			||||||
 | 
					            self.send_response('550', 'Permission denied')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if os.path.exists(new_path) and os.path.isdir(new_path):
 | 
				
			||||||
 | 
					            self.current_directory = new_path
 | 
				
			||||||
 | 
					            self.send_response('250', 'Directory changed')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.send_response('550', 'Directory not found')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_syst(self):
 | 
				
			||||||
 | 
					        """Handle SYST command"""
 | 
				
			||||||
 | 
					        self.send_response('215', 'UNIX Type: L8')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_type(self, type_arg):
 | 
				
			||||||
 | 
					        """Handle TYPE command"""
 | 
				
			||||||
 | 
					        if not self.authenticated:
 | 
				
			||||||
 | 
					            self.send_response('530', 'Not logged in')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if type_arg.upper() in ['I', 'A']:
 | 
				
			||||||
 | 
					            self.transfer_mode = type_arg.upper()
 | 
				
			||||||
 | 
					            self.send_response('200', f'Type set to {type_arg.upper()}')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.send_response('501', 'Type not supported')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_pasv(self):
 | 
				
			||||||
 | 
					        """Handle PASV command - Enter passive mode"""
 | 
				
			||||||
 | 
					        if not self.authenticated:
 | 
				
			||||||
 | 
					            self.send_response('530', 'Not logged in')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # Close existing passive socket if any
 | 
				
			||||||
 | 
					            if self.passive_socket:
 | 
				
			||||||
 | 
					                self.passive_socket.close()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Create new passive socket
 | 
				
			||||||
 | 
					            self.passive_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
				
			||||||
 | 
					            self.passive_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Find available port
 | 
				
			||||||
 | 
					            for port in range(DATA_PORT_RANGE[0], DATA_PORT_RANGE[1]):
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    self.passive_socket.bind(('127.0.0.1', port))
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					                except OSError:
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.send_response('425', 'Cannot open passive connection')
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            self.passive_socket.listen(1)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Calculate port representation for PASV response
 | 
				
			||||||
 | 
					            p1 = port // 256
 | 
				
			||||||
 | 
					            p2 = port % 256
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            self.send_response('227', f'Entering Passive Mode (127,0,0,1,{p1},{p2})')
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            print(f"Error setting up passive mode: {e}")
 | 
				
			||||||
 | 
					            self.send_response('425', 'Cannot open passive connection')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_port(self, args):
 | 
				
			||||||
 | 
					        """Handle PORT command - Active mode"""
 | 
				
			||||||
 | 
					        if not self.authenticated:
 | 
				
			||||||
 | 
					            self.send_response('530', 'Not logged in')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # Parse PORT command arguments
 | 
				
			||||||
 | 
					            parts = args.split(',')
 | 
				
			||||||
 | 
					            if len(parts) != 6:
 | 
				
			||||||
 | 
					                self.send_response('501', 'Invalid PORT command')
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Extract IP and port
 | 
				
			||||||
 | 
					            ip = '.'.join(parts[:4])
 | 
				
			||||||
 | 
					            port = int(parts[4]) * 256 + int(parts[5])
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            self.data_address = (ip, port)
 | 
				
			||||||
 | 
					            self.send_response('200', 'PORT command successful')
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            print(f"Error parsing PORT command: {e}")
 | 
				
			||||||
 | 
					            self.send_response('501', 'Invalid PORT command')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def establish_data_connection(self):
 | 
				
			||||||
 | 
					        """Establish data connection (passive or active mode)"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if self.passive_socket:
 | 
				
			||||||
 | 
					                # Passive mode
 | 
				
			||||||
 | 
					                self.data_socket, _ = self.passive_socket.accept()
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					            elif self.data_address:
 | 
				
			||||||
 | 
					                # Active mode
 | 
				
			||||||
 | 
					                self.data_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
				
			||||||
 | 
					                self.data_socket.connect(self.data_address)
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            print(f"Error establishing data connection: {e}")
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def close_data_connection(self):
 | 
				
			||||||
 | 
					        """Close data connection"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if self.data_socket:
 | 
				
			||||||
 | 
					                self.data_socket.close()
 | 
				
			||||||
 | 
					                self.data_socket = None
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_list(self, command):
 | 
				
			||||||
 | 
					        """Handle LIST/NLST commands"""
 | 
				
			||||||
 | 
					        if not self.authenticated:
 | 
				
			||||||
 | 
					            self.send_response('530', 'Not logged in')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.send_response('150', 'Opening data connection for directory list')
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if not self.establish_data_connection():
 | 
				
			||||||
 | 
					                self.send_response('425', 'Cannot open data connection')
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Get directory listing
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                files = os.listdir(self.current_directory)
 | 
				
			||||||
 | 
					                listing = []
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                for filename in files:
 | 
				
			||||||
 | 
					                    filepath = os.path.join(self.current_directory, filename)
 | 
				
			||||||
 | 
					                    stat = os.stat(filepath)
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if command == 'LIST':
 | 
				
			||||||
 | 
					                        # Detailed listing
 | 
				
			||||||
 | 
					                        is_dir = 'd' if os.path.isdir(filepath) else '-'
 | 
				
			||||||
 | 
					                        permissions = 'rwxr-xr-x'  # Simplified permissions
 | 
				
			||||||
 | 
					                        size = stat.st_size
 | 
				
			||||||
 | 
					                        mtime = time.strftime('%b %d %H:%M', time.localtime(stat.st_mtime))
 | 
				
			||||||
 | 
					                        listing.append(f'{is_dir}{permissions} 1 user user {size:8} {mtime} {filename}')
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        # Simple listing (NLST)
 | 
				
			||||||
 | 
					                        listing.append(filename)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                # Send listing
 | 
				
			||||||
 | 
					                data = '\r\n'.join(listing) + '\r\n'
 | 
				
			||||||
 | 
					                self.data_socket.send(data.encode('utf-8'))
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                self.close_data_connection()
 | 
				
			||||||
 | 
					                self.send_response('226', 'Directory listing completed')
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            except Exception as e:
 | 
				
			||||||
 | 
					                print(f"Error reading directory: {e}")
 | 
				
			||||||
 | 
					                self.close_data_connection()
 | 
				
			||||||
 | 
					                self.send_response('550', 'Directory listing failed')
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            print(f"Error in LIST command: {e}")
 | 
				
			||||||
 | 
					            self.send_response('425', 'Cannot open data connection')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def handle_retr(self, filename):
 | 
				
			||||||
 | 
					        """Handle RETR command - Download file"""
 | 
				
			||||||
 | 
					        if not self.authenticated:
 | 
				
			||||||
 | 
					            self.send_response('530', 'Not logged in')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if not filename:
 | 
				
			||||||
 | 
					            self.send_response('501', 'Filename required')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Construct file path
 | 
				
			||||||
 | 
					        filepath = os.path.join(self.current_directory, filename)
 | 
				
			||||||
 | 
					        filepath = os.path.normpath(filepath)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Security check
 | 
				
			||||||
 | 
					        if not filepath.startswith(SERVER_ROOT):
 | 
				
			||||||
 | 
					            self.send_response('550', 'Permission denied')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if not os.path.exists(filepath) or not os.path.isfile(filepath):
 | 
				
			||||||
 | 
					            self.send_response('550', 'File not found')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.send_response('150', f'Opening data connection for {filename}')
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if not self.establish_data_connection():
 | 
				
			||||||
 | 
					                self.send_response('425', 'Cannot open data connection')
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Send file
 | 
				
			||||||
 | 
					            with open(filepath, 'rb') as f:
 | 
				
			||||||
 | 
					                while True:
 | 
				
			||||||
 | 
					                    data = f.read(8192)
 | 
				
			||||||
 | 
					                    if not data:
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					                    self.data_socket.send(data)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            self.close_data_connection()
 | 
				
			||||||
 | 
					            self.send_response('226', 'File transfer completed')
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            print(f"Error transferring file {filename}: {e}")
 | 
				
			||||||
 | 
					            self.close_data_connection()
 | 
				
			||||||
 | 
					            self.send_response('550', 'File transfer failed')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def cleanup(self):
 | 
				
			||||||
 | 
					        """Clean up session resources"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if self.data_socket:
 | 
				
			||||||
 | 
					                self.data_socket.close()
 | 
				
			||||||
 | 
					            if self.passive_socket:
 | 
				
			||||||
 | 
					                self.passive_socket.close()
 | 
				
			||||||
 | 
					            if self.client_socket:
 | 
				
			||||||
 | 
					                self.client_socket.close()
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        print(f"Session ended for {self.client_address}")
 | 
				
			||||||
							
								
								
									
										6
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								main.py
									
									
									
									
									
								
							@ -1,6 +1,8 @@
 | 
				
			|||||||
def main():
 | 
					"""
 | 
				
			||||||
    print("Hello from cn-design-ftp!")
 | 
					FTP Server Entry Point
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ftp_server import main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    main()
 | 
					    main()
 | 
				
			||||||
 | 
				
			|||||||
@ -3,3 +3,9 @@
 | 
				
			|||||||
| 已知技术参数和设计要求 | 设计要求:1.客户端通过 Windows 的命令行访问 FTP 服务器。2.FTP 服务器可以并发地服务多个客户。3.至少实现对 FTP 命令 user、pass、dir、get 的支持。即用户注册、显示服务器端的文件列表、下载文件等。4.FTP 服务器必须对出现的问题或错误做出响应。 |
 | 
					| 已知技术参数和设计要求 | 设计要求:1.客户端通过 Windows 的命令行访问 FTP 服务器。2.FTP 服务器可以并发地服务多个客户。3.至少实现对 FTP 命令 user、pass、dir、get 的支持。即用户注册、显示服务器端的文件列表、下载文件等。4.FTP 服务器必须对出现的问题或错误做出响应。 |
 | 
				
			||||||
| 设计内容与步骤         | 1. 参考相关的 RFC,熟悉 FTP 规范;2. 学习多线程机制;3. FTP 服务器结构设计;4. FTP 服务器程序设计;5. FTP 服务器程序调试;6. 课程设计任务书。                                                                                              |
 | 
					| 设计内容与步骤         | 1. 参考相关的 RFC,熟悉 FTP 规范;2. 学习多线程机制;3. FTP 服务器结构设计;4. FTP 服务器程序设计;5. FTP 服务器程序调试;6. 课程设计任务书。                                                                                              |
 | 
				
			||||||
| 设计工作计划与进度安排 | 1.Socket 程序设计 4 小时 2.程序调试调试方法 4 小时 3.FTP 规范 4 小时 4.FTP 服务器结构设计 4 小时 5.FTP 服务器程序设计与调试 14 小时 6.课程设计报告 5 小时                                                                                       |
 | 
					| 设计工作计划与进度安排 | 1.Socket 程序设计 4 小时 2.程序调试调试方法 4 小时 3.FTP 规范 4 小时 4.FTP 服务器结构设计 4 小时 5.FTP 服务器程序设计与调试 14 小时 6.课程设计报告 5 小时                                                                                       |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					请参考要求,完成设计的代码和报告,报告用 markdown 格式保存到本地,如果需要画图就使用 mermaid 格式。代码请使用python编写
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					完成的标准是,能够使用 Windows 的命令行访问 FTP 服务器,并且支持 user、pass、dir、get 命令,能够并发服务多个客户,并对错误做出响应。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user