当前位置: 代码迷 >> python >> TCP服务器,多个客户端发送文件,在服务器端解码UTF-8错误
  详细解决方案

TCP服务器,多个客户端发送文件,在服务器端解码UTF-8错误

热度:89   发布时间:2023-06-13 16:45:34.0

我正在做的是首先发送文件名和文件大小(一个编码的(utf-8)json字符串),因此服务器可以提前知道文件的大小,以便他知道何时所有数据到达。

当只有一个客户端发送文件时,它工作得很好,但是当两个或多个客户端同时发送文件时,服务器经常崩溃,而“ utf-8”编解码器无法解码 (有时它可以正常工作,这使它更加令人困惑) 位置36的字节0xff: run()函数的第一行中的无效起始字节

我不知道为什么会这样,因为每个客户都有自己的流程,因此彼此之间不应有任何冲突。

客户:

import socket, json

f = 'img.jpg'
f_bin = open(f, 'rb').read()
info = {'name': f, 'size': len(f_bin)}
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect(('89.1.59.435', 9005))
    s.send(json.dumps(info).encode())
    total_sent = 0
    while total_sent < info['size']:
        try:
            sent = s.send(f_bin[total_sent:])
        except Exception as err:
            break
        total_sent += sent

服务器:

import socket, threading, json

def get_file(conn, info):
    remaining = info['size'] # file size, our trigger to know that all packages arrived
    file_bin = b''
    progress = None
    while remaining > 0:
        try:
            package = conn.recv(1024)
        except Exception as err:
            return None
        file_bin += package
        remaining -= len(package)
    return file_bin

def run(conn):
    info = json.loads(conn.recv(1024).decode()) # 'utf-8' codec can't decode byte 0xff in position 36: invalid start byte
    file_bin = get_file(conn, info)
    if file_bin is not None:
        dest = 'files/{}'.format(info['name'])
        with open(dest, 'wb') as f:
            f.write(file_bin)
        print('success on receiving and saving {} for {}'.format(info['name'], conn.getpeername()))
    conn.close()

host, port = ('', 9005)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((host, port))
    sock.listen(5)
    while True:
        conn, addr = sock.accept()
        print('conn', addr)
        threading.Thread(target=run, args=(conn,)).start()

我删除了印刷品只是为了张贴相关部分并说明问题

TCP是一种流协议,因此不能保证recv准确接收到JSON标头。 它可能只包含JSON的一部分,或者可能包含一些更高版本的二进制数据。 在您的情况下,在激活多个连接的情况下,recv可能会延迟足够长的时间来获取额外的数据。

您需要某种方式来了解标题的大小。 一种常见的方法是选择一个标记结束的字符。 JSON字符串不包含NUL('\\ x00`),因此效果很好。 在标头后立即写一个NUL,服务器可以扫描该null以再次将其拆分。 我稍微修改了代码,使其可以在我的机器上运行,并在此示例中添加了一些基本的错误处理。

client.py

import socket, json, sys

f = 'img.jpg'
f_bin = open(f, 'rb').read()
info = {'name': f, 'size': len(f_bin)}
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect(('localhost', 9005))
    s.send(json.dumps(info).encode())
    s.send(b'\x00')
    total_sent = 0
    while total_sent < info['size']:
        try:
            sent = s.send(f_bin[total_sent:])
        except Exception as err:
            break
        total_sent += sent

server.py

import socket, threading, json, io, struct

def reset_conn(conn):
    """Reset tcp connection"""
    # linger zero
    conn.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
        struct.pack('ii', 1, 0)) # on/off, linger-timeout
    conn.shutdown(socket.SHUT_RDWR)
    conn.close()

def get_file(conn, info):
    remaining = info['size'] # file size, our trigger to know that all packages arrived
    file_bin = b''
    progress = None
    while remaining > 0:
        try:
            package = conn.recv(1024)
            if not package:
                print("Unexpected end of file")
                reset_conn(conn)
                return None
        except Exception as err:
            return None
        file_bin += package
        remaining -= len(package)
    return file_bin

def run(conn):
    # get header
    buf = io.BytesIO()
    while True:
        c = conn.recv(1)
        if not len(c):
            print("Error, no header received")
            reset_conn(conn)
            return
        if c == b'\x00':
            break
        buf.write(c)
    info = json.loads(buf.getvalue().decode())
    del buf
    file_bin = get_file(conn, info)
    if file_bin is not None:
        dest = 'files/{}'.format(info['name'])
        with open(dest, 'wb') as f:
            f.write(file_bin)
        print('success on receiving and saving {} for {}'.format(info['name'], conn.getpeername()))
    conn.close()

host, port = ('localhost', 9005)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((host, port))
    sock.listen(5)
    while True:
        conn, addr = sock.accept()
        print('conn', addr)
        threading.Thread(target=run, args=(conn,)).start()
  相关解决方案