6

Ich habe kleine Server und Client-Python-Skripte, wo der Client eine Zeichenfolge sendet und der Server antwortet mit der Umkehrung. Wenn der Client eine Beendigungszeichenfolge eingibt, wird der Client beendet und der Server beendet.Wie führe ich eine Hintergrundprozedur aus, während ich ständig nach Eingaben suche - Threading?

Ich möchte die "receive, reverse, send" -Prozedur des Servers im Hintergrund laufen lassen, während das Programm ständig stdin nach einer Quit-Zeichenkette prüft.

Ich habe versucht mit threading aber wegen der Blockierung, dass viele Socket-Aufrufe verursachen würde es nicht richtig funktionieren.

Nur damit Sie eine Vorstellung davon bekommen, was ich bereits getan habe.

server.py:

import socket 
from time import sleep 

sock = socket.socket() 
sock.bind(("127.0.0.1",12346)) 
sock.listen(3) 
print "Waiting on connection" 
conn = sock.accept() 
print "Client connected" 

while True: 
    m = conn[0].recv(4096) 
    if m == "exit": 
     sleep(1) 
     break 
    else: 
     conn[0].send(m[::-1]) 

sock.shutdown(socket.SHUT_RDWR) 
sock.close() 

client.py:

import socket 

sock = socket.socket() 
sock.connect(("127.0.0.1",12346)) 

while True: 
    s = raw_input("message: ") 
    sock.send(s) 

    if s == "exit": 
     print "Quitting" 
     break 

    print sock.recv(4096) 

sock.shutdown(socket.SHUT_RDWR) 
sock.close() 
+0

"receive, reverse, send" sieht aus wie eine sehr kurze Zeit, meiner Meinung nach, warum möchten Sie es in den Hintergrund laufen lassen? – justhalf

+0

Weil ich im selben Prozess keine Möglichkeit sehe, "receive, reverse, send" und "get user input" zu erhalten, weil "rohe_input()" blockiert, bis es Eingaben empfängt, die den Fluss von Paketen unterbrechen. Ich habe zwei Ideen, die ich nicht in die Tat umsetzen möchte: Geben Sie dem Client die Möglichkeit, den Server zu töten, oder begrenzen Sie wie lange '' raw_input() '' blockiert für (ein Timeout) – lightandlight

+0

ich nicht verstehen. Sie möchten also die beiden Skripte "server.py" und "client.py" in einem einzigen Skript zusammenfassen und nur dieses eine Skript ausführen? Oder willst du einfach "server.py" im Hintergrund laufen lassen (in Unix kann das mit 'python server.py &' geschehen)? – justhalf

Antwort

11

Da wollen Sie den Serverprozess der Lage sein, den Client zu handhaben, während in der gleichen Zeit Eingang empfängt aus dem Server stdin, können Sie nur den gesamten aktuellen Server-Code in eine Thread setzen, dann warten Sie die Eingabe von stdin.

import socket 
from time import sleep 
import threading 

def process(): 
    sock = socket.socket() 
    sock.bind(("127.0.0.1",12346)) 
    sock.listen(3) 
    print "Waiting on connection" 
    conn = sock.accept() 
    print "Client connected" 

    while True: 
     m = conn[0].recv(4096) 
     conn[0].send(m[::-1]) 

    sock.shutdown(socket.SHUT_RDWR) 
    sock.close() 

thread = threading.Thread(target=process) 
thread.daemon = True 
thread.start() 
while True: 
    exit_signal = raw_input('Type "exit" anytime to stop server\n') 
    if exit_signal == 'exit': 
     break 

und Sie können den "Exit" Check-in Client entfernen.

In diesem Code wird der Server nichts tun, nachdem der Client die Verbindung getrennt hat, aber es wird nur auf "Exit" warten, um in die stdin eingegeben werden. Möglicherweise möchten Sie den Code erweitern, damit der Server neue Clients akzeptieren kann, da Sie nicht möchten, dass der Client den Server schließen kann. In diesem Fall können Sie eine weitere while Schleife von conn = sock.accept() bis sock.close() setzen.

Und wie @usmcs vorgeschlagen, wenn Sie keinen anderen Befehl an den Server gesendet haben, ist es besser, wenn Sie stattdessen STRG-C (KeyboardInterrupt) verwenden, so dass Sie den Thread nicht benötigen, während es immer noch den Server ordnungsgemäß beenden kann (dh keine Fehler aufgrund des CTRL-C berichtet wird) mit diesem Code:

import socket 
from time import sleep 
import threading 

sock = socket.socket() 
sock.bind(("127.0.0.1",12346)) 
sock.listen(3) 
print "Waiting on connection" 
conn = sock.accept() 
print "Client connected" 

while True: 
    try: 
     m = conn[0].recv(4096) 
     conn[0].send(m[::-1]) 
    except KeyboardInterrupt: 
     break 

sock.close() 
+1

Danke. Das habe ich mir vorgestellt. – lightandlight

1

Dies ist ein Beispiel von nicht-blockierenden Socket empfangen ist. Im Falle von keine Daten zu empfangen Socket wird eine Ausnahme auslösen. Hier

import sys 
import socket 
import fcntl, os 
import errno 
from time import sleep 

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.connect(('127.0.0.1',9999)) 
fcntl.fcntl(s, fcntl.F_SETFL, os.O_NONBLOCK) 

while True: 
    try: 
     msg = s.recv(4096) 
    except socket.error, e: 
     err = e.args[0] 
     if err == errno.EAGAIN or err == errno.EWOULDBLOCK: 
      sleep(1) 
      print 'No data available' 
      continue 
     else: 
      # a "real" error occurred 
      print e 
      sys.exit(1) 
    else: 
     # got a message, do something :) 

ist ein Beispiel für nicht-blockierenden stdin lesen:

import sys 
import select 

# If there's input ready, do something, else do something 
# else. Note timeout is zero so select won't block at all. 
while sys.stdin in select.select([sys.stdin], [], [], 0)[0]: 
    line = sys.stdin.readline() 
    if line: 
    something(line) 
    else: # an empty line means stdin has been closed 
    print('eof') 
    exit(0) 
else: 
    something_else() 

Grundsätzlich Sie wollen, dass sie kombinieren und etwas Timeout hinzufügen kann Lesen stdin zu zwingen, auf einer regelmäßigen Basis bei vielen Verbindungen.

1

Ich nahm einen Kern, den ich zuvor für den Aufbau eines vorgekohlten JSON-RPC-Servers in Python veröffentlicht hatte, und modifizierte den Code, um dieses Problem zu lösen. Hier Gist: https://gist.github.com/matthewstory/4547282

$ python server.py localhost 9999 5 
exit 
$ 

Suchen Sie mehr darüber, warum das funktioniert.Der Hauptgabel laicht N viele Gabeln (im Beispiel oben 5), von denen jede geht in eine Schleife annehmen:

# simple pre-fork server, fork before accept 
for i in range(int(argv[2])): 
    # fork our current process 
    pid = os.fork() 

    # if we are the child fork ... 
    if 0 == pid: 
     # die without unhandled exception 
     for signum in (signal.SIGINT, signal.SIGTERM,): 
      signal.signal(signum, _gogentle) 

     # under the hood, this calls `socket.accept` 
     s.serve_forever() 
     os._exit(0) 

    # if we are the papa fork 
    else: 
     _PIDS.append(pid) 

Diese Kind Gabeln werden alle eingehenden Anforderungen an localhost:9999 behandeln. Die Haupt-Gabel fällt dann in eine select/waitpid Combo-Schleife:

# setup signal relaying for INT and TERM 
for signum in (signal.SIGINT, signal.SIGTERM,): 
    signal.signal(signum, _kronos) 

# wait on the kids 
while len(_PIDS): 
    # 1s timeout here means we're checking for exiting children at most 
    # 1x per second, prevents a busy loop 
    reads, _, _ = select.select([sys.stdin], [], [], 1) 
    if sys.stdin in reads: 
     # blocking, read 1 line 
     cmd = sys.stdin.readline() 
     # kill ourselves ... kronos will propegate 
     if cmd.strip() == 'exit': 
      os.kill(os.getpid(), signal.SIGTERM) 

    # check for exited children, non-blocking 
    while True: 
     pid, rc = os.waitpid(-1, os.WNOHANG) 
     if not pid: 
      break 
     _PIDS.remove(pid) 

Die select wird entweder angeben, dass stdin zum Lesen bereit ist, in diesem Fall werden wir 1 Zeile von stdin gelesen werden, oder es wird nach höchstens Timeout 1s, in diesem Fall wird es direkt zu unserem Scheck für alle exited Kinder fallen (unter Verwendung os.waitpid mit der WNOHANG Flagge).

Verwandte Themen