我有小型服务器和客户端Python脚本,客户端发送字符串,服务器反向响应。当客户端输入退出字符串时,客户端退出,然后服务器退出。
我希望服务器的“接收,反向,发送”过程在后台运行,同时程序不断检查stdin是否有退出字符串。
我尝试过使用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 :(得分: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
中键入“exit”。您可能希望扩展代码以使服务器能够接受新客户端,因为您不希望客户端能够关闭服务器。在这种情况下,您可以将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 :(得分: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 :)
以下是非阻塞stdin读取的示例:
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()
基本上,你想要将它们组合在一起,并且可以添加一些超时来强制定期读取stdin,以防多个连接。
答案 2 :(得分: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
的任何传入请求。然后主fork进入select / 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.waitpid
标记的WNOHANG
)。