python练习四—简单的聊天软件
python最强大的是什么?库支持!!有了强大的库支持,一个简单的聊天软件实现就更简单了,本项目思路如下
# 项目思路 1. 服务器的工作 * 初始化服务器 * 新建一个聊天房间 * 维护一个已链接用户的会话列表 * 维护一个已登录用户的字典,用户名和会话 * 监听端口,接受会话,并启动一个ChatSession处理 2. 会话线程 * 初始化一个接收数据缓冲区 * 处理用户输入的命令,并提醒用户先登录(也就是说目前指处理登陆命令) * 将用户输入的数据加入缓冲区 * 用户一次输入结束后,将缓冲区数据发送,并清空缓冲区 3. 命令分发 * 对用的输入进行分割,并将对应的命令分发到相应的处理函数,如果没有则采用默认处理函数 4. 房间,继承自命令分发类,即:具有处理命令能力的房间 5. LoginRoom,继承自Room,处理登陆相关的命令 * 加入房间,有用户进入房间,欢迎该用户 * 登录命令,登陆成功之后进入房间,将该session加入队列 6. ChatRoom,继承自Room,处理发言、查看所有用户等命令 7. LogoutRoom,继承自Room,处理用户登出,相当于用户输入登出命令之后进入一个房间, 然后抛出异常通知ChatSession关闭线程
代码如下:
#! /usr/bin/env python # -*- coding=utf-8 -*- from asyncore import dispatcher import socket import asyncore from asynchat import async_chat # 服务器监听端口号 PORT = 5005 # 服务器名称 NAME = 'QQ' class EndException(Exception): pass class CommandHandler: ''' 用来处理用户输入简单的命令 ''' def unknown(self, session, cmd): '''处理未知命令''' session.push('Unknown command %s\r\n' %cmd) def handler(self, session, line): ''' 处理命令(命令路由),根据用户输入分析储命令,调用对应的处理程序 ''' if not line.strip(): # 如果输入为空,返回,不做处理 return # 切分命令 print line + '----------' parts = line.split(' ', 1) cmd = parts[0] try: line = parts[1] except IndexError: line = '' method = getattr(self, 'do_' + cmd, None) try: method(session, line) except TypeError: # 如果该方法不存在,说明不能处理该命令,未知命令 self.unknown(session, cmd) class Room(CommandHandler): ''' 房间类,用来处理具体情况的命令 ''' 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) def do_logout(self, sessioni, line): '用户退出房间' raise EndException class LoginRoom(Room): ''' 用户登录房间,处理用户登录房间的一些操作 ''' def add(self, session): '扩展父类方法,在加入房间之后,给用户发送欢迎语' Room.add(self, session) session.push('welcome to %s \r\n' % self.server.name) def unknown(self, session, cmd): '处理未知命令,提示用户输入登录命令' session.push('please log in \r "Use login <nick>" \r\n') def do_login(self, session, line): '处理用户登录命令' name = line.strip() if not name: # 如果没有输入名称,提示用户输入 session.push('Please input a name') elif name in self.server.users: # 如果名称已被占用 session.push('the % is taken \r\n' %name) session.push('Please try again') else: # 如果正常则让用户进入房间 session.name = name session.enter(self.server.main_room) class ChatRoom(Room): ''' 聊天房间,处理用户在房间中聊天的命令 ''' def add(self, session): '用户进入聊天房间的时候,通知其他用户' Room.add(self, session) self.broadcast('%s has enter in the room \r\n' %session.name) self.server.users[session.name] = session def remove(self, session): '提醒其他用户,xx离开房间' Room.remove(self, session) self.broadcast('%s has left the room \r\n' %session.name) def do_say(self, session, line): '用户发言' self.broadcast(session.name + ':' + line + '\r\n') def do_look(self, session, line): '查看房间中所有用户' self.broadcast('the following are in the room: \r\n') for user in self.sessions: self.broadcast(user.name + '\r\n') class LogoutRoom(Room): ''' 用户退出房间,所需要的命令 ''' def do_logout(self, session, line): try: del self.server.users[session.name] except KeyError: pass class ChatSession(async_chat): ''' 具体处理客户端链接, 接受客户端发送来的信息, 广播给所有在线的客户端,关闭连接, 服务器保留所有连接到服务器的会话 ''' def __init__(self, server, sock): '' async_chat.__init__(self, sock) self.server = server self.set_terminator('\r\n') self.data = [] self.name = None # 如果有新用户连接,则先要求登陆,登陆成功以后进入房间 self.enter(LoginRoom(self.server)) def enter(self, room): try: cur = self.room except AttributeError: pass else: cur.remove(self) self.room = room room.add(self) def collect_incoming_data(self, data): self.data.append(data) def found_terminator(self): line = "".join(self.data) self.data = [] try: self.room.handler(self, line) except EndException: self.handle_close() def handle_close(self): async_chat.handle_close(self) self.enter(LogoutRoom(self)) class ChatServer(dispatcher): ''' 服务器,负责接受客户端链接并分配给具体的程序(线程处理) ''' def __init__(self, port, name): '初始化服务器,初始会话列表,初始化用户字典(用户名对应用户session),初始化服务器主房间' dispatcher.__init__(self) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() self.bind(('', port)) self.listen(5) self.sessions = [] self.users = {} self.name = name self.main_room = ChatRoom(self) def handle_accept(self): conn, addr = self.accept() print 'connection attemp from ', addr[0] ChatSession(self, conn) s = ChatServer(PORT, NAME) try: asyncore.loop() except KeyboardInterrupt: pass
其实从第一个练习开始我就开始意识到,书中的引导是逐步的——使用的是迭代式的开发,一开始知识一个简单的例子视线了基本的功能,然后基于简单的列子将代码模块化,逐渐加功能,我也是试着这样做的,但是鉴于篇幅,其他代码就没有贴上来,在完整的代码可以下载
再进行本练习中,有一下基础知识得到巩固:
# 判断正在运行的模块是直接运行还是被import执行的,即: # 直接运行的话__name='__main__' # 如果是import的话就不是__main__ if __name__ == '__main__': # 变量作用域 data = 123 * 在函数内部申明,作用域就是函数内部, * 在类内部申明就是类变量,访问的时候直接data * 在类内部的函数内部申明如:self.data=124,调用的时候只能是self.data,如果使用data,默认查找的是全局变量 * 如果不适用self指明,python默认调用的全局变量 * python变量可以重名,在同一作用于内如果已经定义过,只是进赋值 # 在子类方法内部调用父类方法(如:Room父类,ChatRoom子类) Room.__init__()
完整代码