练习项目10:在线聊天室(下) - 魔力Python​www.opython.com

这一篇教程,我们继续使用Python完成带有更多功能的聊天室。

因为功能比较多,这里我们先把功能归类,然后在此基础上编写代码。

分类示意图:

如上图所示,在新的功能中,我们要支持一些命令。

所以,需要一个对命令进行处理的类(CMDHandler)。

然后,房间实际上有三个,一个用于用户登入的房间(CheckInRoom),一个用于用户登出的房间(CheckOutRoom),还有就是进行聊天的房间(ChatRoom)。

房间都要包含一些功能,例如进入房间、离开房间、广播以及退出聊天室。

所以,我们抽象出一个房间类(Room),定义这些功能,再让具体的房间去继承。

除了以上所述内容,我们还要处理每一个来自客户端的连接与通信,这就需要创建聊天会话的类(ChatSession)和结束会话的类(EndSession)。

最后,还有启动服务创建与客户端连接的类(ChatServer)。

有了功能的具体划分之后,我们就依次来实现各个类。

1、导入模块

示例代码:

from asyncore import dispatcher

from asynchat import async_chat

import asyncore

2、CMDHandler类

在前面的示意图中,我们看到了如下命令:login :登录

logout:退出

say :发言

look:查看当前房间中的在线用户

who:查看当前服务器中的所有在线用户

注意:在当前案例中,我们只创建一个聊天室,所以房间中的用户和服务器中的用户是一样的,也就意味着look和who这两个命令的效果是相同的。

对应上述这些命令,我们需要编写相应的方法。

为了能够简单的调用命令所对应的方法,我们可以将方法名定义为:login:do_login()

logout:do_logout()

say:do_say()

look:do_look()

who:do_who()

如上所述,每个方法名都是前缀“do_”和命令m名称组成。

所以,当我们获取到来自客户端的数据,就要对数据进行分析,获取数据中包含的命令,并根据不同的命令调用不同的方法。

不过,我们还要想到,用户可能会有错误的输入,没有输入命令,或者输入了错误的命令,这种情况我们也需要进行处理。

接下来,大家可以通过示例代码中的注释了解整个处理过程。

示例代码:

class CMDHandler:

def unknown(self, session, cmd): # 定义未知命令的处理方法

session.push('不支持命令:{}\r\n请重新输入!\r\n'.format(cmd).encode('GBK')) # 向客户端推送错误提示

def handle(self, session, data): # 定义命令的处理方法

if data.strip(): # 判断去除空格后是否还有数据

parts = data.split(' ', 1) # 对数据以空格为分割符进行分割(最大分割次数为1次)

cmd = parts[0] # 分割后的第1部分为命令

try:

line = parts[1].strip() # 将分割后的第2部分去除空格保存到变量

except IndexError: # 如果捕获索引错误

line = '' # 设置变量为空值

method = getattr(self, 'do_' + cmd, None) # 获取指定名称的方法对象

try:

method(session, line) # 调用获取到的方法对象

except TypeError: # 如果捕获类型错误(没有找到方法)

self.unknown(session, cmd) # 调用未知命令的处理方法

3、Room类

这个类要定义房间的功能。

示例代码:

class Room(CMDHandler):

def __init__(self, server): # 定义初始化功能

self.server = server # 保存传入的服务器对象

self.sessions = [] # 初始化会话列表

def add(self, session): # 定义单个用户进入房间的处理方法

self.sessions.append(session) # 将单个用户的会话添加到会话列表

def remove(self, session): # 定义单个用户离开房间的处理方法

self.sessions.remove(session) # 从会话列表中移除单个用户的会话

def broadcast(self, line): # 定义广播信息的处理方法

for session in self.sessions: # 遍历所有的用户会话

session.push(line.encode('GBK')) # 向每一个用户会话广播信息(注意编码)

def do_logout(self, session, line): # 定义退出命令的处理方法

raise EndSession # 抛出结束会话的异常

这里要注意remove()方法和do_logout()方法的区别。

remove()方法是从一个房间离开的方法,例如离开CheckInRoom进入ChatRoom就会调用这个方法。

do_logout()方法是关闭客户端与服务器的连接对话的方法。

3、EndSession类

在上一段代码中我们看到了EndSession,它是我们定义的异常类,所以这个类要继承Exception类。

在这个类中,无需添加什么内容。

示例代码:

class EndSession(Exception):

pass

4、CheckInRoom类

为了避免和登录这个动作混淆,这里我使用了CheckInRoom这个名称。

这就好像我们去开房,要先在登记处的房间进行登记,然后才能进入入住的房间。

在这个类中,我们要实现登录的相关处理,包括:用户进入房间时发送欢迎信息

未使用login命令登录时的处理

使用login命令登录时的的处理

并且,使用login命令登录时也有不同的情形需要处理:未带有用户名

用户名已使用

正确输入

接下来,大家还是通过代码中的注释理解这些功能的实现。

示例代码:

class CheckInRoom(Room):

def add(self, session): # 重写超类的方法

Room.add(self, session) # 重载超类的方法

session.push('欢迎进入{}聊天室!\r\n'.format(self.server.name).encode('GBK')) # 推送欢迎信息

def unknown(self, session, cmd): # 重写位置命令的处理方法

session.push('请使用命令 进行登录!\r\n'.encode('GBK')) # 推送命令错误的提示

def do_login(self, session, line): # 定义登录命令的处理方法

name = line.strip() # 数据中命令后方的内容去重空格后作为用户名

if not name: # 如果没有内容

session.push('请输入用户名!\r\n'.encode('GBK')) # 推送提示

elif name in self.server.users: # 如果服务器的用户列表中已有这个用户名

session.push('用户名 已被使用!\r\n'.format(name).encode('GBK')) # 推送提示

else: # 正确输入时

session.name = name # 将用户名保存到会话

session.enter(self.server.main_room) # 将会话进入到主聊天房间

在上方代码中,enter()方法我们还没有定义,它是一个会话进入某个房间的方法,需要在ChatSession类中定义这个方法。

5、ChatRoom类

这个是聊天室的类,主要实现功能如下:一个会话进入当前房间的处理方法

一个会话离开当前房间的处理方法

处理say命令的方法

处理look命令的方法

处理who命令的方法

大家通过示例代码中的注释理解实现过程。

示例代码:

class ChatRoom(Room):

def add(self, session):

self.broadcast('用户 进入聊天室!\r\n') # 广播用户进入房间的信息

Room.add(self, session) # 重载超类的方法

self.server.users[session.name] = session # 向服务器的用户列表添加会话的用户名

def remove(self, session):

self.broadcast('用户 离开聊天室!\r\n') # 广播用户离开房间的信息

Room.remove(self, session) # 重载超类的方法

def do_say(self, session, line): # 定义say命令的处理方法

self.broadcast(session.name + ':' + line + '\r\n') # 广播用户的发言信息

def do_look(self, session, line): # 定义look命令的处理方法

session.push('当前房间在线用户\r\n'.encode('GBK'))

session.push('----------------------\r\n'.encode('GBK'))

for user in self.sessions: # 遍历所有会话

session.push('{}\r\n'.format(user.name).encode('GBK')) # 推送每个会话中的用户名信息

def do_who(self, session, line): # 定义who命令的处理方法

session.push('当前服务器在线用户\r\n'.encode('GBK'))

session.push('----------------------\r\n'.encode('GBK'))

for user in self.server.users: # 遍历服务器用户列表

session.push('{}\r\n'.format(user).encode('GBK')) # 推送服务器中所有的用户名信息

注意,上方代码中的“server.users”是服务器的用户列表,users需要在ChatServer类中定义。

6、CheckOutRoom类

还是以开房举例(虽然我很少干这个事),退房时,我们不能一走了之,也要到登记处的房间进行登记。

在这个类中,我们只需要定义进入登记房间的方法。

示例代码:

class CheckOutRoom(Room):

def add(self, session): # 重写进入房间的方法

try:

del self.server.users[session.name] # 从服务器用户列表中移除当前会话的用户名

except KeyError:

pass

7、ChatSession类

在上一篇教程中我们编写过这个类,现在,我们再给它添加一些新的功能。

在这个类中,我们要对会话进行处理,包括会话进入房间、会话中的数据处理以及会话关闭的处理。

新增部分,在示例代码中添加了注释,大家还是通过注释进行理解。

class ChatSession(async_chat):

def __init__(self, server, sock):

async_chat.__init__(self, sock)

self.server = server # 保存传入服务器对象

self.set_terminator('\r\n'.encode())

self.data = []

self.name = None # 创建会话的用户名变量

self.enter(CheckInRoom(server)) # 将当前会话添加到登录的房间

def enter(self, room): # 定义进入房间的方法

try:

current = self.room # 获取当前会话的房间

except AttributeError: # 如果当前会话没有在任何房间

pass

else: # 如果当前会话没有在某个房间

current.remove(self) # 从当前会话所在的房间移除当前会话

self.room = room # 设置当前会话的房间为传入的房间

room.add(self) # 传入的房间添加当前会话

def collect_incoming_data(self, data):

self.data.append(data.decode())

def found_terminator(self):

line = ''.join(self.data)

self.data = []

try:

self.room.handle(self, line) # 处理客户端发来的数据

except EndSession: # 捕获结束会话异常

self.handle_close() # 调用关闭连接的处理方法

def handle_close(self): # 重写关闭连接的处理方法

async_chat.handle_close(self) # 重载超类的方法

self.enter(CheckOutRoom(self.server)) # 将当前会话进入退出登记的房间

8、ChatSever类

在上一篇教程中我们页编写过这个类,同样,我们在这里要添加和修改的内容。

具体添加的内容,大家看代码中的注释。

示例代码:

class ChatSever(dispatcher):

def __init__(self, name, port):

dispatcher.__init__(self)

self.create_socket()

self.set_reuse_addr()

self.bind(('', port))

self.listen(5)

self.name = name # 保存传入的聊天室服务器名称

self.users = {} # 初始化服务器用户列表

self.main_room = ChatRoom(self) # 设定主聊天房间

def handle_accept(self):

conn, addr = self.accept()

ChatSession(self, conn) # 将服务器对象和连接对象传入会话对象

以上就是所有类的编写。

最后,我们就可以运行服务器和Telnet客户端进行测试了。

示例代码:(启动服务器)

if __name__ == '__main__':

name = '心动'

port = 6666

server = ChatSever(name, port)

try:

asyncore.loop()

except KeyboardInterrupt:

print('服务器已关闭!')

python聊天室详细教程_Python基础教程书籍案例:在线聊天室(虚拟茶话会)【下】...相关推荐

  1. python可以这样学读后感_Python基础教程的读后感10篇

    <Python基础教程>是一本由Magnus Lie Hetland著作,人民邮电出版社出版的平装图书,本书定价:69.00元,页数:471,文章吧小编精心整理的一些读者的读后感,希望对大 ...

  2. python中的正则表达式语法_Python基础教程之正则表达式基本语法以及re模块

    什么是正则: 正则表达式是可以匹配文本片段的模式. 正则表达式'Python'可以匹配'python' 正则是个很牛逼的东西,python中当然也不会缺少. 所以今天的Python就跟大家一起讨论一下 ...

  3. python自定义函数详解_python基础教程之自定义函数介绍

    函数最重要的目的是方便我们重复使用相同的一段程序. 将一些操作隶属于一个函数,以后你想实现相同的操作的时候,只用调用函数名就可以,而不需要重复敲所有的语句. 函数的定义 首先,我们要定义一个函数, 以 ...

  4. python雷达图详解_Python基础教程 - matplotlib实现雷达图和柱状图

    原标题:Python基础教程 - matplotlib实现雷达图和柱状图 Python基础教程记录 - 使用matplotlib实现雷达图和柱状图. 注:主要是设置add_subplot(133),分 ...

  5. spyderpython使用教程_Python基础教程—Spyder简介和Python版本

    选择Spyder作为Python开发的集成开发环境(安装教程参加文末链接).它综合了开发工具的高级编辑,性能分析,调试和分析功能与数据探索等功能. 1.Spyder工作页面介绍 Spyder的界面设计 ...

  6. python函数的唯一标识_python基础教程Python通用唯一标识符uuid模块使用案例

    1. 背景知识: UUID: 通用唯一标识符 ( Universally Unique Identifier ), 对于所有的UUID它可以保证在空间和时间上的唯一性. 它是通过MAC地址, 时间戳, ...

  7. python基础案例教程_python基础教程 10-11例子如何执行

    展开全部 10. 模块相关 Python的标准安装包包括一组模块,称为标准库(standard library). 10.1 模块 10.1.1 模块是程序 # hello.pyprint " ...

  8. python 类方法 实例方法的区别_python基础教程Python实例方法、类方法、静态方法区别详解...

    1.关于参数的区别 实例方法:定义实例方法是最少有一个形参 ---> 实例对象,通常用 self 类方法:定义类方法的时候最少有一个形参 ---> 类对象,通常用 cls 静态方法:定义静 ...

  9. python xpath定位打印元素_python基础教程:8种selenium元素定位的实现

    前言 selenium是一个非常厉害的爬虫利器,不,简直是神器了,它可以自动的控制浏览器,但是你得告诉浏览器,你想干嘛,爬哪里,这时候就要用到元素定位了,在HTML中都有着不同的标签和属性,selen ...

  10. python环境变量的配置_python基础教程-第一讲-带你进入python的世界

    python是一门非常流行的语言,在前段时间网上流传的地产大佬潘石屹宣布要开始学习Python编程,这着实让python又火了一把,但确实反映出python的火热程度 .在2019年12月的世界编程语 ...

最新文章

  1. python构建cnn图片匹配_tensorflow搭建cnn人脸识别训练+识别代码(python)
  2. 设计模式七大原则(C++描述)
  3. js中报错“Maximum call stack size exceeded“解决方法
  4. 【Android 安装包优化】资源混淆 ( 资源混淆效果 | APK 构建流程简介 | 资源 ID 组成 )
  5. 遭遇“长租杀熟”,95后蜗居的长租公寓未来也是“扑朔迷离”?
  6. Vector的一种实现(一)
  7. JS过滤表单数据中的特殊字符
  8. 【算法竞赛学习】资金流入流出预测-挑战Baseline_特征工程
  9. 支付宝披露小微商户降费进展:半年减免近50亿
  10. PHP获取当前页面的完整URL
  11. 160508Junit使用
  12. Django 06模板语言的复用
  13. python3基本语法规则,Python中的语法规则
  14. h3c交换机配置nat_H3C-NAT 命令配置
  15. 计算机作业word电子杂志,怎么用Word文档制作电子杂志目录
  16. 我的IT之路------来自黑马程序员
  17. 服务器esn和文件esn不匹配,如何获取服务器ESN
  18. 中学计算机课体育课被占用,那些年被占用的体育课
  19. GitHub 克隆加速
  20. VMware虚拟机没有网

热门文章

  1. 转载:详解C中volatile关键字
  2. 在 Cloud 9 中搭建和运行 Go
  3. Meclipse乱码解决方案
  4. 互联网创业的重重风险
  5. 08Oracle Database 完整性约束
  6. pathway一些网站
  7. sql处理null值
  8. MySQL数据库权限操作指南
  9. sqlldr导入数据(以PostgreSqlOracle为例)
  10. scala学习笔记(四)样本类与模式匹配