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

Python TCP文件传输程序字节解码异常问题求助

TCP文件传输乱码问题解析与改进方案

问题描述

正在重新学习Python网络编程,实现的TCP文件传输客户端与服务端功能基本正常,但接收的文本文件存在乱码:无法正确显示换行、制表符,且包含不可读符号。此前尝试用codecs模块解码时,出现「预期字节对象却收到字符串」的错误;当前代码无报错,但乱码问题始终无法解决,需要问题解析与改进指导。

原代码问题解析

1. 核心缺陷:TCP流边界处理不当(粘包问题)

TCP是面向流的协议,发送的数据无天然边界。原代码中:

  • 服务端发送响应b"Sent\n"后直接发送文件大小,客户端用sock.recv(1024)接收响应时,可能同时读取到部分文件大小的字节,导致响应判断错误或文件大小解析异常,最终接收的文件数据不完整/冗余,解码后出现乱码。
  • 服务端发送文件大小仅用send,未确保数据完整发送;客户端接收文件大小也未做边界校验,易出现解析错误。

2. 编码解码逻辑不严谨

  • 客户端使用codecs.decode(file_data)未指定编码格式,默认编码可能与文件实际编码(UTF-8)不匹配,导致解码乱码。
  • 原代码中注释的file_data.decode('utf-8')是正确方向,但因前面的文件数据接收不完整,导致解码失败。

3. 基础依赖缺失

服务端与客户端代码均未导入socket模块,实际运行会直接报错,属于低级疏漏。

4. 换行符处理时机错误

客户端的clean_text函数逻辑本身无错,但需在完整接收并正确解码文件数据后执行,否则无效。


改进后的代码

服务端代码

import sys
import argparse
import os
import socket  # 补充缺失的socket导入

host = 'localhost'
data_buff = 2048

def TCPserver1(port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_address = (host, port)
    print('Server listening...')
    sock.bind(server_address)
    sock.listen(5)

    def check_file_existence(file_name):
        current_directory = os.getcwd()
        file_path = os.path.join(current_directory, file_name)
        return file_path if os.path.exists(file_path) else None

    while True:
        print("Waiting for connection...")
        client, address = sock.accept()
        print(f"Got connection from {address}")
        
        # 读取文件名(按换行符分割,避免粘包)
        file_name = b""
        while b"\n" not in file_name:
            chunk = client.recv(data_buff)
            if not chunk:
                break
            file_name += chunk
        file_name = file_name.decode('utf-8').strip()
        print(f"Server received the file name '{file_name}'")

        file_path = check_file_existence(file_name)

        if file_path:
            # 用sendall确保响应完整发送
            client.sendall(b"Sent\n")
            file_size = os.path.getsize(file_path)
            # 将文件大小转为固定长度的字节(比如8字节,兼容大文件)
            client.sendall(file_size.to_bytes(8, byteorder='big'))
            with open(file_path, 'rb') as file:
                file_data = file.read(data_buff)
                while file_data:
                    client.sendall(file_data)
                    file_data = file.read(data_buff)
            print("Done sending.")
        else:
            client.sendall(b"File not found\n")

        client.close()
        print("Connection is closed")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='File Transfer Server')
    parser.add_argument('--port', action="store", dest="port", type=int, required=True)
    given_args = parser.parse_args()
    port = given_args.port
    TCPserver1(port)

客户端代码

import sys
import argparse
import socket  # 补充缺失的socket导入

host = 'localhost'

def clean_text(data):
    # 统一换行符格式
    cleaned_data = data.replace('\r\n', '\n').replace('\r', '\n')
    return cleaned_data

def TCPClient1(port, file_name):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_address = (host, port)
    print("Connecting to %s port %s" % server_address)
    sock.connect(server_address)
    try:
        # 发送文件名时添加换行符,便于服务端识别边界
        sock.sendall(f"{file_name}\n".encode('utf-8'))
        print("receiving data...")
        
        # 读取服务端响应(按换行符分割)
        response = b""
        while b"\n" not in response:
            chunk = sock.recv(1024)
            if not chunk:
                break
            response += chunk
        response = response.strip()
        
        if response == b"Sent":
            # 读取固定长度的文件大小(8字节)
            file_size_bytes = sock.recv(8)
            file_size = int.from_bytes(file_size_bytes, byteorder='big')
            file_data = b""
            bytes_received = 0
            
            while bytes_received < file_size:
                # 计算剩余需要接收的字节数,避免多收
                recv_size = min(1024, file_size - bytes_received)
                data = sock.recv(recv_size)
                if not data:
                    break
                bytes_received += len(data)
                file_data += data
            
            # 明确指定UTF-8解码
            decoded_data = file_data.decode('utf-8')
            cleaned_data = clean_text(decoded_data)
            print(cleaned_data)
            print("Successfully got the file")
        else:
            print("File not found on the server")
    except socket.error as e:
        print("Socket error: %s" % str(e))
    except Exception as e:
        print("Other exception: %s" % str(e))
    finally:
        print("Connection is closed")
        sock.close()

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='File Transfer Client')
    parser.add_argument('--port', action="store", dest="port", type=int, required=True)
    parser.add_argument('--file', action="store", dest="file", type=str, required=True)
    given_args = parser.parse_args()
    port = given_args.port
    file_name = given_args.file
    TCPClient1(port, file_name)

关键改进说明

  1. 解决粘包问题:
    • 文件名与响应均通过换行符作为边界,确保完整读取;
    • 文件大小采用固定8字节的二进制格式传输,避免字符串解析的边界问题,同时兼容大文件(最大支持2^64字节)。
  2. 编码解码标准化:客户端明确使用utf-8解码,匹配文件编码。
  3. 数据传输可靠性:全部使用sendall替代send,确保数据完整发送;接收时计算剩余字节数,避免多收或漏收。
  4. 修复基础依赖:补充socket模块导入,确保代码可正常运行。

内容的提问来源于stack exchange,提问作者DevSteph

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 14:45:54