# 文件传输协议 ## 🎯 学习目标 通过本章学习,您将能够: - 理解文件传输的基本原理和协议设计 - 掌握文件上传、下载的完整流程 - 学会设计安全可靠的文件传输系统 - 在Chat-Room项目中实现完整的文件传输功能 ## 📁 文件传输架构 ### 文件传输协议概览 ```mermaid graph TB subgraph "文件传输协议栈" A[应用层协议
File Transfer Protocol] --> B[文件操作
File Operations] B --> C[分块传输
Chunked Transfer] C --> D[数据完整性
Data Integrity] D --> E[传输层
TCP/HTTP] A --> A1[上传协议
Upload Protocol] A --> A2[下载协议
Download Protocol] A --> A3[管理协议
Management Protocol] B --> B1[文件元数据
File Metadata] B --> B2[权限验证
Permission Check] B --> B3[存储管理
Storage Management] C --> C1[分块策略
Chunk Strategy] C --> C2[断点续传
Resume Transfer] C --> C3[并发传输
Concurrent Transfer] D --> D1[校验和
Checksum] D --> D2[错误检测
Error Detection] D --> D3[重传机制
Retransmission] end style A fill:#e8f5e8 style B fill:#fff3cd style C fill:#f8d7da style D fill:#d1ecf1 ``` ### 文件传输流程 ```mermaid sequenceDiagram participant C as 客户端 participant S as 服务器 participant FS as 文件系统 participant DB as 数据库 Note over C,DB: 文件上传流程 C->>S: 1. 上传请求(文件信息) S->>S: 2. 验证文件类型和大小 S->>DB: 3. 检查存储配额 DB->>S: 4. 返回配额信息 S->>FS: 5. 创建临时文件 FS->>S: 6. 返回文件句柄 S->>C: 7. 上传许可(分块信息) loop 分块上传 C->>S: 8. 发送文件块 S->>FS: 9. 写入文件块 S->>C: 10. 确认接收 end C->>S: 11. 上传完成 S->>S: 12. 验证文件完整性 S->>FS: 13. 移动到正式目录 S->>DB: 14. 保存文件记录 S->>C: 15. 上传成功响应 Note over C,DB: 文件下载流程 C->>S: 1. 下载请求(文件ID) S->>DB: 2. 查询文件信息 DB->>S: 3. 返回文件元数据 S->>S: 4. 验证下载权限 S->>FS: 5. 检查文件存在 S->>C: 6. 下载许可(文件信息) loop 分块下载 C->>S: 7. 请求文件块 S->>FS: 8. 读取文件块 S->>C: 9. 发送文件块 end C->>S: 10. 下载完成确认 ``` ## 📋 文件协议设计 ### 协议消息定义 ```python # shared/protocol/file_protocol.py - 文件传输协议 from dataclasses import dataclass from typing import Optional, Dict, Any, List from enum import Enum import hashlib import time class FileOperationType(Enum): """文件操作类型""" UPLOAD_REQUEST = "upload_request" UPLOAD_RESPONSE = "upload_response" UPLOAD_CHUNK = "upload_chunk" UPLOAD_COMPLETE = "upload_complete" DOWNLOAD_REQUEST = "download_request" DOWNLOAD_RESPONSE = "download_response" DOWNLOAD_CHUNK = "download_chunk" FILE_LIST_REQUEST = "file_list_request" FILE_LIST_RESPONSE = "file_list_response" FILE_DELETE_REQUEST = "file_delete_request" FILE_DELETE_RESPONSE = "file_delete_response" class FileTransferStatus(Enum): """文件传输状态""" PENDING = "pending" IN_PROGRESS = "in_progress" COMPLETED = "completed" FAILED = "failed" CANCELLED = "cancelled" PAUSED = "paused" @dataclass class FileMetadata: """文件元数据""" file_id: str filename: str file_size: int file_type: str mime_type: str checksum: str upload_time: float uploader_id: int description: Optional[str] = None tags: Optional[List[str]] = None def to_dict(self) -> Dict[str, Any]: """转换为字典""" return { 'file_id': self.file_id, 'filename': self.filename, 'file_size': self.file_size, 'file_type': self.file_type, 'mime_type': self.mime_type, 'checksum': self.checksum, 'upload_time': self.upload_time, 'uploader_id': self.uploader_id, 'description': self.description, 'tags': self.tags or [] } @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'FileMetadata': """从字典创建""" return cls( file_id=data['file_id'], filename=data['filename'], file_size=data['file_size'], file_type=data['file_type'], mime_type=data['mime_type'], checksum=data['checksum'], upload_time=data['upload_time'], uploader_id=data['uploader_id'], description=data.get('description'), tags=data.get('tags', []) ) @dataclass class FileChunk: """文件块""" chunk_id: int chunk_size: int chunk_data: bytes chunk_checksum: str is_last_chunk: bool = False def __post_init__(self): """计算块校验和""" if not self.chunk_checksum: self.chunk_checksum = hashlib.md5(self.chunk_data).hexdigest() def verify_checksum(self) -> bool: """验证块校验和""" calculated_checksum = hashlib.md5(self.chunk_data).hexdigest() return calculated_checksum == self.chunk_checksum @dataclass class UploadRequest: """上传请求""" filename: str file_size: int file_type: str mime_type: str checksum: str chunk_size: int = 64 * 1024 # 64KB默认块大小 description: Optional[str] = None tags: Optional[List[str]] = None def to_dict(self) -> Dict[str, Any]: """转换为字典""" return { 'type': FileOperationType.UPLOAD_REQUEST.value, 'filename': self.filename, 'file_size': self.file_size, 'file_type': self.file_type, 'mime_type': self.mime_type, 'checksum': self.checksum, 'chunk_size': self.chunk_size, 'description': self.description, 'tags': self.tags or [] } @dataclass class UploadResponse: """上传响应""" success: bool file_id: Optional[str] = None upload_url: Optional[str] = None chunk_size: int = 64 * 1024 total_chunks: int = 0 message: str = "" error_code: Optional[str] = None def to_dict(self) -> Dict[str, Any]: """转换为字典""" return { 'type': FileOperationType.UPLOAD_RESPONSE.value, 'success': self.success, 'file_id': self.file_id, 'upload_url': self.upload_url, 'chunk_size': self.chunk_size, 'total_chunks': self.total_chunks, 'message': self.message, 'error_code': self.error_code } @dataclass class DownloadRequest: """下载请求""" file_id: str range_start: Optional[int] = None range_end: Optional[int] = None def to_dict(self) -> Dict[str, Any]: """转换为字典""" return { 'type': FileOperationType.DOWNLOAD_REQUEST.value, 'file_id': self.file_id, 'range_start': self.range_start, 'range_end': self.range_end } @dataclass class DownloadResponse: """下载响应""" success: bool file_metadata: Optional[FileMetadata] = None download_url: Optional[str] = None message: str = "" error_code: Optional[str] = None def to_dict(self) -> Dict[str, Any]: """转换为字典""" return { 'type': FileOperationType.DOWNLOAD_RESPONSE.value, 'success': self.success, 'file_metadata': self.file_metadata.to_dict() if self.file_metadata else None, 'download_url': self.download_url, 'message': self.message, 'error_code': self.error_code } class FileProtocolHandler: """ 文件协议处理器 负责处理文件传输相关的协议消息 """ def __init__(self): self.supported_types = { # 图片类型 'image/jpeg', 'image/png', 'image/gif', 'image/webp', # 文档类型 'application/pdf', 'text/plain', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', # 压缩文件 'application/zip', 'application/x-rar-compressed', # 音频视频 'audio/mpeg', 'video/mp4', 'video/avi' } self.max_file_size = 100 * 1024 * 1024 # 100MB self.max_chunk_size = 1024 * 1024 # 1MB self.min_chunk_size = 1024 # 1KB def validate_upload_request(self, request: UploadRequest) -> tuple[bool, str]: """ 验证上传请求 Returns: (是否有效, 错误信息) """ # 检查文件名 if not request.filename or len(request.filename.strip()) == 0: return False, "文件名不能为空" if len(request.filename) > 255: return False, "文件名过长" # 检查文件大小 if request.file_size <= 0: return False, "文件大小无效" if request.file_size > self.max_file_size: return False, f"文件大小超过限制({self.max_file_size // (1024*1024)}MB)" # 检查文件类型 if request.mime_type not in self.supported_types: return False, f"不支持的文件类型: {request.mime_type}" # 检查块大小 if not (self.min_chunk_size <= request.chunk_size <= self.max_chunk_size): return False, f"块大小必须在{self.min_chunk_size}-{self.max_chunk_size}字节之间" # 检查校验和 if not request.checksum or len(request.checksum) != 32: return False, "文件校验和格式错误" return True, "验证通过" def calculate_chunks(self, file_size: int, chunk_size: int) -> int: """计算文件块数量""" return (file_size + chunk_size - 1) // chunk_size def generate_file_id(self, filename: str, file_size: int, checksum: str) -> str: """生成文件ID""" import uuid content = f"{filename}_{file_size}_{checksum}_{time.time()}" return hashlib.md5(content.encode()).hexdigest() def get_file_extension(self, filename: str) -> str: """获取文件扩展名""" return filename.split('.')[-1].lower() if '.' in filename else '' def get_mime_type_from_extension(self, extension: str) -> str: """根据扩展名获取MIME类型""" mime_map = { 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png', 'gif': 'image/gif', 'webp': 'image/webp', 'pdf': 'application/pdf', 'txt': 'text/plain', 'doc': 'application/msword', 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'zip': 'application/zip', 'rar': 'application/x-rar-compressed', 'mp3': 'audio/mpeg', 'mp4': 'video/mp4', 'avi': 'video/avi' } return mime_map.get(extension, 'application/octet-stream') def create_upload_response(self, success: bool, file_id: str = None, chunk_size: int = 64*1024, total_chunks: int = 0, message: str = "", error_code: str = None) -> UploadResponse: """创建上传响应""" return UploadResponse( success=success, file_id=file_id, chunk_size=chunk_size, total_chunks=total_chunks, message=message, error_code=error_code ) def create_download_response(self, success: bool, file_metadata: FileMetadata = None, message: str = "", error_code: str = None) -> DownloadResponse: """创建下载响应""" return DownloadResponse( success=success, file_metadata=file_metadata, message=message, error_code=error_code ) # 使用示例 def demo_file_protocol(): """文件协议演示""" handler = FileProtocolHandler() print("=== 文件协议演示 ===") # 创建上传请求 upload_req = UploadRequest( filename="test_image.jpg", file_size=1024 * 1024, # 1MB file_type="image", mime_type="image/jpeg", checksum="d41d8cd98f00b204e9800998ecf8427e", description="测试图片" ) print(f"上传请求: {upload_req.to_dict()}") # 验证上传请求 is_valid, message = handler.validate_upload_request(upload_req) print(f"请求验证: {is_valid}, {message}") if is_valid: # 生成文件ID file_id = handler.generate_file_id( upload_req.filename, upload_req.file_size, upload_req.checksum ) # 计算块数量 total_chunks = handler.calculate_chunks(upload_req.file_size, upload_req.chunk_size) # 创建上传响应 upload_resp = handler.create_upload_response( success=True, file_id=file_id, chunk_size=upload_req.chunk_size, total_chunks=total_chunks, message="上传请求已接受" ) print(f"上传响应: {upload_resp.to_dict()}") # 创建下载请求 download_req = DownloadRequest(file_id="test_file_id") print(f"下载请求: {download_req.to_dict()}") if __name__ == "__main__": demo_file_protocol() ``` ## 📖 导航 ➡️ **下一节:** [Chunked Transfer](chunked-transfer.md) 📚 **返回:** [第10章:文件传输](README.md) 🏠 **主页:** [学习路径总览](../README.md)