Устранение неполадок зависания сокета UDP в коде приложения сервера Python - PullRequest
0 голосов
/ 20 мая 2019

Я пишу серверное приложение UDP, которое служит бэкендом для автомобильных устройств Teltonika FMB630.

Я уже позаботился о специфике и декодировании протокола, проблема, с которой я сталкиваюсь, связана с используемым сокетом UDP.

Мой UDP-сервер должен отправить подтверждение клиентскому устройству после получения сообщения (это протокол), однако, если я отправлю эти ACK, сокет сервера через некоторое время прекратит прием данных.

Объект сокета UDP сервера передается в concurrent.futures.ThreadPoolExecutor, который запускает функцию (send_ack), которая отправляет ACK, однако это не проблема, потому что я попытался вызвать send_ack в главном потоке после получения данных и возникает та же проблема.

Я подозреваю, что проблема в том, что удаленное устройство каким-то образом разрывает соединение, или ISP или MNO не маршрутизируют ответный пакет (это устройство GPRS), а затем метод socket.send(), который используется для отправки подтверждения, как-то останавливает другие операции с сокетами, в частности recvfrom_into, вызываемые в цикле основного потока.

Я написал два сценария, чтобы проиллюстрировать ситуацию:

udp_test_echo.py:

#!/usr/env/bin python

import socket
import concurrent.futures

def send_ack(sock, addr, ack):
    print("Sending ACK to {}".format(addr))
    sock.connect(addr)
    print("connected to {}".format(addr))
    sock.send(ack)
    print("ACK sent to {}".format(addr))

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("127.0.0.1", 1337))
data = bytearray([0] * 10)

executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)

while True:
    print("listening")
    nbytes, address = s.recvfrom_into(data)
    print("Socket Data received {} bytes Address {}".format(nbytes, address))
    print("Data received: ", data, " Echoing back to client")
    executor.submit(send_ack, s, address, data[:nbytes])

udp_test_client.py:

#!/usr/env/bin python

import socket
import time
import random

def get_random_bytes():
    return bytearray([random.randint(0,255) for b in range(10)])

ip = "127.0.0.1"
port = 1337
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect((ip, port))

while True:
    stuff_to_send = get_random_bytes()
    print("Sending stuff", stuff_to_send)
    s.sendall(stuff_to_send)
    print("reply: ", s.recvfrom(10))
    time.sleep(0.1)

Запустив udp_test_echo.py в одном терминале и udp_test_client.py в другом, мы увидим нормальную работу, но если вы нажмете Ctrl + C на тестовом клиенте и повторно запустите его, вы увидите, что сервер не отвечает, пока он не будет перезапущен.

Есть ли способ для тайм-аута определенной операции отправки от определенного вызова к socket.send() методу без влияния на другие вызовы? (Я хочу мой socket.recvfrom_into вызов для блокировки в главном потоке)

Если я settimeout для всего объекта сокета, мне придется иметь дело со многими исключениями при ожидании данных в основном потоке, и мне не нравится полагаться на исключения для правильной работы программы.

1 Ответ

1 голос
/ 20 мая 2019

Виновником был вызов socket.connect() в send_ack, когда он вызывается для объекта сокета сервера, это приводит к тому, что сокет больше не привязывается и не прослушивает порт, указанный при запуске программы.

Вместо этого функция send_ack была изменена на:

def send_ack(sock, addr, ack):
    print("Sending ACK to {}".format(addr))
    sock.sendto(ack, addr)
    print("ACK sent to {}".format(addr))

socket.sendto (данные, адрес) использует существующее соединение вместо создания нового.

...