本程序需要windows 的telnet客户端支持,在windows7下telnet客户端是默认关闭的。如何打开它请参考http://jingyan.baidu.com/article/2fb0ba4055e55800f3ec5f7a.html
具体程序如下:
from asyncore import dispatcher
from asynchat import async_chat
import socket, asyncore
PORT = 5005
NAME = 'TestChat'
class EndSession(Exception):
pass
class CommandHandler(object):
def unknown(self, session, cmd):
session.push('Unknown command: %s\r\n' % cmd)
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):
raise EndSession
class LoginRoom(Room):
def add(self, session):
Room.add(self, session)
self.broadcast('Welcome to %s\r\n' % self.server.name)
def unknow(self, session, cmd):
session.push('Plz log in \nUse "login <nick>"\r\n')
def do_login(self, session, line):
name = line.strip()
if not name:
session.push('Plz enter a name\r\n')
elif name in self.server.users:
session.push('The name "%s" is taken.\r\n' % name)
session.push('Plz 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 + ' has entered the room.\r\n')
self.server.users[session.name] = session
Room.add(self, session)
def remove(self, session):
Room.remove(self, session)
self.broadcast(session.name + ' has left the room.\r\n')
def do_say(self, session, line):
self.broadcast(session.name + ': ' + line + '\r\n')
def do_look(self, session, line):
session.push('The following are in this room\r\n')
for other in self.sessions:
session.push(other.name + '\r\n')
def do_who(self, session, line):
session.push('The following are logged in: \r\n')
for name in self.server.users:
session.push(name + '\r\n')
class LogoutRoom(Room):
def add(self, session):
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(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.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):
dispatcher.__init__(self)
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
对于charserver.py程序其构成思路是
ChatServer类(服务器类):负责侦听和连接响应
ChatRoom类(聊天房间类):负责用户管理
ChatSession类(聊天接口类):负责接收客户端发来的程序。
当有连接进入时,chatserver类将建立对应的ChatSession实例,该实例初始化过程包含:
1.初始化一些配置,如聊天语句终止符设立(必须)
2.给某些变变量赋值:包括该会话属于哪个服务器,会话的接收数据缓冲,会话的名字、
然后建立一个LoginRoom的实例,并将本聊天加入到LoginRoom中去。
LoginRoom类继承自Room超类,所以其中包含了一个ChatSession的列表,用于管理包含在本room中的会话。每次有新用户进入,则将其放入sessions列表中;当有用户退出时。则将该ChatSession类移出列表。
当用户login时,程序将调用do_login方法。然后将向ChatSession类传入使用中的ChatRoom实例(该实例在ChatServer类初始化时实例化),ChatSession调用自身的enter方法,将自身的room变量从LoginRoom更新为ChatRoom,同时调用LoginRoom的remove方法,将自己从LoginRoom的用户列表中移出。再调用ChatRoom的add方法,将自身添加到ChatRoom的用户列表中。
当用户logout时,与之类似,程序将调用do_logout方法,然后向ChatSession中传入LogoutRoom实例(调用的同时实例化),ChatSession用自身的enter方法,将其从ChatRoom中退出,然后加入到LogoutRoom中去,然后调用默认的中断连接方法,终止连接。
注意,当用户接入时,不管进入那个room,命令调用过程都是一样的:
在collect_incoming_data方法中尝试调用超类CommandHandler的handle方法,handle方法将命令自动拼接,然后调用对应room中的do_xx方法。如果调用handle时返回的是EndSession异常,那么调用handle_close()方法,终止连接。
此外,调试中发现一个意外:
当LogoutRoom类中的add方法下self.server.users不幸写错为self.server.use时,asyncore会在某不知名时刻报错。似乎在asyncore某些位置会调用这个方法。然后报错的出错点在ChatSession类下的cur.remove()处,说list中无该实例。原因不明。
思考:
当用户从LoginRoom进入ChatRoom中时,原先的LoginRoom实例是否会被回收?
用户logout之后,对应的ChatSession实例是否会被回收?感觉这个是asyncore保证的,但是在pycharm中是否可以进行观察跟踪?