You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

TCP服务端/客户端文件传输异常:需保持连接完成传输

解决TCP文件传输无需关闭连接的问题

你的核心问题在于TCP是流式协议——两端没有内置的“数据结束”标记,所以当前代码里的接收循环会一直阻塞在recv()调用上,直到连接被关闭(手动中断客户端或调用shutdown)才会退出。要保持连接复用,我们需要给传输流程加上明确的“边界标记”,最可靠的方式就是先传输文件的长度信息,让接收方知道要接收多少字节后就停止循环。

下面是修正后的代码实现,同时处理了文件不存在的异常情况:

1. 服务端代码(server.py)修改

首先记得导入struct模块,用来打包/解包文件长度(保证跨平台兼容性):

import struct

# 原类中的相关方法修改
elif msg[:4] == 'file':
    client_command = msg[5:9]
    if client_command == 'GET ':
        file_name = msg[9:]
        try:
            with open(file_name, "rb") as f:
                # 先获取文件总大小
                f.seek(0, 2)
                file_size = f.tell()
                f.seek(0, 0)
                # 把文件大小打包成4字节的网络字节序(大端)发送给客户端
                self.client_socket.sendall(struct.pack('!I', file_size))
                # 分块发送文件内容
                l = f.read(self.BUFF_SIZE)
                while l:
                    self.send_info(l)
                    l = f.read(self.BUFF_SIZE)
        except FileNotFoundError:
            # 文件不存在时发送0作为标记
            self.client_socket.sendall(struct.pack('!I', 0))
    elif client_command == 'SEND':
        file_name = msg[10:]
        # 先接收4字节的文件大小
        size_data = self.recv_info(4)
        if not size_data:
            return
        file_size = struct.unpack('!I', size_data)[0]
        if file_size == 0:
            print(f"客户端发送的文件不存在: {file_name}")
            return
        received_size = 0
        with open(file_name, "wb") as f:
            # 循环接收直到达到文件总大小
            while received_size < file_size:
                # 计算剩余需接收的字节数,避免最后一次接收冗余数据
                remaining = file_size - received_size
                l = self.recv_info(min(remaining, self.BUFF_SIZE))
                if not l:
                    break
                f.write(l)
                received_size += len(l)

# 修改recv_info方法,支持指定接收长度
def recv_info(self, buf_size=None):
    if buf_size is None:
        buf_size = self.BUFF_SIZE
    recv = self.client_socket.recv(buf_size)
    return recv

def send_info(self, msg):
    info = bytes(msg)
    send = self.client_socket.sendall(info)
    return send

2. 客户端代码(client.py)修改

同样导入struct模块,同步修改传输逻辑:

import struct

# 处理file命令的部分修改
answer = input()
elif answer[:4] == 'file':
    s.send(answer.encode('iso-8859-1'))
    command = answer[5:9]
    if command == 'GET ':
        fileName = answer[9:]
        # 先接收4字节的文件大小
        size_data = s.recv(4)
        if not size_data:
            print("连接已断开")
            return
        file_size = struct.unpack('!I', size_data)[0]
        if file_size == 0:
            print("服务端找不到该文件")
            return
        received_size = 0
        with open(fileName, 'wb') as f:
            while received_size < file_size:
                remaining = file_size - received_size
                l = s.recv(min(remaining, 2048))
                if not l:
                    break
                f.write(l)
                received_size += len(l)
        print(f"文件 {fileName} 接收完成")
    elif command == 'SEND':
        fileName = answer[10:]
        try:
            with open(fileName, 'rb') as f:
                # 获取文件大小
                f.seek(0, 2)
                file_size = f.tell()
                f.seek(0, 0)
                # 发送文件大小
                s.sendall(struct.pack('!I', file_size))
                # 分块发送文件内容
                l = f.read(2048)
                while l:
                    s.send(l)
                    l = f.read(2048)
            print(f"文件 {fileName} 发送完成")
        except FileNotFoundError:
            # 文件不存在时发送0标记
            s.sendall(struct.pack('!I', 0))
            print(f"找不到文件: {fileName}")
    # 这里不再需要调用shutdown(SHUT_WR)了!

关键逻辑解释

  1. 文件长度前置传输:发送方先把文件大小打包成固定4字节的二进制数据发送,接收方解析后就明确知道需要接收多少字节,不会无限阻塞在recv()上。
  2. 网络字节序兼容:使用struct.pack('!I', ...)保证大小端格式统一,避免不同操作系统之间的解析错误。
  3. 剩余字节数计算:最后一次接收时只取剩余需要的字节数,防止接收多余数据影响后续命令的处理。
  4. 异常处理:增加了文件不存在的判断,避免一方因等待不存在的数据而阻塞。

后续优化文件名处理时,建议用分隔符(比如file GET|test.txt)替代切片,这样通过split('|')拆分命令和文件名会更可靠,不会因为文件名长度变化导致切片错误。

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

火山引擎 最新活动