5. Python脚本学习实战笔记五 茶话会

5. Python脚本学习实战笔记五 茶话会

本篇名言:“得得失失平常事;是是非非任自由;恩恩怨怨心不愧;冷冷暖暖我自知;坎坎坷坷人生路;曲曲折折事业梯;凡事不必太在意;愿你一生好运气!别驻足,梦想要不停追逐;别认输,遨过黑夜才有日出;要记住,成功就在下一步;路很苦,汗水是最美的书;尽情欢呼,相约巅峰共舞!人生短暂,不必计较太多得失!成功会被时间掩住光彩;失败会在岁月中淡化!人生最珍贵的不是“得不到”和“已失去”而是现在能把握的幸福!平淡是真!”

                  上篇中的NNTP协议大家一定会感觉太老,不过本篇的项目就有亲切感了。我们来实现一个聊天服务器。可以使用TWISTED框架,当时这不是本篇重点,本篇使用标准块内的asyncore和asynchat模块。

1.  需求

学习网络编程知识。

….

没了,需求这是这个简单明了,好吧~~

2.  工具和准备

asyncore和asynchat模块,asyncore框架可以处理同时连接的多个用户。

                  此外服务端还需要一个端口来提供给客户端连接。这里只要取一个大于1023的端口即可,因为小于1023的话是系统保留的,最好别用。      

3.  初次实现

废话不多,直接开工了这次。

需要两个主类,一个作为聊天服务器,一个用于表示每个聊天会话。

3.1      ChatServer类

ChatServer类继承于asyncore的dispatcher以创建基础的ChatServer类。

一个简单服务程序如下:

fromasyncore import dispatcher

importasyncore

 

class ChatServer(dispatcher):pass

 

s = ChatServer()

asyncore.loop()

这个什么都不能做。能接受链接的话修改如下:

fromasyncore import dispatcher

importsocket, asyncore

 

class ChatServer(dispatcher):

 

   def handle_accept(self):

        conn, addr = self.accept()

        print'Connection attempt from', addr[0]

 

s = ChatServer()

s.create_socket(socket.AF_INET,socket.SOCK_STREAM)

s.bind(('', 5005))

s.listen(5)

asyncore.loop()

 

测试:

直接执行:telnet 127.0.0.1 5005

然后服务端出现如下:

Connectionattempt from 127.0.0.1

 

telnet命令没有,通过如下步骤设置:

1、  打开控制面板

2、  程序和功能

3、  打开或关闭WINDOWS功能

4、  CHECK TELNET 客户端。

TELNET输入如果出现乱码:

                  按下 CTRL+] 即可正常输入。

3.2      ChatSession类

基本的ChatSession类用处不大,在代码实现中为每个连接创建一个dispatcher对象。主要任务是收集来自客户端的数据进行响应,可以使用asynchat模块。

                  为了让asynchat起作用,只要覆盖两个方法即可:collect_incoming_data和found_terminator。

                  collect_incoming_data:在从套接字中读取一些bit文本时调用。

found_terminator:在读取一个结束符时调用。结束符通过set_terminiator方法设置,一般设置为”\r\n”.

带ChatSession类的如下:

fromasyncore import dispatcher

fromasynchat import async_chat

importsocket, asyncore

 

PORT = 5005

 

class ChatSession(async_chat):

 

   def __init__(self,sock):

        async_chat.__init__(self, sock)

        self.set_terminator("\r\n")

        self.data = []

 

   def collect_incoming_data(self,data):

        self.data.append(data)

 

   def found_terminator(self):

        line = ''.join(self.data)

        self.data = []

        #Do something with the line...

        print line

 

class ChatServer(dispatcher):

 

   def __init__(self,port):

        dispatcher.__init__(self)

        self.create_socket(socket.AF_INET,socket.SOCK_STREAM)

        self.set_reuse_addr()

        self.bind(('', port))

        self.listen(5)

        self.sessions = []

 

 

   def handle_accept(self):

        conn, addr = self.accept()

        self.sessions.append(ChatSession(conn))

 

if__name__ == '__main__':

   s = ChatServer(PORT)

   try:asyncore.loop()

    except KeyboardInterrupt: print

 

3.3      整合

还需要将用户的发言广播给其他的用户。

                  可以通过在服务器端遍历回话的列表,将发言行写到每一个客户端里面。

                  PS:必须保证在客户单断开连接后,将它从会话列表中移除。通过重写事件处理方法handle_close 来实现这个功能。

代码实现如下:

fromasyncore import dispatcher

fromasynchat import async_chat

importsocket, asyncore

 

PORT = 5005

NAME = 'TestChat'

 

class ChatSession(async_chat):

   """

    A class that takes care of a connectionbetween the server

    and a single user.

    """

   def __init__(self,server, sock):

        #Standard setup tasks:

        async_chat.__init__(self, sock)

        self.server = server

        self.set_terminator("\r\n")

        self.data = []

        #Greet the user:

        self.push('Welcome to %s\r\n' % self.server.name)

 

   def collect_incoming_data(self,data):

        self.data.append(data)

 

   def found_terminator(self):

        """

        If a terminator is found, that meansthat a full

        line has been read. Broadcast it toeveryone.

        """

        line = ''.join(self.data)

        self.data = []

        self.server.broadcast(line)

 

   def handle_close(self):

        async_chat.handle_close(self)

        self.server.disconnect(self)

 

class ChatServer(dispatcher):

   """

    A class that receives connections andspawns individual

    sessions. It also handles broadcasts tothese sessions.

    """

   def __init__(self,port, name):

        #Standard setup tasks

        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.sessions = []

 

   def disconnect(self,session):

        self.sessions.remove(session)

 

   def broadcast(self,line):

        for session in self.sessions:

            session.push(line + '\r\n')

 

   def handle_accept(self):

        conn, addr = self.accept()

        self.sessions.append(ChatSession(self,conn))

 

if__name__ == '__main__':

   s = ChatServer(PORT, NAME)

   try:asyncore.loop()

    except KeyboardInterrupt: print

                  基本实现了聊天功能,不过还存在一些问题和功能的局限性。

4.  重构

针对初次实现,无法判断是谁在讲话,而且缺少命令解释的支持。

 

4.1      基础命令解释

例如输入 say hello

调用如下方法:

do_say(‘hello’)

下面是解释未知命令的方法:

class CommandHandler:

   """

    Simple command handler similar to cmd.Cmdfrom the standard

    library.

    """

 

 

   def unknown(self,session, cmd):

        'Respondto an unknown command'

        session.push('Unknown command: %s\r\n' % cmd)

 

   def handle(self,session, line):

        'Handlea received line from a given session'

        ifnot line.strip():return

        #Split off the command:

        parts = line.split(' ', 1)

        cmd = parts[0]

        try: line = parts[1].strip()

        except IndexError: line = ''

        #Try to find a handler:

        meth = getattr(self, 'do_'+cmd, None)

        try:

            #Assume it's callable:

            meth(session, line)

        except TypeError:

            #If it isn't, respond to the unknown command:

           self.unknown(session, cmd)

 

 

 

 

4.2      房间

每个房间都是一个拥有自定义专名命令的CommandHandler对象。

此外,还能记录哪个用户目前位于聊天室内。

class Room(CommandHandler):

   """

    A generic environment that may contain oneor more users

    (sessions). It takes care of basic commandhandling and

    broadcasting.

    """

 

   def __init__(self,server):

        self.server = server

        self.sessions = []

 

   def add(self,session):

        'Asession (user) has entered the room'

        self.sessions.append(session)

 

   def remove(self,session):

        'Asession (user) has left the room'

        self.sessions.remove(session)

 

   def broadcast(self,line):

        'Senda line to all sessions in the room'

        for session in self.sessions:

            session.push(line)

 

 

   def do_logout(self,session, line):

        'Respondto the logout command'

        raise EndSession

除了基本的add和remove方法之外,broadcast方法会对房间内的所有用户调用push方法。还定义了do_logout方法,引发一个较高级别的处理操作的异常。

4.3      登陆和退出房间

Room子类还应该能表示其他状态。例如登陆和退出房间。

登陆时候打印一个欢迎语,退出时在服务器端删除用户名。

 

 

 

 

4.4      主聊天室

主聊天室覆盖add和remove方法。Add方法会广播用户进入的消息,并将用户名添加到服务端的users字典内。Remove方法广播用户离开的消息。

此外还有3个命令

l  Say命令:广播一个单行,用发言的用户的名字作为前缀

l  Look:查看房间内有哪些用户

l  Who:告诉用户哪些用户登录到了当前的服务器。当扩展到多个房间的时候look和who的命令就会不同了。

 

4.5      新的服务器

相比第一个实现,

l  ChatSession有个enter方法,用于进入新房间

l  ChatSession构造函数使用了LoginRoom

l  Handle_close方法使用了LogoutRoom

l  ChatServer构造函数增加了users字典,并且将名为main_room的ChatRoom增加为自己的特性。

5.  交付

最后实现代码如下:

和C比起来,短短200行代码(还包括空行的)就实现了一个聊天室的程序,是不是很给力?

fromasyncore import dispatcher

fromasynchat import async_chat

importsocket, asyncore

 

PORT = 5005

NAME = 'TestChat'

 

class EndSession(Exception):pass

 

class CommandHandler:

   """

    Simple command handler similar to cmd.Cmdfrom the standard

    library.

    """

 

 

   def unknown(self,session, cmd):

        'Respondto an unknown command'

        session.push('Unknown command: %s\r\n' % cmd)

 

   def handle(self,session, line):

        'Handlea received line from a given session'

        ifnot line.strip():return

        #Split off the command:

        parts = line.split(' ', 1)

        cmd = parts[0]

        try: line = parts[1].strip()

        except IndexError: line = ''

        #Try to find a handler:

        meth = getattr(self, 'do_'+cmd, None)

        try:

            #Assume it's callable:

            meth(session, line)

        except TypeError:

            #If it isn't, respond to the unknown command:

            self.unknown(session, cmd)

 

class Room(CommandHandler):

   """

    A generic environment that may contain oneor more users

    (sessions). It takes care of basic commandhandling and

    broadcasting.

    """

 

   def __init__(self,server):

        self.server = server

        self.sessions = []

 

   def add(self,session):

        'Asession (user) has entered the room'

        self.sessions.append(session)

 

   def remove(self,session):

        'Asession (user) has left the room'

        self.sessions.remove(session)

 

   def broadcast(self,line):

        'Senda line to all sessions in the room'

        for session in self.sessions:

            session.push(line)

 

 

   def do_logout(self,session, line):

        'Respondto the logout command'

        raise EndSession

 

class LoginRoom(Room):

   """

    A room meant for a single person who hasjust connected.

    """

 

   def add(self,session):

        Room.add(self, session)

        #When a user enters, greet him/her:

        self.broadcast('Welcome to %s\r\n' % self.server.name)

 

   def unknown(self,session, cmd):

        #All unknown commands (anything except login or logout)

        #results in a prodding:

        session.push('Please log in\nUse "login <nick>"\r\n')

 

   def do_login(self,session, line):

        name = line.strip()

        #Make sure the user has entered a name:

        ifnot name:

            session.push('Please enter a name\r\n')

        #Make sure that the name isn't in use:

        elif name in self.server.users:

            session.push('The name "%s" is taken.\r\n' %name)

            session.push('Please try again.\r\n')

        else:

            #The name is OK, so it is stored in the session, and

            #the user is moved into the main room.

            session.name = name

            session.enter(self.server.main_room)

 

class ChatRoom(Room):

   """

    A room meant for multiple users who canchat with the others in

    the room.

    """

 

   def add(self,session):

        #Notify everyone that a new user has entered:

        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)

        #Notify everyone that a user has left:

        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):

        'Handlesthe look command, used to see who is in a room'

        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):

        'Handlesthe who command, used to see who is logged in'

        session.push('The following are logged in:\r\n')

        for name in self.server.users:

            session.push(name + '\r\n')

 

class LogoutRoom(Room):

   """

    A simple room for a single user. Its solepurpose is to remove

    the user's name from the server.

    """

 

   def add(self,session):

        #When a session (user) enters the LogoutRoom it is deleted

        try: del self.server.users[session.name]

        except KeyError: pass

 

class ChatSession(async_chat):

   """

    A single session, which takes care of thecommunication with a

    single user.

    """

 

   def __init__(self,server, sock):

        async_chat.__init__(self, sock)

        self.server = server

        self.set_terminator("\r\n")

        self.data = []

        self.name = None

        #All sessions begin in a separate LoginRoom:

        self.enter(LoginRoom(server))

 

 

   def enter(self,room):

        #Remove self from current room and add self to

        #next 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):

   """

    A chat server with a single room.

    """

 

   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

 

聊天服务器可用的命令

命令

可用于

描述

Login name

登陆房间

登陆服务器

Logout     

所有房间

退出服务器

Say statement

聊天室

发言

Look

聊天室

查看同一个房间内的人

who

聊天室

查看登陆到服务器的人

 

 

5.1      关于测试

执行Python脚本后,用telnet登陆即可。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值