Проблемы с неблокирующими серверными сокетами при использовании опроса в 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: soft reboot
    Connection successful
    ('192.168.1.74', '255.255.255.0', '192.168.1.1', '192.168.1.1')
    b'29.0,24.0'
    
    Got an incoming connection request
    Start processing
    Got a connection from ('192.168.1.64', 58581)
    Content = b'GET / HTTP/1.1\r\nHost: 192.168.1.74\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nDNT: 1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66\r\nAccept: text/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-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'
    
    Got an incoming connection request
    Start processing
    Got a connection from ('192.168.1.64', 58582)
    Content = b'GET /favicon.ico HTTP/1.1\r\nHost: 192.168.1.74\r\nConnection: keep-alive\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like 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. После долгой работы я больше не смогу к нему подключиться, так как он не отвечает. Есть ли что-то явно неправильное в моем подходе? Вот что я получил из консоли:
    Got an incoming connection request
    Start processing
    Got a connection from ('192.168.1.64', 59158)
    Content = b'GET / HTTP/1.1\r\nHost: 192.168.1.74\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nDNT: 1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66\r\nAccept: text/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-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'
    
    Got an incoming connection request
    Start processing
    Got a connection from ('192.168.1.64', 59157)
    Content = b'GET /favicon.ico HTTP/1.1\r\nHost: 192.168.1.74\r\nConnection: keep-alive\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like 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'
    
    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.
    >>> 

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

        conn.sendall(response)

Спасибо!


person Felipe    schedule 31.12.2020    source источник


Ответы (1)


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

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

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

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

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

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

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, кроме else.

person Phlipi    schedule 08.04.2021