You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否同时实现Python Socket服务端与客户端?双向文件传输咨询

Python Socket实现双向文件传输(兼具服务端/客户端功能)

完全可以做到。Socket本身就是基于双向通信的,只要设计好通信逻辑,一个程序既可以作为服务端监听连接,也能作为客户端发起连接,建立连接后双方还能主动发起文件传输请求或发送文件。

核心思路

  1. 用线程分离读写操作:单线程下收发会互相阻塞,必须用两个独立线程——一个负责监听并处理对方的消息/文件,另一个负责处理本地的发送请求,避免程序卡住。
  2. 定义统一通信协议: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 03:01:07