Что такое правильный бесконечный цикл сервера сокетов в Python - PullRequest
0 голосов
/ 19 февраля 2019

Я новичок в Python, и моя первая задача - создать небольшую серверную программу, которая будет перенаправлять события из сетевого модуля в остальные API.Общая структура моего кода, кажется, работает, но у меня есть одна проблема.После получения первой посылки ничего не происходит.Что-то не так с моим циклом, что новые пакеты (от того же клиента) не принимаются?

Пакеты выглядят примерно так: EVNTTAG 20190219164001132% 0C% 3D% E2% 80h% 90% 00% 00%00% 01% CBU% FB% DF ... не то чтобы это имело значение, но я делюсь только для ясности.

Мой код (я пропустил нерелевантную инициацию отдыха и т. Д., Но основной цикл - этополный код):

# Configure TAGP listener
ipaddress = ([l for l in ([ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")][:1], [[(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]]) if l][0][0])
server_name = ipaddress
server_address = (server_name, TAGPListenerPort)
print ('starting TAGP listener on %s port %s' % server_address)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(server_address)
sock.listen(1)

sensor_data = {'tag': 0}

# Listen for TAGP data and forward events to ThingsBoard
try:
    while True:
        data = ""
        connection, client_address = sock.accept()
        data = str(connection.recv(1024))
        if data.find("EVNTTAG") != -1:
            timestamp = ((data.split())[1])[:17]
            tag = ((data.split())[1])[17:]
            sensor_data['tag'] = tag
            client.publish('v1/devices/me/telemetry', json.dumps(sensor_data), 1)
            print (data)
except KeyboardInterrupt:
    # Close socket server (TAGP)
    connection.shutdown(1)
    connection.close()
    # Close client to ThingsBoard
    client.loop_stop()
    client.disconnect()

1 Ответ

0 голосов
/ 19 февраля 2019

Есть несколько проблем с вашим кодом:

Прежде всего вам нужен цикл над тем, что отправляет клиент.Итак, вы сначала connection, client_address = sock.accept(), и теперь у вас есть клиент.Но на следующей итерации цикла вы снова .accept() перезаписываете свой старый connection новым клиентом.Если нового клиента нет, он просто ждет вечно.И это то, что вы наблюдаете.

Так что это можно исправить следующим образом:

while True:
    conn, addr = sock.accept()
    while True:
        data = conn.recv(1024)

, но у этого кода есть другая проблема: новый клиент не может подключиться, пока старый не отключится (ну, вв тот момент, когда он просто зацикливается бесконечно, независимо от того, жив клиент или нет, мы разберемся с этим позже).Чтобы преодолеть это, вы можете использовать потоки (или асинхронное программирование) и обрабатывать каждого клиента независимо .Например:

from threading import Thread

def client_handler(conn):
    while True:
        data = conn.recv(1024)

while True:
    conn, addr = sock.accept()
    t = Thread(target=client_handler, args=(conn,))
    t.start()

Асинхронное программирование сложнее, и я не буду его здесь рассматривать.Просто имейте в виду, что асинхронность по сравнению с потоками имеет несколько преимуществ (вы можете гуглить их).

Теперь у каждого клиента есть свой собственный поток, а основной поток беспокоится только о принятии соединений.Вещи происходят одновременно.Пока все хорошо.

Давайте сосредоточимся на функции client_handler.То, что вы не понимаете, это как работают сокеты.Это:

data = conn.recv(1024)

не не читает 1024 байта из буфера.На самом деле он читает от до 1024 байта с 0, также возможно.Даже если вы отправите 1024 байта, он все равно может прочитать, скажем, 3. А когда вы получите буфер длины 0, это означает, что клиент отключился.Итак, в первую очередь вам нужно это:

def client_handler(conn):
    while True:
        data = conn.recv(1024)
        if not data:
            break

Теперь начинается настоящее веселье.Даже если data не пусто , это может быть произвольной длины от 1 до 1024. Ваши данные могут быть разделены на части и могут потребовать нескольких вызовов .recv.И нет, Вы ничего не можете с этим поделать .Чанкинг может происходить из-за некоторых других прокси-серверов или маршрутизаторов, из-за задержки в сети, космического излучения или чего-то еще.Вы должны быть готовы к этому.

Так что для правильной работы с этим вам нужно надлежащий протокол кадрирования.Например, вы должны как-то узнать, насколько велик входящий пакет (чтобы вы могли ответить на вопрос «прочитал ли я все, что мне нужно?»).Один из способов сделать это - поставить перед каждым кадром, скажем, 2 байта, которые объединяются в общую длину кадра.Код может выглядеть так:

def client_handler(conn):
    while True:
        chunk = conn.recv(1)  # read first byte
        if not chunk:
            break
        size = ord(chunk)
        chunk = conn.recv(1)  # read second byte
        if not chunk:
            break
        size += (ord(chunk) << 8)

Теперь вы знаете, что входящий буфер будет иметь длину size.При этом вы можете читать все циклы:

def handle_frame(conn, frame):
    if frame.find("EVNTTAG") != -1:
        pass  # do your stuff here now

def client_handler(conn):
    while True:
        chunk = conn.recv(1)
        if not chunk:
            break
        size = ord(chunk)
        chunk = conn.recv(1)
        if not chunk:
            break
        size += (ord(chunk) << 8)
        # recv until everything is read
        frame = b''
        while size > 0:
            chunk = conn.recv(size)
            if not chunk:
                return
            frame += chunk
            size -= len(chunk)
        handle_frame(conn, frame)

ВАЖНО: это всего лишь пример обработки протокола, в котором префикс каждого кадра соответствует его длине.Обратите внимание, что клиент также должен быть настроен .Вы должны либо определить такой протокол, либо, если у вас есть данный, вы должны прочитать спецификацию и попытаться понять, как работает кадрирование.Например, это делается совсем по-другому с HTTP.В HTTP вы читаете, пока не встретите \r\n\r\n, который сигнализирует об окончании заголовков.Затем вы проверяете заголовки Content-Length или Transfer-Encoding (не говоря уже о таких жестких вещах, как переключение протокола), чтобы определить следующее действие.Это становится довольно сложным, хотя.Я просто хочу, чтобы вы знали, что есть другие варианты.Тем не менее кадрирование необходимо.

Также сетевое программирование сложно.Я не собираюсь погружаться в такие вещи, как безопасность (например, от DDOS) и производительность.Приведенный выше код следует рассматривать как крайнее упрощение, а не готовый продукт.Я советую использовать какой-нибудь существующий софт.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...