multithread webserver probleme

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Lambda
User
Beiträge: 25
Registriert: Freitag 27. April 2007, 17:11

Mittwoch 23. Mai 2007, 16:16

hi,

zuerst der code:

Code: Alles auswählen

# multithreading httpd - test
try:
	import psyco
	psyco.full()
except ImportError:
	print 'Psyco not installed, the program will just run slower'
import SocketServer, socket, BaseHTTPServer, os

class MyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
	def __del__(self):
		print 'object deleted.'

	def do_GET(self):
		self.send_text('test')

	def send_text(self, text, mimetype='text/html', code=200):
		self.send_response(code)
		self.send_header('Content-type', mimetype)
		self.send_header('Content-Length', str(len(text)))
		self.end_headers()
		self.wfile.write(text)
		self.wfile.flush()

class MyThreadingServer(SocketServer.ThreadingTCPServer):
	def __init__(self, server_address, request_handler, AllowIPs):
		SocketServer.ThreadingTCPServer.__init__(self, server_address, request_handler)
		self.AllowIPs = [mask.split('.') for mask in AllowIPs]

	def server_bind(self):
		SocketServer.ThreadingTCPServer.server_bind(self)
		host, self.server_port = self.socket.getsockname()[:2]
		self.server_name = socket.getfqdn(host)

	def verify_request(self, dummy, client_address):
		def check_ip(mask):
			for mask_part, ip_part in zip(mask, ip):
				if mask_part != ip_part and mask_part != '*':
					return False
			return True
		ip = client_address[0].split('.')
		for mask in self.AllowIPs:
			if check_ip(mask):
				return True
		print "IP [%s] not allowed!" % client_address
		return False

def ServerStart(ListenPort, AllowIPs):
	print "Starte HTTP-Server auf Port .:", ListenPort
	print "Zugelassener IP-Bereich .....:", AllowIPs
	httpd = MyThreadingServer(("", ListenPort), MyRequestHandler, AllowIPs)
	httpd.serve_forever()

if __name__=="__main__":
	ServerStart(ListenPort=6667, AllowIPs=('*.*.*.*'))
das ganze funzt ohne probleme (python 2.5.1 @ windows). wenn ich allerdings mehrmals die seite aufrufe, erhöht sich der verbrauche arbeitsspeicher des prozesses und wird nicht freigeben was dann irgendwann zum crash führt... wüßte jemand wo das problem liegt? ich schätze irgendwie bei dem threadTCPServer mit den threads oder so ähnlich?
BlackJack

Mittwoch 23. Mai 2007, 17:23

Zwei Vermutungen: `psyco` kann eventuell *viel* Speicher benötigen. Den Sinn sehe ich bei einem Webserver, der in aller Regel nicht CPU- sondern E/A-lastig ist, sowieso nicht so ganz.

Das andere ist die `__del__()`-Methode, die bei "Objektzyklen" verhindert, dass der Garbage Collector die Objekte "abräumen" kann.
Lambda
User
Beiträge: 25
Registriert: Freitag 27. April 2007, 17:11

Mittwoch 23. Mai 2007, 19:06

habe beides entfern, hat etwas geholfen. allerdings wächst der speicher immernoch, allerdings langsamer und nur von verschiedenenn usern (ips), womit kann das noch zusammen hängen?
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Mittwoch 23. Mai 2007, 19:08

Vielleicht spawnt er so viele Threads wie er möchte, bevor alte wieder zu verwenden, weil kein Maximal-Limit angegeben ist?
Lambda
User
Beiträge: 25
Registriert: Freitag 27. April 2007, 17:11

Mittwoch 23. Mai 2007, 19:39

das tut er, für jeden GET 1 thread, also eine seite mit 5 bildern = 6 threads... wie kann ich sowas hinbekommen vonwegen alte threads wiederverwenden? (irgendwie erinnere ich mich an den cherrpy wsgi, dort gabs standard 10 worker threads)?
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Mittwoch 23. Mai 2007, 20:01

Da müsstest du einen Pool oder eine Queue bilden. Vielleicht kann dich ja der Quelltext von Paste-HTTP-Server inspirieren, der das Threadverhalten einigermaßen konfigurierbar hält.
Lambda
User
Beiträge: 25
Registriert: Freitag 27. April 2007, 17:11

Mittwoch 23. Mai 2007, 22:19

ah, scheint das richtige zu sein, allerdings nicht gerade leicht für mich pers. das rauszufiltern, damit ich das ganze möglichst minimalistisch habe um mich da reinzusteigern... hast du evtl. noch andere sources?
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Mittwoch 23. Mai 2007, 23:44

Lambda hat geschrieben:das ganze funzt ohne probleme (python 2.5.1 @ windows). wenn ich allerdings mehrmals die seite aufrufe, erhöht sich der verbrauche arbeitsspeicher des prozesses und wird nicht freigeben was dann irgendwann zum crash führt...
Hallo Lambda!

Ich habe mich jetzt durch den Code von SimpleHTTPServer, BaseHTTPServer und SocketServer gewühlt.

Wird ein Request vom Browser gesendet, dann wird dieser Request an einen Thread weitergegeben. Dieser Thread arbeitet diesen einzelnen Request ab. Dabei werden zwei Dateiobjekte angelegt, über die mit dem Socket kommuniziert wird.

Die Datei-Objekte werden wieder geschlossen, wenn im StreamRequestHandler die Methode "finish()" aufgerufen wird. Und "finish()" wird von deinem Programm korrekt aufgerufen (ich habe zum Testen "finish()" überschrieben).

Nach dem Abarbeiten beendet sich der Thread von selbst. Es können also hunderte Threads gleichzeitig arbeiten und sollten sich eigentlich nicht in die Quere kommen, da sie unabhängig voneinander arbeiten.

Ich kann keinen Fehler in deinem Code feststellen. -- Bis auf die bereits gerügten "__del__" und "psyco". __del__ kann den GC unterlaufen und psyco bringt dir in deinem Programm nichts, da keine großen Berechnungen zu durchlaufen sind.

Dein Code sollte eigentlich korrekt arbeiten. Und wenn irgendwann mal ein anderes Programm Speicher braucht, dann sollte durch den GC wieder genügend Speicher frei werden.

Jetzt kommt die große Frage:
Stürzt dein Programm wirklich ab??? Oder vermutest du nur, dass es irgendwann mal abstürzen könnte???

Der GC gibt den Speicher nicht sofort frei. Der GC muss Zeit dafür finden und den Bedarf erkennen (das glaube ich irgendwo gelesen zu haben). Wenn genügend Speicher frei ist, dann braucht das Freigeben des Speichers mehr Zeit, als den Speicher bis zu einem gewissen Grad befüllt stehen zu lassen, auch wenn der Speicher nicht mehr vom Programm benötigt wird. Das ist der Grund, weshalb man glauben könnte, dass der Speicher nicht mehr frei gegeben wird.

mfg
Gerold
:-)

PS: Ich habe mir nur den Code von Python 2.4.4 angesehen. Ich gehe aber davon aus, dass in Python 2.5.1 kein Fehler dazu gekommen ist, der evt. ein Objekt im Speicher sperrt.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Lambda
User
Beiträge: 25
Registriert: Freitag 27. April 2007, 17:11

Donnerstag 24. Mai 2007, 05:45

danke gerold :) ich bin davon ausgegangen das irgendwann das programm crasht, da ich bisher dachte es wird echt nichtmehr freigegeben. kann es sein das es etwas unnütze ist für jeden request 1 thread zu öffnen, wäre das mit worker threads schneller und würde der GC vielleicht garnicht erst unnütze speicher extra reservieren da die threads dann immer aktiv sind?
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Donnerstag 24. Mai 2007, 08:55

Lambda hat geschrieben:kann es sein das es etwas unnütze ist für jeden request 1 thread zu öffnen, wäre das mit worker threads schneller und würde der GC vielleicht garnicht erst unnütze speicher extra reservieren da die threads dann immer aktiv sind?
Hallo Lambda!

Wenn du keine Threads verwendest, dann werden die Anfragen hintereinander abgearbeitet. Es wird aber für jede Anfrage ein RequestHandler erstellt und mit der Arbeit betraut.

Wenn du Threads verwendest, dann werden mehrere RequestHandler gleichzeitig mit verschiedensten Anfragen betraut. Es ändert sich also, dass die RequestHandler nebeneinander arbeiten. Bei wenigen Requests oder wenn sowiso immer alles hintereinander abgearbeitet werden soll, dann sind Threads sinnlos oder sogar gefährlich. Aber auch schon eine Website wie diese Foren-Website besteht aus so vielen Elementen, die vom Browser bei jedem Seitenaufbau angefordert werden, dass sich hier ein Threading-Webserver rentiert. Jedes Bild muss z.B. vom Webserver einzeln abgeholt werden -- abhängig von der HTTP-Version können auch mehrere Anfragen in einem Request zusammengefasst werden, aber darauf ist Python schon vorbereitet.

Wenn du mit Threads arbeitest, dann musst du aber auch darauf achten, Daten zwischen Threads nur über "Thread-sichere" Objekte auszutauschen. Du kannst z.B. nicht jeden Thread in eine Textdatei schreiben lassen, da sich die Threads in die Quere kommen können. Genau so ist es mit Pickle. Sobald du mit Threads arbeitest, solltest du auch einen "Thread-sicheren" Datenunterbau verwenden. Z.B. eine Datenbank wie PostgreSQL, mit der du mit mehreren Verbindungen gleichzeitig arbeiten kannst.

Wenn du dir den Aufwand sparen möchtest, dann verzichte auf Threads. Die rentieren sich sowiso nur bei vielen gleichzeitigen Requests.

Edit:
Du kannst es ja mal testen. Das hier ist ein einfacher XMLRPC-Server: http://www.python-forum.de/topic-5478.html
Greife doch mal auf diesen XMLRPC-Server mit dem Client aus diesem Beitrag zu: http://www.python-forum.de/topic-7545.html
Verändere die Anzahl der gleichzeitigen Anforderungen um raus zu finden, ab wie vielen gleichzeitigen Anforderungen der einfache XMLRPC-Server nicht mehr nach kommt.

Probiere dann den "multithreaded" XMLRPC-Server aus. Du wirst sehen, dass dieser viel mehr gleichzeitige Anfragen schaffst. Du wirst aber auch heraus finden, dass der einfache XMLRPC-Server alles schön hintereinander abarbeitet und dabei auch nicht gerade langsam ist.

Es hängt also wirklich davon ab, was du mit deinem Server erreichen möchtest, ob du Threading einsetzen solltest oder nicht.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Lambda
User
Beiträge: 25
Registriert: Freitag 27. April 2007, 17:11

Donnerstag 24. Mai 2007, 09:47

Wenn du Threads verwendest, dann werden mehrere RequestHandler gleichzeitig mit verschiedensten Anfragen betraut. Es ändert sich also, dass die RequestHandler nebeneinander arbeiten. Bei wenigen Requests oder wenn sowiso immer alles hintereinander abgearbeitet werden soll, dann sind Threads sinnlos oder sogar gefährlich. Aber auch schon eine Website wie diese Foren-Website besteht aus so vielen Elementen, die vom Browser bei jedem Seitenaufbau angefordert werden, dass sich hier ein Threading-Webserver rentiert. Jedes Bild muss z.B. vom Webserver einzeln abgeholt werden -- abhängig von der HTTP-Version können auch mehrere Anfragen in einem Request zusammengefasst werden, aber darauf ist Python schon vorbereitet.
das mit dem zusammenfassen habe ich gestern schon probiert, allerdings bekomme ich schon beim 2 element ein "connection software error". wenn jemand ne .html aufruft, wärs schon praktisch das gleich alle benötigten <img>'s mitübertragen werden. wie sähe eine zusammen fassung von request aus? (aus python programmier sicht)
Wenn du mit Threads arbeitest, dann musst du aber auch darauf achten, Daten zwischen Threads nur über "Thread-sichere" Objekte auszutauschen. Du kannst z.B. nicht jeden Thread in eine Textdatei schreiben lassen, da sich die Threads in die Quere kommen können. Genau so ist es mit Pickle. Sobald du mit Threads arbeitest, solltest du auch einen "Thread-sicheren" Datenunterbau verwenden. Z.B. eine Datenbank wie PostgreSQL, mit der du mit mehreren Verbindungen gleichzeitig arbeiten kannst.
:) dann werde ich auf jedenfall postgresql benutzen (hast du evtl. eine empfehlung welche "lib" ich nutzen sollte?), habe auch schon aus dem forum erfahren das PostreSQL query zusammenfassen kann oder ähnliches, was bei großen abfragen wesentlich schneller sein soll als mysql gegebenüber?

nochmals danke für die hilfe :)

MfG
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Donnerstag 24. Mai 2007, 11:58

Lambda hat geschrieben:wenn jemand ne .html aufruft, wärs schon praktisch das gleich alle benötigten <img>'s mitübertragen werden. wie sähe eine zusammen fassung von request aus? (aus python programmier sicht)
Hallo Lambda!

HTTP/1.0 baut für jede Anfrage eine TCP/IP-Verbindung auf.
HTTP/1.1 kann mehrere Anfragen über eine TCP/IP-Verbindung senden.

Das ist also eine Sache, die zwischen dem Browser und dem Server ausgemacht wird. Wie ich gestern gesehen habe, scheint es so, dass Python mit so etwas klar kommt.

Dieser Code in "BaseHTTPServer.py" lässt zumindest darauf schließen:

Code: Alles auswählen

    def handle_one_request(self):
        """Handle a single HTTP request.

        You normally don't need to override this method; see the class
        __doc__ string for information on how to handle specific HTTP
        commands such as GET and POST.

        """
        self.raw_requestline = self.rfile.readline()
        if not self.raw_requestline:
            self.close_connection = 1
            return
        if not self.parse_request(): # An error code has been sent, just exit
            return
        mname = 'do_' + self.command
        if not hasattr(self, mname):
            self.send_error(501, "Unsupported method (%r)" % self.command)
            return
        method = getattr(self, mname)
        method()

    def handle(self):
        """Handle multiple requests if necessary."""
        self.close_connection = 1

        self.handle_one_request()
        while not self.close_connection:
            self.handle_one_request()
Edit:
Siehe: http://de.wikipedia.org/wiki/Http#Protokollversionen

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
jens
Moderator
Beiträge: 8483
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Donnerstag 24. Mai 2007, 12:15

Hab interessiert mitgelesen ;)

In der Doku habe ich das gefunden:
protocol_version
This specifies the HTTP protocol version used in responses. If set to 'HTTP/1.1', the server will permit HTTP persistent connections; however, your server must then include an accurate Content-Length header (using send_header()) in all of its responses to clients. For backwards compatibility, the setting defaults to 'HTTP/1.0'.
siehe http://docs.python.org/lib/module-BaseHTTPServer.html

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Donnerstag 24. Mai 2007, 13:27

For backwards compatibility, the setting defaults to 'HTTP/1.0'.
Hallo Jens!

Hier die Ausgabe des Beispielprogramms von Lambda:

Code: Alles auswählen

J:\Dokumente und Einstellungen\Gerold\Desktop>hallo.py
Starte HTTP-Server auf Port .: 6667
Zugelassener IP-Bereich .....: *.*.*.*
localhost - - [24/May/2007 14:24:02] "GET / HTTP/1.1" 200 -
object deleted.
localhost - - [24/May/2007 14:24:10] "GET / HTTP/1.0" 200 -
object deleted.
^C
J:\Dokumente und Einstellungen\Gerold\Desktop>
Zuerst habe ich den URL mit Firefox aufgerufen.
Dann habe ich den gleichen URL mit "wget" aufgerufen.
Wie man sieht, macht sich der Firefox HTTP/1.1 als Protokoll aus, da er es kann. "wget" hingegen scheint es nicht zu unterstützen und der Server fällt auf HTTP/1.0 zurück. Man muss also nichts verstellen, damit der Server HTTP/1.1-Verbindungen annimmt.

lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
BlackJack

Donnerstag 24. Mai 2007, 15:04

Damit teilt der Client mit welche Protokollversion er kann, nicht welche dann, insbesondere auch vom Server, "gesprochen" wird.
Antworten