虚拟茶话会
在这个项目中,将编写一个聊天服务器,让人们能够通过网络实时地聊天
准备工作
首先,必须由一台连接到网络(如互联网)的计算机,否则别人无法连接到聊天服务器。(可在自己计算机上连接到聊天服务器,但这样做没多大意思。)要连接到聊天服务器,用户必须知道计算机地址(可以是机器名,如foo.bar.baz.com,也可以是IP地址)。另外,用户必须知道聊天服务器使用的端口号。这种端口号可在程序中设置;在代码中,使用的端口号为5005(这里是随便选择的)。
注意: 有些端口号受到限制,必须有管理员权限才能使用。一般而言,使用大于1023的端口号就不会有什么问题。
设计
代码
# -*- coding: utf-8 -*-
from asyncore import dispatcher
from asynchat import async_chat
import socket, asyncore
PORT = 5005
NAME = 'TestChat'
#Exception所有异常的基类
class EndSession(Exception): pass
class CommandHandler():
"""
类似于标准库中的cmd.Cmd简单命令处理程序
"""
def unknown(self, session, cmd):
'响应未知命令'
session.push(b'Unknown command: %s\r\n'%cmd.encode('utf-8'))
def handle(self, session, line):
'处理从给定的会话中接收到的行'
if not line.strip(): return
#分离命令 用空格分割字符串,只分割一次
parts = line.split(' ', 1)
cmd = parts[0]
try: line = parts[1].strip()
except IndexError: line = ''
#试着查找处理程序
meth = getattr(self, 'do_'+cmd, None)
try:
#假定它是可调用的
meth(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, session, line):
'响应logout命令'
raise EndSession
class LoginRoom(Room):
"""
为刚刚连接上的用户准备的房间
"""
def add(self, session):
Room.add(self, session)
#当用户进入时, 问候他或她
self.broadcast(b'Welcome to %s\r\n'%self.server.name.encode('utf-8'))
def unknown(self, session, cmd):
#所有未知命令(除了login或者logout外的一切)会导致一个警告
session.push(b'Please log in \n Use "login <nick>"\r\n')
def do_login(self, session, line):
name = line.strip().encode('utf-8')
#确保用户输入了名字
if not name:
session.push(b'Please enter a name\r\n')
#确保用户名没有被使用
elif name in self.server.users:
session.push(b'The name "{}" is taken.\r\n'.format(name))
session.push(b'Please try again.\r\n')
else:
#名字没问题,所以储存在会话中,并且将用户移动到主聊天室
session.name = name
session.enter(self.server.main_room)
class ChatRoom(Room):
"""
为多用户相互聊天准备的房间。
"""
def add(self, session):
#告诉所有人有新用户进入
self.broadcast(session.name + b' has entered the room.\r\n')
self.server.users[session.name] = session
super().add(session)
def remove(self, session):
Room.remove(self, session)
#告诉所有人有用户离开
self.broadcast(session.name + b' has left the room.\r\n')
def do_say(self, session, line):
'处理say命令,向每个会话发送发言人名称和发言内容'
self.broadcast(session.name+":".encode('utf-8')+line.encode('utf-8') +b"\r\n")
def do_look(self, session, line):
'处理look命令,该命令用于查看谁在房间内'
session.push(b'The follwing are in this room: \r\n')
for other in self.sessions:
session.push(other.name + b'\r\n')
def do_who(self, session, line):
'处理who命令, 该命令用于查看谁登录了'
session.push(b'The following are logged in : \r\n')
for name in self.server.users:
session.push(name + b'\r\n')
class LogoutRoom(Room):
"""
为单用户准备的房间,只用于将用户名从服务器移除.
"""
def add(self, session):
#当前会话(用户)进入要删除的LogoutRoom时
try:del self.server.users[session.name]
except KeyError:pass
class ChatSession(async_chat):
"""
单会话, 负责和单用户通信
"""
def __init__(self, server, sock):
super().__init__(sock)
self.server = server
self.set_terminator(b"\r\n")
self.data = []
self.name = None
#所有的会话都开始于单独的LoginRoom
self.enter(LoginRoom(server))
def enter(self, room):
#从当前房间移除自身(self), 并且将自身添加到下一个房间
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.decode('utf-8'))
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(LogoutRoom(self.server))
class ChatServer(dispatcher):
"""
只有一个房间的聊天服务器
"""
def __init__(self, port, name):
super().__init__()
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
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)
if __name__ == '__main__':
s = ChatServer(PORT, NAME)
try:asyncore.loop()
except KeyboardInterrupt:print(' End run')
运行
打开三个cmd,在电脑属性下找到计算机名称复制下来,在命令行中输入: telnet 机器名 端口号(5005)
三个用户就可以进行通话了
注
本文借鉴了《python基础教程(第3版)》,想了解更详细的内容可以自行搜索查阅