Add README and design report updates, implement FTP test client, and enhance session handling
This commit is contained in:
parent
e51829abfa
commit
62a99abe3f
238
README.md
238
README.md
@ -0,0 +1,238 @@
|
|||||||
|
# FTP 服务器实现 - 计算机网络课程设计
|
||||||
|
|
||||||
|
一个基于 Python 的多线程 FTP 服务器实现,支持 Windows 命令行客户端访问。
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
本项目实现了一个功能完整的 FTP 服务器,严格遵循 RFC 959 标准,支持:
|
||||||
|
|
||||||
|
- ✅ **多线程并发**: 支持多个客户端同时连接
|
||||||
|
- ✅ **用户认证**: USER/PASS 命令实现用户登录
|
||||||
|
- ✅ **目录操作**: LIST/DIR 命令显示文件列表
|
||||||
|
- ✅ **文件下载**: RETR/GET 命令下载文件
|
||||||
|
- ✅ **路径导航**: PWD/CWD 命令目录切换
|
||||||
|
- ✅ **错误处理**: 完善的错误响应机制
|
||||||
|
- ✅ **Windows 兼容**: 完全支持 Windows FTP 客户端
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 启动 FTP 服务器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
服务器将在 `127.0.0.1:2121` 启动(使用非特权端口避免需要管理员权限)。
|
||||||
|
|
||||||
|
### 2. 使用 Windows FTP 客户端连接
|
||||||
|
|
||||||
|
打开 Windows 命令提示符:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
ftp 127.0.0.1 2121
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 登录测试
|
||||||
|
|
||||||
|
使用以下测试账户:
|
||||||
|
|
||||||
|
| 用户名 | 密码 |
|
||||||
|
|--------|------|
|
||||||
|
| admin | admin123 |
|
||||||
|
| user | password |
|
||||||
|
| test | test123 |
|
||||||
|
| guest | guest |
|
||||||
|
|
||||||
|
### 4. 基本操作示例
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
# 连接服务器
|
||||||
|
ftp 127.0.0.1 2121
|
||||||
|
|
||||||
|
# 登录
|
||||||
|
User (127.0.0.1:(none)): admin
|
||||||
|
Password: admin123
|
||||||
|
|
||||||
|
# 查看当前目录
|
||||||
|
ftp> pwd
|
||||||
|
|
||||||
|
# 列出文件
|
||||||
|
ftp> dir
|
||||||
|
|
||||||
|
# 下载文件
|
||||||
|
ftp> get readme.txt
|
||||||
|
|
||||||
|
# 切换目录
|
||||||
|
ftp> cd documents
|
||||||
|
|
||||||
|
# 退出
|
||||||
|
ftp> quit
|
||||||
|
```
|
||||||
|
|
||||||
|
## 支持的 FTP 命令
|
||||||
|
|
||||||
|
| FTP 命令 | Windows 命令 | 功能描述 |
|
||||||
|
|----------|--------------|----------|
|
||||||
|
| USER | USER | 用户身份验证 |
|
||||||
|
| PASS | PASS | 密码验证 |
|
||||||
|
| LIST | DIR | 详细文件列表 |
|
||||||
|
| NLST | LS | 简单文件列表 |
|
||||||
|
| RETR | GET | 文件下载 |
|
||||||
|
| PWD | PWD | 显示当前目录 |
|
||||||
|
| CWD | CD | 改变目录 |
|
||||||
|
| PASV | - | 被动模式 |
|
||||||
|
| PORT | - | 主动模式 |
|
||||||
|
| TYPE | - | 传输类型设置 |
|
||||||
|
| SYST | - | 系统信息 |
|
||||||
|
| QUIT | QUIT | 退出连接 |
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
CN-design-ftp/
|
||||||
|
├── main.py # 程序入口点
|
||||||
|
├── ftp_server.py # 主服务器类
|
||||||
|
├── ftp_session.py # 客户端会话处理
|
||||||
|
├── config.py # 服务器配置
|
||||||
|
├── test_ftp_client.py # 测试客户端
|
||||||
|
├── design_report.md # 详细设计报告
|
||||||
|
├── README.md # 项目说明
|
||||||
|
├── ftp_root/ # FTP 服务器根目录
|
||||||
|
│ ├── readme.txt # 服务器说明文件
|
||||||
|
│ ├── sample.txt # 示例文本文件
|
||||||
|
│ ├── data.json # JSON 数据文件
|
||||||
|
│ └── documents/ # 子目录示例
|
||||||
|
│ └── test_doc.txt # 测试文档
|
||||||
|
└── test_files/ # 测试文件目录
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术特性
|
||||||
|
|
||||||
|
### 架构设计
|
||||||
|
|
||||||
|
- **多线程服务器**: 每个客户端连接独立线程处理
|
||||||
|
- **模块化设计**: 清晰的代码结构和职责分离
|
||||||
|
- **配置化管理**: 易于修改服务器参数
|
||||||
|
|
||||||
|
### 安全特性
|
||||||
|
|
||||||
|
- **路径安全**: 防止目录遍历攻击
|
||||||
|
- **用户认证**: 基于用户名/密码的访问控制
|
||||||
|
- **错误处理**: 全面的异常处理和错误响应
|
||||||
|
|
||||||
|
### 协议兼容性
|
||||||
|
|
||||||
|
- **RFC 959 标准**: 严格遵循 FTP 协议规范
|
||||||
|
- **双连接模式**: 支持控制连接和数据连接
|
||||||
|
- **传输模式**: 支持被动模式和主动模式
|
||||||
|
|
||||||
|
## 测试
|
||||||
|
|
||||||
|
### 自动化测试
|
||||||
|
|
||||||
|
运行内置测试脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动服务器(在一个终端)
|
||||||
|
python main.py
|
||||||
|
|
||||||
|
# 运行测试(在另一个终端)
|
||||||
|
python test_ftp_client.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 手动测试
|
||||||
|
|
||||||
|
1. **基本连接测试**
|
||||||
|
```bash
|
||||||
|
telnet 127.0.0.1 2121
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **FTP 客户端测试**
|
||||||
|
```cmd
|
||||||
|
ftp 127.0.0.1 2121
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **并发测试**
|
||||||
|
- 同时打开多个 FTP 客户端连接
|
||||||
|
- 验证多用户并发访问
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
编辑 `config.py` 文件可以修改服务器配置:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 服务器设置
|
||||||
|
FTP_HOST = '127.0.0.1' # 服务器地址
|
||||||
|
FTP_PORT = 2121 # 服务器端口
|
||||||
|
DATA_PORT_RANGE = (20000, 20100) # 数据连接端口范围
|
||||||
|
|
||||||
|
# 服务器目录
|
||||||
|
SERVER_ROOT = './ftp_root' # FTP 根目录
|
||||||
|
|
||||||
|
# 用户账户
|
||||||
|
USERS = {
|
||||||
|
'admin': 'admin123',
|
||||||
|
'user': 'password',
|
||||||
|
'test': 'test123',
|
||||||
|
'guest': 'guest'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
1. **端口被占用**
|
||||||
|
- 修改 `config.py` 中的 `FTP_PORT` 为其他端口
|
||||||
|
- 确保防火墙允许该端口通信
|
||||||
|
|
||||||
|
2. **权限错误**
|
||||||
|
- 确保 `ftp_root` 目录有读写权限
|
||||||
|
- 在 macOS/Linux 上可能需要 `chmod 755 ftp_root`
|
||||||
|
|
||||||
|
3. **连接被拒绝**
|
||||||
|
- 检查服务器是否正常启动
|
||||||
|
- 验证客户端连接的 IP 和端口是否正确
|
||||||
|
|
||||||
|
4. **文件传输失败**
|
||||||
|
- 检查被动模式端口范围是否被防火墙阻止
|
||||||
|
- 确保数据连接端口 (20000-20100) 可用
|
||||||
|
|
||||||
|
### 调试模式
|
||||||
|
|
||||||
|
服务器会在控制台输出详细的连接和命令日志,便于调试:
|
||||||
|
|
||||||
|
```
|
||||||
|
FTP Server started on 127.0.0.1:2121
|
||||||
|
New connection from ('127.0.0.1', 54321)
|
||||||
|
Received from ('127.0.0.1', 54321): USER admin
|
||||||
|
Sent to ('127.0.0.1', 54321): 331 User admin OK. Password required
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发说明
|
||||||
|
|
||||||
|
### 扩展功能
|
||||||
|
|
||||||
|
要添加新的 FTP 命令:
|
||||||
|
|
||||||
|
1. 在 `ftp_session.py` 的 `handle_command` 方法中添加命令路由
|
||||||
|
2. 实现对应的 `handle_xxx` 方法
|
||||||
|
3. 在 `config.py` 中添加相应的响应码
|
||||||
|
|
||||||
|
### 代码结构
|
||||||
|
|
||||||
|
- `FTPServer`: 主服务器类,处理客户端连接
|
||||||
|
- `FTPSession`: 单个客户端会话处理
|
||||||
|
- `config.py`: 集中配置管理
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
本项目仅用于教育和学习目的。
|
||||||
|
|
||||||
|
## 作者
|
||||||
|
|
||||||
|
计算机网络课程设计项目
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
更多详细信息请参阅 [设计报告](design_report.md)。
|
@ -403,9 +403,11 @@ python main.py
|
|||||||
|
|
||||||
### 2. Windows 客户端连接
|
### 2. Windows 客户端连接
|
||||||
```cmd
|
```cmd
|
||||||
ftp 127.0.0.1
|
ftp 127.0.0.1 2121
|
||||||
```
|
```
|
||||||
|
|
||||||
|
注意:由于使用非标准端口 2121(避免需要管理员权限),需要指定端口号。
|
||||||
|
|
||||||
### 3. 测试用户账号
|
### 3. 测试用户账号
|
||||||
| 用户名 | 密码 |
|
| 用户名 | 密码 |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
|
8
ftp_root/text1.txt
Normal file
8
ftp_root/text1.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
This is a test file for FTP download.
|
||||||
|
|
||||||
|
Content:
|
||||||
|
- Line 1: Hello World
|
||||||
|
- Line 2: FTP Server Test
|
||||||
|
- Line 3: File download working correctly
|
||||||
|
|
||||||
|
You can download this file using the GET command in FTP client.
|
@ -64,7 +64,9 @@ class FTPSession:
|
|||||||
args = parts[1] if len(parts) > 1 else ''
|
args = parts[1] if len(parts) > 1 else ''
|
||||||
|
|
||||||
# Handle command
|
# Handle command
|
||||||
self.handle_command(command, args)
|
should_quit = self.handle_command(command, args)
|
||||||
|
if should_quit:
|
||||||
|
break
|
||||||
|
|
||||||
except ConnectionResetError:
|
except ConnectionResetError:
|
||||||
print(f"Client {self.client_address} disconnected")
|
print(f"Client {self.client_address} disconnected")
|
||||||
@ -85,7 +87,7 @@ class FTPSession:
|
|||||||
elif command == 'PASS':
|
elif command == 'PASS':
|
||||||
self.handle_pass(args)
|
self.handle_pass(args)
|
||||||
elif command == 'QUIT':
|
elif command == 'QUIT':
|
||||||
self.handle_quit()
|
return self.handle_quit()
|
||||||
elif command == 'PWD':
|
elif command == 'PWD':
|
||||||
self.handle_pwd()
|
self.handle_pwd()
|
||||||
elif command == 'CWD':
|
elif command == 'CWD':
|
||||||
@ -106,6 +108,7 @@ class FTPSession:
|
|||||||
self.send_response('200')
|
self.send_response('200')
|
||||||
else:
|
else:
|
||||||
self.send_response('500', f'Command {command} not recognized')
|
self.send_response('500', f'Command {command} not recognized')
|
||||||
|
return False
|
||||||
|
|
||||||
def handle_user(self, username):
|
def handle_user(self, username):
|
||||||
"""Handle USER command"""
|
"""Handle USER command"""
|
||||||
@ -178,7 +181,9 @@ class FTPSession:
|
|||||||
new_path = os.path.normpath(new_path)
|
new_path = os.path.normpath(new_path)
|
||||||
|
|
||||||
# Security check: ensure path is within server root
|
# Security check: ensure path is within server root
|
||||||
if not new_path.startswith(SERVER_ROOT):
|
server_root_abs = os.path.abspath(SERVER_ROOT)
|
||||||
|
new_path_abs = os.path.abspath(new_path)
|
||||||
|
if not new_path_abs.startswith(server_root_abs):
|
||||||
self.send_response('550', 'Permission denied')
|
self.send_response('550', 'Permission denied')
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -356,8 +361,10 @@ class FTPSession:
|
|||||||
filepath = os.path.join(self.current_directory, filename)
|
filepath = os.path.join(self.current_directory, filename)
|
||||||
filepath = os.path.normpath(filepath)
|
filepath = os.path.normpath(filepath)
|
||||||
|
|
||||||
# Security check
|
# Security check: ensure path is within server root
|
||||||
if not filepath.startswith(SERVER_ROOT):
|
server_root_abs = os.path.abspath(SERVER_ROOT)
|
||||||
|
filepath_abs = os.path.abspath(filepath)
|
||||||
|
if not filepath_abs.startswith(server_root_abs):
|
||||||
self.send_response('550', 'Permission denied')
|
self.send_response('550', 'Permission denied')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
55
test_ftp_client.py
Normal file
55
test_ftp_client.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Simple FTP Client Test Script
|
||||||
|
Tests the basic functionality of our FTP server
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
|
||||||
|
def test_ftp_server():
|
||||||
|
"""Test basic FTP server functionality"""
|
||||||
|
print("Testing FTP Server...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Connect to FTP server
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.connect(('127.0.0.1', 2121))
|
||||||
|
|
||||||
|
# Read welcome message
|
||||||
|
response = sock.recv(1024).decode('utf-8')
|
||||||
|
print(f"Server: {response.strip()}")
|
||||||
|
|
||||||
|
# Test USER command
|
||||||
|
sock.send(b'USER admin\r\n')
|
||||||
|
response = sock.recv(1024).decode('utf-8')
|
||||||
|
print(f"Server: {response.strip()}")
|
||||||
|
|
||||||
|
# Test PASS command
|
||||||
|
sock.send(b'PASS admin123\r\n')
|
||||||
|
response = sock.recv(1024).decode('utf-8')
|
||||||
|
print(f"Server: {response.strip()}")
|
||||||
|
|
||||||
|
# Test PWD command
|
||||||
|
sock.send(b'PWD\r\n')
|
||||||
|
response = sock.recv(1024).decode('utf-8')
|
||||||
|
print(f"Server: {response.strip()}")
|
||||||
|
|
||||||
|
# Test SYST command
|
||||||
|
sock.send(b'SYST\r\n')
|
||||||
|
response = sock.recv(1024).decode('utf-8')
|
||||||
|
print(f"Server: {response.strip()}")
|
||||||
|
|
||||||
|
# Test QUIT command
|
||||||
|
sock.send(b'QUIT\r\n')
|
||||||
|
response = sock.recv(1024).decode('utf-8')
|
||||||
|
print(f"Server: {response.strip()}")
|
||||||
|
|
||||||
|
sock.close()
|
||||||
|
print("✅ Basic FTP commands test passed!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Test failed: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_ftp_server()
|
Loading…
x
Reference in New Issue
Block a user