代码位置
https://github.com/duganlx/fopnp/tree/m/py3
支持《Python之禅》示例协议的数据与规则
文件位置:fopnp/py3/chapter07/zen_utils.py
import argparse, socket, time
aphorisms = {b'Beautiful is better than?': b'Ugly.',
b'Explicit is better than?': b'Implicit.',
b'Simple is better than?': b'Complex.'}
def get_answer(aphorism):
"""Return the string response to a particular Zen-of-Python aphorism."""
time.sleep(0.0) # increase to simulate an expensive operation
return aphorisms.get(aphorism, b'Error: unknown aphorism.')
def parse_command_line(description):
"""Parse command line and return a socket address."""
parser = argparse.ArgumentParser(description=description)
parser.add_argument('host', help='IP or hostname')
parser.add_argument('-p', metavar='port', type=int, default=1060,
help='TCP port (default 1060)')
args = parser.parse_args()
address = (args.host, args.p)
return address
def create_srv_socket(address):
"""Build and return a listening server socket."""
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.bind(address)
listener.listen(64)
print('Listening at {}'.format(address))
return listener
def accept_connections_forever(listener):
"""Forever answer incoming connections on a listening socket."""
while True:
sock, address = listener.accept()
print('Accepted connection from {}'.format(address))
handle_conversation(sock, address)
def handle_conversation(sock, address):
"""Converse with a client over `sock` until they are done talking."""
try:
while True:
handle_request(sock)
except EOFError:
print('Client socket to {} has closed'.format(address))
except Exception as e:
print('Client {} error: {}'.format(address, e))
finally:
sock.close()
def handle_request(sock):
"""Receive a single client request on `sock` and send the answer."""
aphorism = recv_until(sock, b'?')
answer = get_answer(aphorism)
sock.sendall(answer)
def recv_until(sock, suffix):
"""Receive bytes over socket `sock` until we receive the `suffix`."""
message = sock.recv(4096)
if not message:
raise EOFError('socket closed')
while not message.endswith(suffix):
data = sock.recv(4096)
if not data:
raise IOError('received {!r} then socket closed'.format(message))
message += data
return message
代码解析:
aphorisms
aphorisms = {b'Beautiful is better than?': b'Ugly.',
b'Explicit is better than?': b'Implicit.',
b'Simple is better than?': b'Complex.'}
- 客户端希望服务器理解的3个间题作为
aphorisms
字典的键列出,对应的回答则以字典值的形式存储
get_answer(aphorism)
def get_answer(aphorism):
"""Return the string response to a particular Zen-of-Python aphorism."""
time.sleep(0.0) # increase to simulate an expensive operation
return aphorisms.get(aphorism, b'Error: unknown aphorism.')
- 为了在字典中查找回答而编写的一个简单的快速函数
- 如果传入的问题无法被识别的话,该函数会返回一个简短的错误信息
- 客户端的请求始终以问号结尾
- 回答则始终以句点结尾
- 这两个标点符号为这个协议提供了封帧的功能
parse_command_line(description)
def parse_command_line(description):
"""Parse command line and return a socket address."""
parser = argparse.ArgumentParser(description=description)
parser.add_argument('host', help='IP or hostname')
parser.add_argument('-p', metavar='port', type=int, default=1060,
help='TCP port (default 1060)')
args = parser.parse_args()
address = (args.host, args.p)
return address
- 提供了用于读取命令行参数的通用机制
create_srv_socket(address)
def create_srv_socket(address):
"""Build and return a listening server socket."""
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.bind(address)
listener.listen(64)
print('Listening at {}'.format(address))
return listener
- 则用于构造TCP的监听套接字,服务器通过监听套接字来接受连接请求
accept_connections_forever(listener)
def accept_connections_forever(listener):
"""Forever answer incoming connections on a listening socket."""
while True:
sock, address = listener.accept()
print('Accepted connection from {}'.format(address))
handle_conversation(sock, address)
- 只包含一个简单的循环
- 循环中不断通过监听套接字接受连接请求
- 并且使用print()把每个连接的客户端打印出来
- 然后将连接套接字作为参数传递给
handle_conversation()
handle_conversation(sock, address)
def handle_conversation(sock, address):
"""Converse with a client over `sock` until they are done talking."""
try:
while True:
handle_request(sock)
except EOFError:
print('Client socket to {} has closed'.format(address))
except Exception as e:
print('Client {} error: {}'.format(address, e))
finally:
sock.close()
- 包含一个无限循环,来不断地处理请求
- 该程序会捕捉可能发生的错误,这样的设计使得客户端套接字的任何问题都不会引起程序的崩溃
- 如果客户端完成了所有的请求并且已经挂起,那么最内层的数据接收循环会抛出
EOFError
异常作为信号传递的方式 - 程序专门在一个单独的
except
从句中捕捉了E0FError
异常,而将所有其他异常都视为错误,这些错误被捕捉后会通过print()
函数进行输出 finally
从句能够确保无论该函数通过哪一条代码路径退出,始终都会将客户端套接字关闭
handle_request(sock)
def handle_request(sock):
"""Receive a single client request on `sock` and send the answer."""
aphorism = recv_until(sock, b'?')
answer = get_answer(aphorism)
sock.sendall(answer)
- 简单地读取客户端的问题,然后做出应答
- 因为
send()
调用本身无法保证数据发送的完整性,所以要使用sendall()
recv_until(sock, suffix)
def recv_until(sock, suffix):
"""Receive bytes over socket `sock` until we receive the `suffix`."""
message = sock.recv(4096)
if not message:
raise EOFError('socket closed')
while not message.endswith(suffix):
data = sock.recv(4096)
if not data:
raise IOError('received {!r} then socket closed'.format(message))
message += data
return message
- 使用第5章中概述的方法进行封帧
- 不断累加的字节字符串没有形成一个完整的问题,就会不断重复调用套接字的
recv()
方法
用于《Python之禅》示例协议的客户端程序
文件位置:fopnp/py3/chapter07/client.py
import argparse, random, socket, zen_utils
def client(address, cause_error=False):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(address)
aphorisms = list(zen_utils.aphorisms)
if cause_error:
sock.sendall(aphorisms[0][:-1])
return
for aphorism in random.sample(aphorisms, 3):
sock.sendall(aphorism)
print(aphorism, zen_utils.recv_until(sock, b'.'))
sock.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Example client')
parser.add_argument('host', help='IP or hostname')
parser.add_argument('-e', action='store_true', help='cause an error')
parser.add_argument('-p', metavar='port', type=int, default=1060,
help='TCP port (default 1060)')
args = parser.parse_args()
address = (args.host, args.p)
client(address, args.e)
说明:
- 在正常情况下,
cause_error
为False
- 此时客户端将创建一个TCP套接字,然后发送3句格言作为请求,每发送一个就等待服务器返回相应的答案
- 不过有时会想知道本章的服务器会如何处理输入有误的情况,因此该客户端提供了
-e
选项,用来发送不完整的问题,然后使服务器突然挂起 - 如果没有提供
-e
选项,那么只要服务器已经启动并且正确运行,就能在客户端看到这3个问题以及相应的答案
运行效果
# 需要运行一个服务器
python client.py 127.0.0.1
b'Explicit is better than?' b'Implicit.'
b'Beautiful is better than?' b'Ugly.'
b'Simple is better than?' b'Complex.'
最简单的可用服务器是单线程的
文件位置:fopnp/py3/chapter07/srv_single.py
import zen_utils
if __name__ == '__main__':
address = zen_utils.parse_command_line('simple single-threaded server')
listener = zen_utils.create_srv_socket(address)
zen_utils.accept_connections_forever(listener)
说明
- 上面的这个服务器要求提供一个命令行参数,供服务器用来监听接请求的接口
- 如果要防止LAN或网络中的其他用户访问该服务器的话,应指定标准本地主机IP地址127.0.0.1作为监听接口
- 提供空字符串作为参数(这在Python中表示当前机器上的任意接口),这样就能够通过本机的所有接口来提供服务
- 上一个连接一关闭,这个服务器就可以准备好进行下一个连接
运行效果
python srv_single.py 127.0.0.1
Listening at ('127.0.0.1', 1060)
Accepted connection from ('127.0.0.1', 11954)
Client socket to ('127.0.0.1', 11954) has closed
多线程服务器
文件位置:fopnp/py3/chapter07/srv_threaded.py
import zen_utils
from threading import Thread
def start_threads(listener, workers=4):
t = (listener,)
for i in range(workers):
Thread(target=zen_utils.accept_connections_forever, args=t).start()
if __name__ == '__main__':
address = zen_utils.parse_command_line('multi-threaded server')
listener = zen_utils.create_srv_socket(address)
start_threads(listener)
使用标准库服务器模式构建的多线程服务器
文件位置:fopnp/py3/chapter07/srv_legacy1.py
from socketserver import BaseRequestHandler, TCPServer, ThreadingMixIn
import zen_utils
class ZenHandler(BaseRequestHandler):
def handle(self):
zen_utils.handle_conversation(self.request, self.client_address)
class ZenServer(ThreadingMixIn, TCPServer):
allow_reuse_address = 1
# address_family = socket.AF_INET6 # uncomment if you need IPv6
if __name__ == '__main__':
address = zen_utils.parse_command_line('legacy "SocketServer" server')
server = ZenServer(address, ZenHandler)
server.serve_forever()
来源:CSDN
作者:duganlx
链接:https://blog.csdn.net/qq_40626497/article/details/103776687