计算机网络课程设计--基于TCP协议的文件传输系统
目录
1、课程设计目的
2、课程设计要求
2.1 功能实现要求
2.2 代码工作量要求
2.3 演示与报告要求
3、相关知识
3.1 TCP 协议原理
3.2 Socket 编程基础
3.3 文件操作与传输
3.4 多线程编程
4、课程设计分析
4.1 系统架构设计
4.1.1 整体架构
4.1.2 数据传输流程
4.2 服务器端实现分析
4.2.1 核心类与功能
4.2.2 关键方法解析
4.3 客户端实现分析
4.3.1 核心类与功能
4.3.2 交互流程
4.4 关键技术点
5、相关扩展
6、源代码
6.1 server
6.2 client
1、课程设计目的
1.1 深入理解 TCP 协议的工作原理及其在网络编程中的应用
1.2 掌握 Socket 编程技术,实现客户端 - 服务器架构的网络应用
1.3 学习文件传输的基本原理和实现方法,包括文件分块传输、进度显示等
1.4 理解多线程编程在网络服务器中的应用,实现并发处理多个客户端连接
1.5 掌握网络应用程序的错误处理和异常处理机制
1.6 通过实际项目设计,提升综合运用计算机网络知识解决实际问题的能力
2、课程设计要求
2.1 功能实现要求
设计并实现基于 TCP 协议的文件传输系统,包含客户端和服务器端,支持文件上传、下载、列表查询及删除功能,确保核心功能完整可用。
2.2 代码工作量要求
代码需体现一定的复杂性和完整性,包含模块化设计、异常处理、多线程支持等,避免简单的单一功能实现。
2.3 演示与报告要求
通过代码功能演示验证系统可用性,包括界面操作、功能流程展示
编写课程设计报告,详细说明设计思路、技术实现和心得体会
3、相关知识
3.1 TCP 协议原理
面向连接的可靠传输:通过三次握手建立连接,四次挥手断开连接,保证数据有序、无丢失传输
流量控制与拥塞控制:使用滑动窗口机制控制数据发送速率,通过慢启动、拥塞避免等算法应对网络拥塞
字节流传输:数据以字节流形式传输,无消息边界,需在应用层处理数据分块
3.2 Socket 编程基础
Socket 通信模型:服务器端通过绑定端口、监听连接、接受客户端请求建立通信
TCP Socket 关键方法:bind()、listen()、accept()(服务器端),connect()(客户端)
数据传输:通过send()和recv()方法实现字节数据的发送与接收
3.3 文件操作与传输
文件分块传输:大文件拆分为固定大小数据块传输,避免内存溢出
文件元数据传输:通过 JSON 格式传输文件名、文件大小等元信息
进度计算:根据已传输字节数与总文件大小的比例计算传输进度
3.4 多线程编程
并发处理:服务器通过多线程同时处理多个客户端连接,提升系统吞吐量
线程安全:需注意共享资源(如文件目录)的访问同步,避免数据冲突
4、课程设计分析
4.1 系统架构设计
4.1.1 整体架构
C/S 架构:客户端(Client)与服务器端(Server)通过 TCP 协议建立连接
模块化设计:客户端和服务器端分别封装为独立类,各功能模块解耦(如文件传输、命令处理、界面交互)
4.1.2 数据传输流程
命令交互:使用 JSON 格式传输命令与响应(如{"command": "upload", "filename": "test.txt"})
文件传输:
上传:客户端分块发送文件数据,服务器接收并写入磁盘
下载:服务器分块读取文件数据,客户端接收并保存
删除:客户端发送删除命令,服务器验证后删除文件
4.2 服务器端实现分析
4.2.1 核心类与功能
FileTransferServer 类:
启动与监听:通过start()方法绑定端口并监听客户端连接
多线程处理:为每个客户端创建独立线程(handle_client方法),避免阻塞
命令处理:支持list(列表查询)、upload(上传)、download(下载)、delete(删除)命令
4.2.2 关键方法解析
handle_upload 方法:
接收文件元数据(文件名、大小)
分块接收文件数据,写入磁盘
错误处理:上传失败时删除不完整文件
handle_delete 方法:
验证文件存在性
执行删除操作并返回结果
4.3 客户端实现分析
4.3.1 核心类与功能
FileTransferClient 类:
连接管理:connect()和disconnect()方法管理服务器连接
文件操作:实现upload_file(上传)、download_file(下载)、delete_file(删除)功能
用户交互:通过命令行菜单提供操作界面
4.3.2 交互流程
列表查询:发送list命令获取服务器文件列表并显示
文件上传 / 下载:
显示进度条与传输速度(进度: XX% | 速度: XX KB/s)
通过字节数计算实时传输状态
文件删除:
显示文件列表供用户选择
确认后发送删除命令并反馈结果
4.4 关键技术点
TCP 可靠性保证:利用 TCP 协议的确认机制、重传机制确保文件数据完整传输
流式数据处理:通过固定缓冲区(4096 字节)分块处理大文件,避免内存压力
异常处理:服务器与客户端均包含完整的异常捕获逻辑(如文件不存在、传输中断)
并发处理:服务器使用多线程技术,支持同时处理多个客户端请求
5、相关扩展
断点续传:
拓展功能代码:在服务器和客户端代码中,我们添加了对已传输字节偏移量的记录和恢复功能。具体来说,我们使用元数据文件(文件名加上.meta后缀)来保存传输状态。在上传和下载文件时,会检查元数据文件是否存在,如果存在则恢复传输。
上传文件:客户端会发送已传输字节偏移量给服务器,服务器会根据偏移量继续接收文件。
下载文件:客户端会发送已传输字节偏移量给服务器,服务器会根据偏移量继续发送文件。
6、源代码
6.1 server
import socket
import os
import json
import threading
import time
import ssl # 拓展:SSL/TLS加密模块class FileTransferServer:def __init__(self, host='localhost', port=9999, buffer_size=4096, upload_dir='./server_files',use_ssl=True, cert_file='./cert/server.crt', key_file='./cert/server.key'):self.host = hostself.port = portself.buffer_size = buffer_sizeself.upload_dir = upload_dirself.server_socket = Noneself.use_ssl = use_ssl # 拓展:是否使用SSLself.cert_file = cert_file # 拓展:证书文件路径self.key_file = key_file # 拓展:私钥文件路径# 确保上传目录存在if not os.path.exists(upload_dir):os.makedirs(upload_dir)def start(self):"""启动服务器"""self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)try:self.server_socket.bind((self.host, self.port))self.server_socket.listen(5)print(f"服务器已启动,监听地址: {self.host}:{self.port}")print("等待客户端连接...")while True:client_socket, client_address = self.server_socket.accept()print(f"客户端 {client_address} 已连接")# 为每个客户端创建一个线程client_thread = threading.Thread(target=self.handle_client, args=(client_socket,))client_thread.daemon = Trueclient_thread.start()except Exception as e:print(f"服务器启动失败: {e}")finally:if self.server_socket:self.server_socket.close()def handle_client(self, client_socket):"""处理客户端请求"""try:while True:# 接收客户端命令command_data = client_socket.recv(self.buffer_size).decode('utf-8')if not command_data:breakcommand = json.loads(command_data)cmd_type = command.get('command')if cmd_type == 'list':self.list_files(client_socket)elif cmd_type == 'upload':self.handle_upload(client_socket, command)elif cmd_type == 'download':self.handle_download(client_socket, command)elif cmd_type == 'delete':self.handle_delete(client_socket, command)elif cmd_type == 'exit':breakelse:response = {'status': 'error', 'message': '未知命令'}client_socket.send(json.dumps(response).encode('utf-8'))print("客户端已断开连接")except Exception as e:print(f"处理客户端请求时出错: {e}")finally:client_socket.close()def list_files(self, client_socket):"""列出服务器上的文件"""try:files = os.listdir(self.upload_dir)response = {'status': 'success', 'files': files}client_socket.send(json.dumps(response).encode('utf-8'))except Exception as e:response = {'status': 'error', 'message': f'获取文件列表失败: {str(e)}'}client_socket.send(json.dumps(response).encode('utf-8'))def handle_upload(self, client_socket, command):"""处理文件上传"""filename = command.get('filename')filesize = command.get('filesize')offset = command.get('offset', 0) # 拓展功能代码:获取已传输字节偏移量if not filename or not filesize:response = {'status': 'error', 'message': '缺少文件名或文件大小'}client_socket.send(json.dumps(response).encode('utf-8'))returnfile_path = os.path.join(self.upload_dir, filename)metadata_path = f"{file_path}.meta" # 拓展功能代码:元数据文件路径# 检查文件是否已存在if os.path.exists(file_path):if offset == 0:response = {'status': 'error', 'message': '文件已存在'}client_socket.send(json.dumps(response).encode('utf-8'))returnelse:# 恢复传输try:with open(metadata_path, 'r') as meta_file:meta_data = json.load(meta_file)offset = meta_data.get('offset', 0)except FileNotFoundError:offset = 0# 准备接收文件response = {'status': 'ready', 'offset': offset} # 拓展功能代码:发送偏移量给客户端client_socket.send(json.dumps(response).encode('utf-8'))# 接收文件数据try:with open(file_path, 'ab') as f: # 拓展功能代码:以追加模式打开文件f.seek(offset) # 拓展功能代码:移动文件指针到偏移量位置bytes_received = offsetwhile bytes_received < filesize:data = client_socket.recv(self.buffer_size)if not data:breakf.write(data)bytes_received += len(data)# 保存传输状态 拓展功能代码with open(metadata_path, 'w') as meta_file:json.dump({'offset': bytes_received}, meta_file)# 上传完成,删除元数据文件 拓展功能代码if os.path.exists(metadata_path):os.remove(metadata_path)response = {'status': 'success', 'message': f'文件 {filename} 上传成功'}client_socket.send(json.dumps(response).encode('utf-8'))except Exception as e:# 上传失败,删除不完整文件if os.path.exists(file_path):os.remove(file_path)if os.path.exists(metadata_path):os.remove(metadata_path)response = {'status': 'error', 'message': f'上传失败: {str(e)}'}client_socket.send(json.dumps(response).encode('utf-8'))def handle_download(self, client_socket, command):"""处理文件下载"""filename = command.get('filename')offset = command.get('offset', 0) # 拓展功能代码:获取已传输字节偏移量if not filename:response = {'status': 'error', 'message': '缺少文件名'}client_socket.send(json.dumps(response).encode('utf-8'))returnfile_path = os.path.join(self.upload_dir, filename)metadata_path = f"{file_path}.meta" # 拓展功能代码:元数据文件路径# 检查文件是否存在if not os.path.exists(file_path):response = {'status': 'error', 'message': '文件不存在'}client_socket.send(json.dumps(response).encode('utf-8'))returnfilesize = os.path.getsize(file_path)# 发送文件信息response = {'status': 'success', 'filesize': filesize, 'offset': offset} # 拓展功能代码:发送偏移量给客户端client_socket.send(json.dumps(response).encode('utf-8'))# 等待客户端确认confirmation_data = client_socket.recv(self.buffer_size).decode('utf-8')confirmation = json.loads(confirmation_data)if confirmation.get('status') != 'ready':return# 发送文件数据try:with open(file_path, 'rb') as f:f.seek(offset) # 拓展功能代码:移动文件指针到偏移量位置bytes_sent = offsetwhile bytes_sent < filesize:data = f.read(self.buffer_size)if not data:breakclient_socket.send(data)bytes_sent += len(data)# 保存传输状态 拓展功能代码with open(metadata_path, 'w') as meta_file:json.dump({'offset': bytes_sent}, meta_file)# 下载完成,删除元数据文件 拓展功能代码if os.path.exists(metadata_path):os.remove(metadata_path)except Exception as e:print(f"发送文件失败: {str(e)}")def handle_delete(self, client_socket, command):"""处理文件删除"""filename = command.get('filename')if not filename:response = {'status': 'error', 'message': '缺少文件名'}client_socket.send(json.dumps(response).encode('utf-8'))returnfile_path = os.path.join(self.upload_dir, filename)metadata_path = f"{file_path}.meta" # 拓展功能代码:元数据文件路径# 检查文件是否存在if not os.path.exists(file_path):response = {'status': 'error', 'message': '文件不存在'}client_socket.send(json.dumps(response).encode('utf-8'))return# 尝试删除文件try:os.remove(file_path)if os.path.exists(metadata_path):os.remove(metadata_path) # 拓展功能代码:删除元数据文件response = {'status': 'success', 'message': f'文件 {filename} 已删除'}client_socket.send(json.dumps(response).encode('utf-8'))except Exception as e:response = {'status': 'error', 'message': f'删除文件失败: {str(e)}'}client_socket.send(json.dumps(response).encode('utf-8'))def main():server = FileTransferServer()server.start()if __name__ == "__main__":main()
6.2 client
import socket
import os
import json
import timeclass FileTransferClient:def __init__(self, host='localhost', port=9999, buffer_size=4096, download_dir='./client_files'):self.host = hostself.port = portself.buffer_size = buffer_sizeself.download_dir = download_dirself.client_socket = None# 确保下载目录存在if not os.path.exists(download_dir):os.makedirs(download_dir)def connect(self):"""连接到服务器"""self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)try:self.client_socket.connect((self.host, self.port))print(f"已连接到服务器 {self.host}:{self.port}")return Trueexcept Exception as e:print(f"连接服务器失败: {e}")return Falsedef disconnect(self):"""断开与服务器的连接"""if self.client_socket:self.client_socket.close()print("已断开与服务器的连接")def list_files(self):"""获取服务器文件列表"""command = {'command': 'list'}self.client_socket.send(json.dumps(command).encode('utf-8'))response_data = self.client_socket.recv(self.buffer_size).decode('utf-8')response = json.loads(response_data)if response.get('status') == 'success':files = response.get('files', [])print("\n服务器文件列表:")if not files:print(" 空")else:for i, file in enumerate(files, 1):print(f" {i}. {file}")return fileselse:print(f"获取文件列表失败: {response.get('message')}")return []def upload_file(self, file_path):"""上传文件到服务器"""if not os.path.isfile(file_path):print(f"文件不存在: {file_path}")returnfilename = os.path.basename(file_path)filesize = os.path.getsize(file_path)metadata_path = f"{file_path}.meta" # 拓展功能代码:元数据文件路径# 检查是否有中断的传输 拓展功能代码offset = 0if os.path.exists(metadata_path):try:with open(metadata_path, 'r') as meta_file:meta_data = json.load(meta_file)offset = meta_data.get('offset', 0)except FileNotFoundError:offset = 0command = {'command': 'upload', 'filename': filename, 'filesize': filesize, 'offset': offset}self.client_socket.send(json.dumps(command).encode('utf-8'))# 等待服务器确认response_data = self.client_socket.recv(self.buffer_size).decode('utf-8')response = json.loads(response_data)if response.get('status') != 'ready':print(f"服务器准备失败: {response.get('message')}")returnserver_offset = response.get('offset', 0) # 拓展功能代码:获取服务器返回的偏移量if server_offset != offset:offset = server_offset# 发送文件数据with open(file_path, 'rb') as f:f.seek(offset) # 拓展功能代码:移动文件指针到偏移量位置bytes_sent = offsetstart_time = time.time()while bytes_sent < filesize:data = f.read(self.buffer_size)if not data:breakself.client_socket.send(data)bytes_sent += len(data)# 保存传输状态 拓展功能代码with open(metadata_path, 'w') as meta_file:json.dump({'offset': bytes_sent}, meta_file)# 显示上传进度progress = (bytes_sent / filesize) * 100elapsed_time = time.time() - start_timespeed = bytes_sent / (elapsed_time + 0.001) / 1024 # KB/sprint(f"\r上传进度: {progress:.2f}% | 速度: {speed:.2f} KB/s", end='')print("\n上传完成")# 上传完成,删除元数据文件 拓展功能代码if os.path.exists(metadata_path):os.remove(metadata_path)# 等待上传结果response_data = self.client_socket.recv(self.buffer_size).decode('utf-8')response = json.loads(response_data)if response.get('status') == 'success':print(response.get('message'))else:print(f"上传失败: {response.get('message')}")def download_file(self, filename):"""从服务器下载文件"""download_path = os.path.join(self.download_dir, filename)metadata_path = f"{download_path}.meta" # 拓展功能代码:元数据文件路径# 检查是否有中断的传输 拓展功能代码offset = 0if os.path.exists(download_path):if os.path.exists(metadata_path):try:with open(metadata_path, 'r') as meta_file:meta_data = json.load(meta_file)offset = meta_data.get('offset', 0)except FileNotFoundError:offset = 0command = {'command': 'download', 'filename': filename, 'offset': offset}self.client_socket.send(json.dumps(command).encode('utf-8'))# 接收文件信息response_data = self.client_socket.recv(self.buffer_size).decode('utf-8')response = json.loads(response_data)if response.get('status') != 'success':print(f"下载失败: {response.get('message')}")returnfilesize = response.get('filesize')server_offset = response.get('offset', 0) # 拓展功能代码:获取服务器返回的偏移量if server_offset != offset:offset = server_offset# 发送准备好的确认confirmation = {'status': 'ready'}self.client_socket.send(json.dumps(confirmation).encode('utf-8'))# 接收文件数据with open(download_path, 'ab') as f: # 拓展功能代码:以追加模式打开文件f.seek(offset) # 拓展功能代码:移动文件指针到偏移量位置bytes_received = offsetstart_time = time.time()while bytes_received < filesize:data = self.client_socket.recv(self.buffer_size)if not data:breakf.write(data)bytes_received += len(data)# 保存传输状态 拓展功能代码with open(metadata_path, 'w') as meta_file:json.dump({'offset': bytes_received}, meta_file)# 显示下载进度progress = (bytes_received / filesize) * 100elapsed_time = time.time() - start_timespeed = bytes_received / (elapsed_time + 0.001) / 1024 # KB/sprint(f"\r下载进度: {progress:.2f}% | 速度: {speed:.2f} KB/s", end='')print("\n下载完成")print(f"文件已保存至: {download_path}")# 下载完成,删除元数据文件 拓展功能代码if os.path.exists(metadata_path):os.remove(metadata_path)def delete_file(self, filename):"""删除服务器上的文件"""command = {'command': 'delete', 'filename': filename}self.client_socket.send(json.dumps(command).encode('utf-8'))# 接收删除结果response_data = self.client_socket.recv(self.buffer_size).decode('utf-8')response = json.loads(response_data)if response.get('status') == 'success':print(f"文件 '{filename}' 已成功删除")else:print(f"删除文件失败: {response.get('message')}")def main():client = FileTransferClient()if not client.connect():returntry:while True:print("\n=== 文件传输系统 ===")print("1. 查看服务器文件列表")print("2. 上传文件到服务器")print("3. 从服务器下载文件")print("4. 删除服务器文件")print("5. 退出")choice = input("请选择操作 (1-5): ")if choice == '1':client.list_files()elif choice == '2':file_path = input("请输入要上传的文件路径: ")client.upload_file(file_path)elif choice == '3':client.list_files()file_num = input("请输入要下载的文件编号: ")try:file_num = int(file_num)files = client.list_files()if 1 <= file_num <= len(files):client.download_file(files[file_num - 1])else:print("无效的文件编号")except ValueError:print("请输入有效的数字")elif choice == '4':client.list_files()file_num = input("请输入要删除的文件编号: ")try:file_num = int(file_num)files = client.list_files()if 1 <= file_num <= len(files):confirm = input(f"确定要删除文件 '{files[file_num - 1]}' 吗?(y/n): ")if confirm.lower() == 'y':client.delete_file(files[file_num - 1])else:print("无效的文件编号")except ValueError:print("请输入有效的数字")elif choice == '5':command = {'command': 'exit'}client.client_socket.send(json.dumps(command).encode('utf-8'))breakelse:print("无效的选择,请重新输入")finally:client.disconnect()if __name__ == "__main__":main()