6

我有小型服務器和客戶端Python腳本,客戶端發送一個字符串,服務器以相反的方式響應。客戶端輸入退出字符串時,客戶端退出,然後服務器退出。如何在不斷檢查輸入線程的同時運行後臺程序?

我希望服務器的「接收,反向,發送」過程在後臺運行,而程序不斷檢查標準輸入的退出字符串。

我試過使用threading,但由於阻塞,許多套接字調用導致它無法正常工作。

這樣你就可以知道我已經做了什麼。

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

「接收,反向,發送」看起來像是花了很短的時間,在我看來,你爲什麼要讓它在後臺運行? – justhalf

+0

因爲我看不到有什麼辦法在同一個進程中「接收,反向,發送」和「獲取用戶輸入」,因爲''raw_input()''會阻塞,直到它接收到中斷數據包流的輸入爲止。 我有兩個想法,我不想付諸行動: 給予客戶端殺死服務器的能力,或限制''raw_input()''阻塞(超時)的時間長度 – lightandlight

+0

我沒有理解。所以你想要的是將兩個腳本「server.py」和「client.py」合併成一個腳本,然後運行一個腳本?或者你只是想在後臺運行「server.py」(在Unix中可以使用'python server.py&'來完成)? – justhalf

回答

11

既然你希望服務器進程能夠同時在同一時間接收輸入來處理客戶端從服務器的stdin,您可以將當前的整個服務器代碼放入Thread,然後等待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 

並且您可以刪除客戶端中的「退出」檢查。

在此代碼中,服務器在客戶端斷開連接後將不會執行任何操作,但它只會等待「退出」在stdin中輸入。您可能需要擴展代碼以使服務器能夠接受新客戶端,因爲您不希望客戶端有能力關閉服務器。在這種情況下,您可以將另一個while循環從conn = sock.accept()更改爲sock.close()

而@usmcs建議,如果您沒有任何其他命令要發送到服務器,它會更好,如果你使用CTRL-C(KeyboardInterrupt),所以你不需要線程,而它仍然可以結束服務器正常(意味着沒有誤差由於CTRL-C報道)與此代碼:

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

謝謝。這是我設想的。 – lightandlight

1

這是非阻塞套接字接收的例子。如果沒有數據接收套接字將拋出異常。

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 :) 

這裏是標準輸入非阻塞的例子閱讀:

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() 

基本上,你希望將它們組合,並且可以添加一些超時強制定期讀取標準輸入在很多的情況下,連接。

1

我以前發佈過的用於在Python中構建預分叉的JSON-RPC服務器並修改代碼以解決此問題的要點。要點在這裏:https://gist.github.com/matthewstory/4547282

$ python server.py localhost 9999 5 
exit 
$ 

查看更多爲什麼這個工程。主叉派生N許多叉(在上面5的示例),其中的每一個進入一個接受循環:

# 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) 

這些子叉將處理任何傳入請求localhost:9999。主叉然後下降到一個選擇/ waitpid函數組合循環:

# 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) 

select要麼指示stdin是準備進行讀取,在這種情況下,我們將從stdin讀取1線,或至多後它就會超時1s,在這種情況下,它將直接轉入我們對任何已退出子女的支票(使用os.waitpidWNOHANG標誌)。