Python基础教程之虚拟茶话会程序分析

本程序需要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中是否可以进行观察跟踪?




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值