第一个原型虽然是个管用的聊天服务器,但其功能很有限,最明显的缺陷是没法知道每句话都是谁说的。另外,它也不能解释命令(如say或logout),而最初的规范要求提供这样的功能。有鉴于此,需要添加对身份(每个用户都有唯一的名字)和命令解释的支持,同时必须让每个会话的行为都依赖于其所处的状态(刚连接、已登录等)。添加这些功能时,必须确保程序是易于扩展的。
5.1.基本的命令解释功能
我将演示如何模仿标准库模块cmd中Cmd类的命令解释功能。(遗憾的是,你不能直接使用这个类,因为它只能用于处理sys.stdin和sys.stdout,而你处理的是多个流)你需要一个函数或方法,用于处理用户输入的单行文本。这个方法应提取第一个单词(命令),并根据这个单词调用相应的方法。例如,如果文本行像下面这样:
say Hello,world!
将导致这个函数调用下面的方法:
do_say('Hello,world!')
do_say还可能将会话本身作为参数,以便知道是谁在说话。
下面是一种简单的实现,其中还包含一个处理未知命令的方法。
在这个类中,使用了getattr。实现基本的命令处理功能后,需要定义一些命令,并根据会话当前的状态决定哪些命令可用(以及它们将做什么)。如何表示会话的状态呢?
5.2.聊天室
每种状态都可用一个自定义的命令处理程序表示,很容易将此与标准的聊天室表示法(MUD中的地点)结合起来使用。每个聊天时都是一个包含特定命令的CommandHandler。另外,它还应记录聊天室内当前有哪些用户(会话)。下面是一个通用的超类,所有聊天室都将继承它。
除基本方法add和remove外,它还包含方法broadcast,这个方法对聊天室内的所有用户(会话)调用push。这个类还以方法do_logout的方式定义了一个命令——logout。这个方法引发异常EndSession,而这种异常将在较高的层级(found_terminator中)处理。
5.3.登陆和退出聊天室
除表示常规聊天室(这个项目中只有一个这样的聊天室)之外,Room的子类还可表示其他状态,这正是你创建Room类的意图所在。例如,用户刚连接到服务器时,将进入专用的LoginRoom(其中没有其它用户)。LoginRoom在用户进入时打印一条欢迎消息(这是在方法add中实现的)。他还重写了方法unknown,使其让用户登录。这个类只支持一个命令,即命令login,这个命令检查用户名是否是可接受的(不是空字符串,且未被其它用户使用)。
LogoutRoom要简单得多,它唯一的职责是将用户的名字从服务器中删除(服务器包含存储会话的字典users)。如果用户名不存在(因为用户从未登录),将忽略因此而引发的KeyError异常。
注意 虽然服务器中的字典users存储了指向所有会话的引用,但根本没有从中获取会话。字典users只用于记录哪些用户名被占用。然而,我没有将用户名关联到随便选择的值(如True),而是将其关联到相应的会话。虽然现在这样做没什么用处,但在以后的程序版本中可能发挥作用(例如,让用户能够发私信时)。也可采用另一种做法,将会话存储在一个集合或列表中。
5.4.主聊天室
主聊天室也重写了方法add和remove。在方法add中,它广播一条消息,指出有用户进入,同时将用户的名字添加到服务器中的字典users中。方法remove广播一条消息,指出有用户离开。
除这些方法外,ChatRoom类(主聊天室)还实现了三个命令。
5.5.新的服务器
至此已介绍了大部分功能。对于ChatSession和ChatServer类,所做的主要改进如下。
另外请注意,handle_accept不再将新的ChatSession添加到会话列表中,因为现在会话由聊天室管理。
注意 一般而言,如果你实例化一个对象(就像handle_accept中的ChatSession),而不将其赋给变量或添加到容器中,它将丢失并可能当作垃圾收集(这意味着它将完全消失)。由于所有的dispatcher都由asyncore处理(引用),而async_chat是一个dispatcher子类,因此在这里不是问题。
聊天服务器的最终代码如图所示。
聊天服务器支持的命令
下图是一个聊天过程示例。在这个示例中,服务器是使用如下命令启动的:
python chatserver.py
而用户win10是使用如下命令连接到服务器的:
telnet localhost 5005
6.进一步探索
对于这个基本服务器,可以在很多方面进行扩展和改进。
本文分享自 Python机器学习算法说书人 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!