能否同时实现Python Socket服务端与客户端?双向文件传输咨询
Python Socket实现双向文件传输(兼具服务端/客户端功能)
完全可以做到。Socket本身就是基于双向通信的,只要设计好通信逻辑,一个程序既可以作为服务端监听连接,也能作为客户端发起连接,建立连接后双方还能主动发起文件传输请求或发送文件。
核心思路
- 用线程分离读写操作:单线程下收发会互相阻塞,必须用两个独立线程——一个负责监听并处理对方的消息/文件,另一个负责处理本地的发送请求,避免程序卡住。
- 定义统一通信协议:Socket传输的是字节流,必须约定规则区分不同类型的消息(比如文件请求、文件元数据、文件内容),还要解决粘包问题(比如用固定长度的消息头标识后续数据长度)。
具体实现步骤
1. 基础连接逻辑
- 服务端模式:创建TCP套接字,绑定端口后监听,等待客户端连接。
- 客户端模式:创建套接字,主动连接指定的服务端地址和端口。
- 可以通过命令行参数选择运行模式,比如
python transfer.py server 8000启动服务端,python transfer.py client 127.0.0.1 8000启动客户端。
2. 通信协议设计(解决粘包+区分消息)
约定每个消息的前4字节为消息长度(用struct模块打包成大端字节),后面跟着具体消息内容,用前缀标识消息类型:
REQ_FILE:<文件名>:请求对方发送指定文件FILE_INFO:<文件名>:<文件大小>:发送文件元数据,告知对方文件名和大小FILE_DATA:<十六进制数据块>:发送文件内容分块(转十六进制避免编码问题)TRANS_DONE:标记文件传输完成ERROR:<错误信息>:传输出错时返回提示
3. 核心功能实现
通用消息收发函数(解决粘包)
import struct def send_message(sock, content): # 先发送4字节的消息长度(大端格式) msg_len = struct.pack('!I', len(content)) sock.sendall(msg_len) sock.sendall(content.encode('utf-8')) def recv_message(sock): # 先读取4字节的长度 len_bytes = sock.recv(4) if not len_bytes: return None msg_len = struct.unpack('!I', len_bytes)[0] # 循环接收直到拿到完整消息 full_msg = b'' while len(full_msg) < msg_len: chunk = sock.recv(min(msg_len - len(full_msg), 4096)) if not chunk: return None full_msg += chunk return full_msg.decode('utf-8')
文件发送逻辑
import os def send_target_file(sock, file_path): if not os.path.exists(file_path): send_message(sock, 'ERROR:目标文件不存在') return # 获取文件名和大小 file_name = os.path.basename(file_path) file_size = os.path.getsize(file_path) # 发送文件元数据 send_message(sock, f'FILE_INFO:{file_name}:{file_size}') # 分块发送文件内容 with open(file_path, 'rb') as f: while True: chunk = f.read(4096) if not chunk: break # 转成十六进制发送,避免字节编码问题 send_message(sock, f'FILE_DATA:{chunk.hex()}') # 发送完成信号 send_message(sock, 'TRANS_DONE')
文件接收逻辑
def receive_incoming_file(sock): msg = recv_message(sock) if not msg.startswith('FILE_INFO:'): print(f'无效的文件信息: {msg}') return # 解析文件名和大小 _, file_name, file_size = msg.split(':', 2) file_size = int(file_size) received_bytes = 0 # 写入本地文件 with open(file_name, 'wb') as f: while received_bytes < file_size: data_msg = recv_message(sock) if data_msg == 'TRANS_DONE': break if data_msg.startswith('FILE_DATA:'): chunk = bytes.fromhex(data_msg.split(':', 1)[1]) f.write(chunk) received_bytes += len(chunk) print(f'文件 {file_name} 接收完成,共接收 {received_bytes} 字节')
4. 线程调度与主逻辑
建立连接后,启动两个线程:一个持续监听对方消息并处理,另一个接收用户输入并发起发送/请求操作:
import socket import threading def handle_connection(sock): # 接收线程:处理对方发来的所有消息 def receive_loop(): while True: msg = recv_message(sock) if not msg: print('连接已断开') break if msg.startswith('REQ_FILE:'): # 处理文件请求 target_file = msg.split(':', 1)[1] send_target_file(sock, target_file) elif msg.startswith('FILE_INFO:'): # 开始接收文件 receive_incoming_file(sock) elif msg == 'TRANS_DONE': print('对方已完成文件传输') elif msg.startswith('ERROR:'): print(f'错误: {msg.split(":",1)[1]}') else: print(f'收到消息: {msg}') # 发送线程:处理用户输入的命令 def send_loop(): while True: cmd = input('输入命令(send <文件路径> / request <文件名> / exit): ') if cmd.startswith('send '): file_path = cmd.split(' ', 1)[1] send_target_file(sock, file_path) elif cmd.startswith('request '): file_name = cmd.split(' ', 1)[1] send_message(sock, f'REQ_FILE:{file_name}') elif cmd == 'exit': sock.close() break else: print('无效命令,请重新输入') # 启动两个线程,接收线程设为守护线程,随主线程退出 threading.Thread(target=receive_loop, daemon=True).start() send_loop() # 启动服务端 def run_server(port): server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_sock.bind(('0.0.0.0', port)) server_sock.listen(1) print(f'服务端已启动,监听端口 {port}') client_sock, client_addr = server_sock.accept() print(f'已连接客户端: {client_addr}') handle_connection(client_sock) # 启动客户端 def run_client(host, port): client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_sock.connect((host, port)) print(f'已连接到服务端 {host}:{port}') handle_connection(client_sock) # 程序入口 if __name__ == '__main__': import sys if len(sys.argv) < 3: print('使用方法:') print('作为服务端: python file_transfer.py server <端口号>') print('作为客户端: python file_transfer.py client <服务端IP> <端口号>') sys.exit(1) if sys.argv[1] == 'server': run_server(int(sys.argv[2])) elif sys.argv[1] == 'client': run_client(sys.argv[2], int(sys.argv[3]))
注意事项
- 防火墙和端口开放:跨机器传输时,需确保服务端端口在防火墙中开放。
- 大文件优化:可调整分块大小(比如改成16KB),或加入进度条显示传输进度。
- 异常处理:可增加更多异常捕获(如连接中断、文件读写错误),提升程序稳定性。
内容的提问来源于stack exchange,提问作者MOK
相关产品推荐
相关产品推荐

