发送多个json对象时,线程池挂在json.loads上

时间:2018-08-26 23:32:06

标签: python-3.x threadpoolexecutor

我目前正在使用Python 3.x制作简单的服务器。我正在使用线程池,因为在任何给定时间都可能连接大量客户端。客户端将json对象发送到服务器,然后在其中解析并处理该对象。但是,当我从客户端使用sendall发送多个json对象时,服务器挂起,并且不会处理handle_client函数中的json.loads。

这只是一个示例,因为我正在学习如何在python中使用套接字和线程池。最终,我需要保持持久的连接。人们对它为什么挂断的任何解释表示赞赏。谢谢!

服务器

import sys, socket, threading, json, time, concurrent.futures

HOST = socket.gethostbyname(socket.gethostname())
PORT = 65000
TIMEOUT = 5
MAX_CLIENTS = 5
BUFFER_SIZE = 1024
ENCODING_TYPE = 'utf-8'

clients = []

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))
server.settimeout(TIMEOUT)
server.setblocking(0)
server.listen(MAX_CLIENTS)
print("Server started successfully")

Pool = concurrent.futures.ThreadPoolExecutor(max_workers=5)

def handle_client(client):
    if client not in clients:
        clients.append(client)
        print('%s clients connected.' % len(clients))
    while True:
        data = client.recv(BUFFER_SIZE).decode(ENCODING_TYPE)
        if data:
            jsonObj = json.loads(data)
            client.sendall('Keep up the great work!'.encode(ENCODING_TYPE))
        else:
            clients.remove(client)
            client.close()
        print('%(name)s is a %(occupation)s!' % {'name': jsonObj['name'], 'occupation': jsonObj['occupation']})

while True:
    try:
        client, addr = server.accept()
        client.setblocking(0)
        Pool.submit(handle_client, client)
        Pool.shutdown(wait=False)
    except BlockingIOError:
        pass

server.close()
print('Server shutdown')
sys.exit()

客户

import sys, socket, json, time

HOST = socket.gethostbyname(socket.gethostname())
PORT = 65000
BUFFER_SIZE = 1024
ENCODING_TYPE = 'utf-8'

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((HOST, PORT))

data1 = {
        'name': 'John Doe',
        'age': 23,
        'occupation': 'QA Engineer',
        'employer': 'Samsung'
    }
data2 = {
        'name': 'Jane Roe',
        'age': 32,
        'occupation': 'HR Representative',
        'employer': 'Samsung'
    }
packet1 = json.dumps(data1)
packet2 = json.dumps(data2)

client.sendall(packet1.encode(ENCODING_TYPE))
client.sendall(packet2.encode(ENCODING_TYPE))

while True:
    response = client.recv(BUFFER_SIZE).decode(ENCODING_TYPE)
    print(response)

client.close()

print('Client terminated')

1 个答案:

答案 0 :(得分:0)

这里有很多问题,但我认为您的问题始于这一行:

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

了解此行的含义很重要。您正在创建一个TCP套接字。 TCP是“面向流”的协议。这意味着它没有任何“数据包”的概念。 send()sendall()将字节推入套接字,而recv()将字节拉出套接字。在将字节推入套接字后,TCP不会努力跟踪字节来自哪个“ send()”。

如果ab都是bytes个对象,则sendall(a); sendall(b)等同于sendall(a+b)(存在明显的性能差异,我有意忽略)。同样,recv(1024)的意思是“从套接字中获取下一个1024字节,如果还没有1024字节,则给我少于这个数目”。如果对等方连续执行两次发送,则recv可能会将它们组合在一起,也可能不会合并,具体取决于时间(以及其他因素,例如Nagle's algorithm)。同样,一个非常大的发送可能会拆分为多个recv(即使缓冲区大小足够大)。

通常,在TCP之上设计协议时,必须注意“文书工作”的某些方面,以确保一切顺利进行:

  • 大多数协议都具有“消息”的概念。例如,HTTP具有requests and responses,而SMTP和FTP均具有命令。在您的协议中,客户端正在发送JSON对象,而服务器正在发送固定的字符串;对您来说,“消息”可以是一个JSON对象,也可以是该固定字符串的一个实例。
  • 通常,每个对等方都在发送和接收消息之间交替,直到关闭连接为止,尽管可能会有更复杂的安排。
    • 如果任何一个对等体要连续接收多条消息,则它必须能够分辨出一条消息的结束位置,下一条消息的开始位置,最好无需进行很多复杂的字符串操作。这就是为什么许多协议都倾向于替代的原因。
  • 在接收时,一个对等方必须确保在发送任何内容之前已接收到整个消息(否则,另一个对等方将发送更多数据,现在您回到“查找消息结束的地方”的问题)。这可能需要将多次调用recv()的结果串联起来。对等方必须准备好妥善处理部分消息,例如:
    • 有可能(如果不太可能)拆分多字节UTF-8字符。这样的字节序列将无法用.decode() UnicodeError来结束。
    • 可能会拆分JSON对象。这样的字符序列将在json.loads()ValueError处失败。
  • 对于非阻塞套接字,仅在recv失败之前调用BlockingIOError是不够的,因为由于性能原因,以后的数据包可能已被延迟或故意保留(小数据包相对昂贵)。接收方必须实际检查到目前为止收到的数据,并检查它是否是完整的消息。

最后,这是我对您的服务器代码的其他一些看法:

  • 我不确定handle_client()在做什么。它似乎陷入了一个无限循环,在该循环中,它反复.close()客户端的套接字,然后尝试再次从该套接字中恢复。那是非法的。
  • 类似地,您的顶级代码反复.shutdown()进入池,然后尝试提交更多任务,这也是非法的。
  • 如果使用非阻塞I / O,则可能还需要调用select()poll()以便检测客户端何时可以读取。就目前的代码而言,您将其放弃。