2015-07-02 11 views
5

Ich spiele mit gevent und websockets. Dies ist ein einfaches Echo-Server:warum ist gevent-websocket synchron?

from gevent.pywsgi import WSGIServer 
from geventwebsocket.handler import WebSocketHandler 
from gevent import sleep 
from datetime import datetime 
def app(environ, start_response): 
    ws = environ['wsgi.websocket'] 
    while True: 
     data = ws.receive() 
     print('{} got data "{}"'.format(
      datetime.now().strftime('%H:%M:%S'), data)) 
     sleep(5) 
     ws.send(data) 

server = WSGIServer(("", 10004), app, 
    handler_class=WebSocketHandler) 
server.serve_forever() 

und der Kunde:

<html> 
    <body> 
     <button type="button" id="push_data">Push</button> 
    </body> 
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.11.3.js"></script> 
    <script> 
     var ws = new WebSocket("ws://localhost:10004"); 
     ws.onmessage = function(evt) { 
      console.log(evt) 
     }; 
     $('#push_data').click(function(){ 
      console.log('sending data...'); 
      ws.send('sample data'); 
     }); 
    </script> 
</html> 

Wegen gevent Ich erwartete mehrere greenlets haben die Daten dienen asynchron; Das heißt, wenn ich einige Daten mehrmals in den Websocket schob (schnell auf den Push-Button klickend), erwartete ich, dass ich nach 5 Sekunden Wartezeit alles gleichzeitig zurück hatte.

Doch egal, wie schnell ich die Push-Button klicken, ist das, was ich in der Konsole:

18:28:07 got data "sample data" 
18:28:12 got data "sample data" 
18:28:17 got data "sample data" 
18:28:22 got data "sample data" 
18:28:27 got data "sample data" 

warum es synchron meine Daten empfangen, alle 5 Sekunden eine Pause? Wie verwandele ich es in einen asynchronen Server?

Antwort

6

Das Verhalten ist synchron, weil Ihr eigener Code synchron ist. gevent ist nur eine Coroutinenbibliothek, die eine Ereignisschleife verwendet. Es verwandelt den synchronen Code nicht magisch in asynchronen Code.

haben Sie einen Blick auf die Dokumentation unter: http://www.gevent.org/servers.html

Es wird gesagt, dass die Server eine greenlet pro Verbindung (nicht pro Anfrage) laichen. Die Ausführung mehrerer Anforderungen für dieselbe Verbindung wird daher serialisiert.

Wenn Sie mehrere Anfragen für die gleiche Verbindung gleichzeitig bearbeiten möchten, müssen Sie neue Greenlets generieren oder die Verarbeitung an einen Pool von Greenlets delegieren.

Hier ist ein Beispiel (eine Greenlet bei jeder Anforderung Laichen):

import gevent 
from gevent.pywsgi import WSGIServer 
from gevent.lock import Semaphore 
from geventwebsocket.handler import WebSocketHandler 
from datetime import datetime 

def process(ws,data,sem): 
    print('{} got data "{}"'.format(datetime.now().strftime('%H:%M:%S'), data)) 
    gevent.sleep(5) 
    with sem: 
     ws.send(data) 

def app(environ, start_response): 
    ws = environ['wsgi.websocket'] 
    sem = Semaphore() 
    while True: 
     data = ws.receive() 
     gevent.spawn(process,ws,data,sem) 

server = WSGIServer(("", 10004), app,handler_class=WebSocketHandler) 
server.serve_forever() 

Beachten Sie die Anwesenheit des Semaphors. Da die Verarbeitung gleichzeitig erfolgt, muss verhindert werden, dass zwei gleichzeitige Greenlets gleichzeitig im Socket schreiben, was zu beschädigten Nachrichten führt.

Letzter Punkt, mit dieser Implementierung gibt es keine Garantie, dass die Antworten in der Reihenfolge der Anforderungen gesendet werden.

+1

Fällt Affe-Patching mit Gevent nicht "magisch synchronen Code in asynchronen Code verwandeln"? – FullStack

+2

Nur wenn Sie mehrere Greenlets und Verbindungen haben. Wenn sich der Code auf einzelne Verbindungen von einem einzelnen Greenlet bezieht, wird der Koroutinenmechanismus von gevent nicht helfen.Mit anderen Worten, Magie passiert nur, wenn du daran glaubst, und strukturiere deinen Code entsprechend ;-) –

0

Das eigentliche Problem ist folgendes: data = ws.receive()

Was geschieht hier ist Ihre websocket nun für eine Verbindung warten, während die ganze app hängt gerade heraus.

Sie haben zwei Lösungen, entweder eine Auszeit in der ws.receive() oder legen Sie es als eine hohe Anwendung auf:

from geventwebsocket import WebSocketServer, WebSocketApplication, Resource 

class EchoApplication(WebSocketApplication): 
    def on_open(self): 
     print "Connection opened" 

    def on_message(self, message): 
     self.ws.send(message) 

    def on_close(self, reason): 
     print reason 

WebSocketServer(('', 8000), Resource({'/': EchoApplication}).serve_forever() 

wie hier exampled: https://pypi.python.org/pypi/gevent-websocket/

Dies würde dann Setup Ihrer vollständig Async verarbeiten, also würde das Senden und Empfangen nicht um die gleiche Ressource konkurrieren.