Próbuję wykonać this Matasano crypto challenge, który polega na wykonaniu ataku synchronizującego na serwer ze sztucznie spowolnioną funkcją porównywania ciągów. Mówi się, aby użyć "wybranej przez siebie struktury sieci", ale nie miałem ochoty instalować ram internetowych, więc zdecydowałem się użyć modułu HTTPServer class wbudowanego w moduł http.server
.Tajemnicze wyjątki podczas robienia wielu równoczesnych żądań od adresu urllib.request do HTTPServer
Wymyśliłem coś, co działało, ale działo się bardzo wolno, więc starałem się przyspieszyć za pomocą (słabo udokumentowanej) puli wątków wbudowanej w multiprocessing.dummy
. Było znacznie szybciej, ale zauważyłem coś dziwnego: jeśli wykonam 8 lub mniej żądań jednocześnie, to działa dobrze. Jeśli mam więcej, to działa przez chwilę i daje mi błędy w pozornie przypadkowych czasach. Błędy wydają się być niespójne i nie zawsze takie same, ale zwykle mają w sobie Connection refused, invalid argument
, OSError: [Errno 22] Invalid argument
, urllib.error.URLError: <urlopen error [Errno 22] Invalid argument>
, BrokenPipeError: [Errno 32] Broken pipe
lub urllib.error.URLError: <urlopen error [Errno 61] Connection refused>
.
Czy istnieje ograniczenie liczby połączeń obsługiwanych przez serwer? Nie sądzę, że liczba wątków jako takich jest problemem, ponieważ napisałem prostą funkcję, która spowodowała spowolnienie porównywania ciągów bez uruchamiania serwera WWW i wywołała go z 500 równoczesnymi wątkami i działała dobrze. Nie sądzę, że problem polega na zwykłym wysyłaniu żądań z wielu wątków, ponieważ zrobiłem roboty, które używały ponad 100 wątków (wszystkie zgłaszały jednoczesne żądania do tej samej witryny) i działały dobrze. Wygląda na to, że być może HTTPServer nie jest przeznaczony do hostowania witryn produkcyjnych, które generują duży ruch, ale jestem zaskoczony, że tak łatwo jest go zawiesić.
Próbowałem stopniowo usuwać rzeczy z mojego kodu, który wyglądał niezwiązany z problemem, jak zwykle robię, kiedy diagnozuję takie tajemnicze błędy, ale to nie było bardzo pomocne w tym przypadku. Wyglądało na to, że usuwając pozornie niepowiązany kod, liczba połączeń, które serwer mógł obsłużyć, stopniowo wzrastała, ale nie było wyraźnej przyczyny awarii.
Czy ktoś wie, jak zwiększyć liczbę żądań, które mogę wykonać naraz, a przynajmniej dlaczego tak się dzieje?
Mój kod jest skomplikowany, ale wpadłem na to prosty program, który pokazuje problem:
#!/usr/bin/env python3
import os
import random
from http.server import BaseHTTPRequestHandler, HTTPServer
from multiprocessing.dummy import Pool as ThreadPool
from socketserver import ForkingMixIn, ThreadingMixIn
from threading import Thread
from time import sleep
from urllib.error import HTTPError
from urllib.request import urlopen
class FancyHTTPServer(ThreadingMixIn, HTTPServer):
pass
class MyRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
sleep(random.uniform(0, 2))
self.send_response(200)
self.end_headers()
self.wfile.write(b"foo")
def log_request(self, code=None, size=None):
pass
def request_is_ok(number):
try:
urlopen("http://localhost:31415/test" + str(number))
except HTTPError:
return False
else:
return True
server = FancyHTTPServer(("localhost", 31415), MyRequestHandler)
try:
Thread(target=server.serve_forever).start()
with ThreadPool(200) as pool:
for i in range(10):
numbers = [random.randint(0, 99999) for j in range(20000)]
for j, result in enumerate(pool.imap(request_is_ok, numbers)):
if j % 20 == 0:
print(i, j)
finally:
server.shutdown()
server.server_close()
print("done testing server")
Z jakiegoś powodu, program powyżej działa poprawnie, chyba że ma ponad 100 wątków lub tak, ale moja prawdziwy kod wyzwania może obsłużyć tylko 8 wątków. Jeśli uruchomię go z 9, zwykle otrzymuję błędy połączenia, a przy 10 zawsze otrzymuję błędy połączenia. Próbowałem używać concurrent.futures.ThreadPoolExecutor
, i multiprocessing.pool
zamiast z multiprocessing.dummy.pool
i żadne z nich nie pomogło. Próbowałem używać zwykłego obiektu HTTPServer
(bez ThreadingMixIn
) i to tylko sprawiało, że rzeczy działały bardzo wolno i nie rozwiązały problemu. Próbowałem użyć ForkingMixIn
i to też nie naprawiło.
Co mam z tym zrobić? Używam Python 3.5.1 na MacBooka Pro z końca 2013 roku z systemem OS X 10.11.3.
EDIT: Próbowałem kilka rzeczy, w tym uruchomienie serwera w procesie zamiast wątku, jako prosty HTTPServer
, z ForkingMixIn
iz ThreadingMixIn
. Żadne z nich nie pomogło.
EDIT: Ten problem jest dziwniejszy niż myślałem.Próbowałem utworzyć jeden skrypt z serwerem, a drugi z wieloma wątkami wysyłającymi żądania i uruchamiając je w różnych zakładkach w moim terminalu. Proces z serwerem działał dobrze, ale ten, który zgłasza żądania, zawiesił się. Wyjątkami były mieszanką ConnectionResetError: [Errno 54] Connection reset by peer
, urllib.error.URLError: <urlopen error [Errno 54] Connection reset by peer>
, OSError: [Errno 41] Protocol wrong type for socket
, urllib.error.URLError: <urlopen error [Errno 41] Protocol wrong type for socket>
, urllib.error.URLError: <urlopen error [Errno 22] Invalid argument>
.
Próbowałem go z fałszywym serwerem, jak ten powyżej, a jeśli ograniczyłem liczbę współbieżnych żądań do 5 lub mniej, to działało dobrze, ale z 6 żądaniami, proces klienta się zawiesił. Wystąpiły błędy na serwerze, ale nadal działało. Klient zawiesił się niezależnie od tego, czy używałem wątków lub procesów do wysyłania żądań. Następnie spróbowałem umieścić spowolnioną funkcję na serwerze i było w stanie obsłużyć 60 równoczesnych żądań, ale rozbił się z 70. Wydaje się, że może to być sprzeczne z dowodami, że problem dotyczy serwera.
EDIT: Próbowałem większość rzeczy, opisanych za pomocą requests
zamiast urllib.request
i pobiegł do podobnych problemów.
EDYCJA: Używam teraz systemu OS X 10.11.4 i uruchamiam te same problemy.
Czy zapewniasz zamknięcie nieużywanych połączeń z klientami? –
@Cory Shay, Próbowałem robić 'x = urlopen (cokolwiek)' następnie 'x.close()', a to nie pomagało. –
Muszę przyznać, że powód, który podałem, niekoniecznie jest przyczyną tego problemu. Mogą istnieć inne. Jednak kilka pytań, które mogą pomóc w zbadaniu tego problemu, brzmi: "co stanie się, jeśli wydasz' ulimit -r $ ((32 * 1024))? " i "co to jest wynik z' netstat -anp | grep SERVERPROCESSNAME'? " –