Проблемы с неблокирующими серверными сокетами при использовании опроса в MicroPython

Я пытаюсь собрать простой термометр, который показывает температуру на OLED-дисплее, а также через HTTP-запросы на ESP8266 с использованием MicroPython.

Поллер Объект был использован для предотвращения WebSocket от блокирования петли (поэтому измерения и OLED - дисплей могут быть обновлены).

      #CREATE SOCKET
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSocket.bind(('', 80))
serverSocket.listen(5)

#REGISTER SOCKET TO THE POLLER
pollerObject = select.poll()
pollerObject.register(serverSocket, select.POLLIN)

#PERFORM FIRST MEASUREMENT AT STARTUP
last_meas_time = startup_time
sensor_readings = read_sensor()
print(sensor_readings)
display.display_measurement(str(temp),str(hum))

#LOOP FOREVER
while True:
    
    #ONE MEASUREMENT UPDATE EVERY 30s
    if(time.time() - last_meas_time >= 30):
        sensor_readings = read_sensor()
        print(sensor_readings)
        display.display_measurement(str(temp),str(hum))
        last_meas_time = time.time()
    
    #WAIT UP TO 10s FOR INCOMING CONNECTIONS
    fdVsEvent = pollerObject.poll(10000)
    
    for descriptor, Event in fdVsEvent:
        print()
        print("Got an incoming connection request")
        print("Start processing")
        # Do accept() on server socket or read from a client socket
        conn, addr = serverSocket.accept()
        print('Got a connection from %s' % str(addr))
        request = conn.recv(1024)
        print('Content = %s' % str(request))
        response = web_page()
        conn.send('HTTP/1.1 200 OK\n')
        conn.send('Content-Type: text/html\n')
        conn.send('Connection: close\n\n')
        conn.sendall(response)
        conn.close()

Кажется, какое-то время он работает нормально, но я обнаружил две проблемы, и я был бы признателен за вашу помощь:

  1. Несмотря на то, что я подключаюсь к нему только один раз, 2 или 3 запроса отображаются как полученные в терминале оболочки, как вы можете видеть ниже. Почему это происходит и как я могу с этим справиться? Может ли быть так, что браузер достаточно долго ждал, чтобы отправить второй или третий запрос?
    MPY: мягкая перезагрузка
    Соединение успешно
    (192.168.1.74, 255.255.255.0, 192.168.1.1, 192.168.1.1)
    b'29,0,24,0 '
    
    Получен входящий запрос на подключение
    Начать обработку
    Получил соединение от ('192.168.1.64', 58581)
    Content = b'GET / HTTP / 1.1 \ r \ nHost: 192.168.1.74 \ r \ nСоединение: keep-alive \ r \ nCache-Control: max-age= 0 \ r \ nDNT: 1 \ r \ nUpgrade-Insecure- Запросы: 1 \ r \ nАгент пользователя: Mozilla / 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit / 537.36 (KHTML, как Gecko) Chrome / 87.0.4280.88 Safari / 537.36 Edg / 87.0.664.66 \ r \ nПринять: текст /html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nAccept-Кодирование: gzip, deflate \ r \ nAccept-Language: pt-BR,pt;q= 0,9,en;q= 0,8,en-GB;q= 0,7,en-US;q= 0,6, sv;q= 0,5 \ r \ n \ r \ n '
    
    Получен входящий запрос на подключение
    Начать обработку
    Получил соединение от ('192.168.1.64', 58582)
    Content = b'GET /favicon.ico HTTP / 1.1 \ r \ nHost: 192.168.1.74 \ r \ nСоединение: keep-alive \ r \ nПрагма: no-cache \ r \ nCache-Control: no-cache \ r \ nПользователь -Агент: Mozilla / 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit / 537.36 (KHTML, например, Gecko) Chrome / 87.0.4280.88 Safari / 537.36 Edg / 87.0.664.66 \ r \ nDNT: 1 \ r \ nAccept: image / webp,image / apng,image / *,* / *;q=0.8 \ r \ nReferer: http://192.168.1.74/\r\nAccept-Encoding: gzip, deflate \ r \ nAccept-Language: pt-BR , pt;q= 0,9,en;q= 0,8,en-GB;q= 0,7,en-US;q= 0,6, sv;q= 0,5 \ r \ n \ r \ n '
  1. После некоторого времени работы я больше не смогу подключиться к нему, так как он не будет отвечать. Что-то явно не так с моим подходом? Вот что я получил из консоли:
    Получен входящий запрос на подключение
    Начать обработку
    Получил соединение от ('192.168.1.64', 59158)
    Content = b'GET / HTTP / 1.1 \ r \ nHost: 192.168.1.74 \ r \ nСоединение: keep-alive \ r \ nCache-Control: max-age= 0 \ r \ nDNT: 1 \ r \ nUpgrade-Insecure- Запросы: 1 \ r \ nАгент пользователя: Mozilla / 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit / 537.36 (KHTML, как Gecko) Chrome / 87.0.4280.88 Safari / 537.36 Edg / 87.0.664.66 \ r \ nПринять: текст /html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nAccept-Кодирование: gzip, deflate \ r \ nAccept-Language: pt-BR,pt;q= 0,9,en;q= 0,8,en-GB;q= 0,7,en-US;q= 0,6, sv;q= 0,5 \ r \ n \ r \ n '
    
    Получен входящий запрос на подключение
    Начать обработку
    Получил соединение от ('192.168.1.64', 59157)
    Content = b'GET /favicon.ico HTTP / 1.1 \ r \ nHost: 192.168.1.74 \ r \ nСоединение: keep-alive \ r \ nПрагма: no-cache \ r \ nCache-Control: no-cache \ r \ nПользователь -Агент: Mozilla / 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit / 537.36 (KHTML, например, Gecko) Chrome / 87.0.4280.88 Safari / 537.36 Edg / 87.0.664.66 \ r \ nDNT: 1 \ r \ nAccept: image / webp,image / apng,image / *,* / *;q=0.8 \ r \ nReferer: http://192.168.1.74/\r\nAccept-Encoding: gzip, deflate \ r \ nAccept-Language: pt-BR , pt;q= 0,9,en;q= 0,8,en-GB;q= 0,7,en-US;q= 0,6, sv;q= 0,5 \ r \ n \ r \ n '
    
    Получен входящий запрос на подключение
    Начать обработку
    Получил соединение от ('192.168.1.64', 59160)
    Content = b''
    Отслеживание (последний вызов последний):
      Файл "main.py", строка 104, в 
    OSError: [Errno 104] ECONNRESET
    MicroPython v1.13 от 11.09.2020; Модуль ESP с ESP8266
    Для получения дополнительной информации введите «help()».
    >>> 

Строка 104 соответствует:

              conn.sendall(response)

Спасибо!

1 ответ

Несмотря на то, что я подключаюсь к нему только один раз, 2 или 3 запроса отображаются как полученные в терминале оболочки, как вы можете видеть ниже. Почему это происходит и как я могу с этим справиться? Может ли быть так, что браузер достаточно долго ждал, чтобы отправить второй или третий запрос?

Это зависит от того, как браузер подключается к вашему серверу. Может быть несколько запросов, которые ищет браузер, или у браузера есть значение тайм-аута для сокета, подключающегося к вашему серверу. У меня нет никаких веб-знаний, но похоже, что два запроса разной информации. Как эта информация обрабатывается, следует передать web_page(). Похоже, вы отправляете веб-страницу целиком, а не конкретный контент, который она ищет.

После некоторого времени работы я больше не смогу подключиться к нему, так как он не будет отвечать. Что-то явно не так с моим подходом?

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

Вы на правильном пути, используя select.poll(). На первый взгляд кажется, что регистрация вашего с помощью (select.poll) будет обрабатывать будущие подключения. Это не то, что происходит. Вы регистрируете только один сокет. В severSocket получает select.POLLINсобытие для входящего соединения из браузера. Вам нужен способ добавления / регистрации новых сокетов, созданных serverSocket к pollerObject так что вы можете обслуживать другие розетки.

Теперь лучший пример того, что вы пытаетесь сделать в micropython, - это сделать что-то похожее на пример селектора в Python 3 Selectors.

      import selectors
import socket

sel = selectors.DefaultSelector()

def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    print('accepted', conn, 'from', addr)
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)

def read(conn, mask):
    data = conn.recv(1000)  # Should be ready
    if data:
        print('echoing', repr(data), 'to', conn)
        conn.send(data)  # Hope it won't block
    else:
        print('closing', conn)
        sel.unregister(conn)
        conn.close()

sock = socket.socket()
sock.bind(('localhost', 1234))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)

while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)

Как правило, вам не нужно беспокоиться о заполнении буфера передачи сокета с помощью socket.send(), но вы должны справиться с этим. А пока я бы поставил несколько отладочных отпечатков до и после socket.sendall()так как это будет блокировать / повторять попытки до тех пор, пока все данные не будут отправлены. В случае, если не все данные были отправлены, вам нужно будет зарегистрировать сокет для события готовности к записи и передать оставшиеся данные, которые необходимо отправить. Это немного сложнее.

      Got an incoming connection request
Start processing
Got a connection from ('192.168.1.64', 59160)
Content = b''
Traceback (most recent call last):
  File "main.py", line 104, in 
OSError: [Errno 104] ECONNRESET
MicroPython v1.13 on 2020-09-11; ESP module with ESP8266
Type "help()" for more information.
>>> 

Проблема, с которой вы столкнулись выше, заключается в том, что у вас, вероятно, истекло время подключения к сокету. TCP сообщает вам, что срок действия соединения истек. Вы должны справиться с этим с помощью предложения try except else.

Другие вопросы по тегам