服务器架构 操作小记

血红的双手。 提交于 2019-12-31 12:24:20

代码位置

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_errorFalse
  • 此时客户端将创建一个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()
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!