• python基础教程总结15——5 虚拟茶话会


    聊天服务器:

      服务器能接受来自不同用户的多个连接;

      允许用户同时(并行)操作;

      能解释命令,例如,say或者logout;

      容易拓展

    套接字和端口:

      套接字是一种使用标准UNIX文件描述符(file descriptor)与其他程序通信的方式。套接字可以看作是处于不同主机之间的两个程序的通信连接端点。一方面程序将要传输的信息写入套接字中,而另一方面则通过读取套接字内的数据来获得传输的信息。

     


    套接字通信示意图

      所示为使用套接字进行通信的示意图。假设存在两台主机A与B,在主机A中存在进程C,主机B中存在进程D,当进程C需要将数据送到进程D时,首先将数据写到套接字中,而进程D可以通过读取套接字来获得进程C发送的信息。

      在网络中,不同计算机是通过IP地址来区分的,也就是说,要将数据由主机A发送到主机B,只要知道主机B的IP地址就可以确定数据要发送的目的地。但是,在主机A与B中不可能只有进程C和进程D两个进程。主机B在收到主机A发送来的数据后,如何才能确定该数据是发送给进程D?因此,还需要某种标识信息,用于描述网络通信数据发往的进程。TCP/IP协议提出了协议端口的概念,用于标识通信的进程

      当进程与某个端口绑定后,操作系统会将收到的给该端口的数据送往该进程。与文件描述符类似,每个端口都有被称为端口号的整数类型的标识符,该标识符用于区分不同的端口。不同协议可以使用相同的端口号进行数据传输。例如,TCP使用了344的端口号,UDP同样可以使用344端口号进行数据传输。

      端口号为一个16位的无符号整数,其取值范围为0~65535低于256的端口被作为系统的保留端口号,主要用于系统进程的通信,不在这一范围的端口号被称为自由端口号,可以由进程自由使用。

    python  asyncore 

      该模块是事件驱动(event-driver)。主要方法是loop(),用于进行网络事件的循环。而类dispatcher用于进行网络交互事件。不能直接Instance,需要通过继承并在__init__中显式的声明。它封装了网络的读写事件,并通过readable()和writable()来进行事件进行控制。

      下面是dispatcher的主要方法,这些都是基于消息的,捕捉到某个事件,就会触发:
    (1)handle_read():Called when the asynchronous loop detects that a read() call on the channel’s socket will succeed.
    (2)handle_write():Called when the asynchronous loop detects that a writable socket can be written
    (3)handle_expt():Called when there is out of band (OOB) data for a socket connection
    (4)handle_connect():Called when the active opener’s socket actually makes a connection
    (5)handle_close():Called when the socket is closed.
    (6)handle_error():Called when an exception is raised and not otherwise handled
    (7)handle_accept():Called on listening channels (passive openers) when a connection can be established with a new remote endpoint that has issued     a connect() call for the local endpoint.
    (8)readable():Called each time around the asynchronous loop to determine whether a channel’s socket should be added to the list on which read events    can occur
    (10)writable():Called each time around the asynchronous loop to determine whether a channel’s socket should be added to the list on which write       events can occur
      下面是每个channel的主要方法:
    (1)create_socket(family, type)
    (2)connect(address)
    (3)send(data)
    (4)recv(buffer_size)
    (5)listen(backlog)
    (6)bind(address)
    (7)accept()
    (8)close()

    1. 初次实现

    1.1 CharServer类

    #可接受连接的服务器
    
    from asyncore import dispatcher
    import asyncore,socket
    
    class ChatServer(dispatcher):
        def handle_accept(self):
            conn.addr=self.accept()
            print 'Connection attempt  from', addr[0]   #客户端的Ip地址
    
    s=ChatServer()
    s.create_socket(socket.AF_INET, socket.SOCK_STREAM)#创建套接字
    s.bind(('',5005))#将服务器绑定到具体的地址(主机名和端口),空字符串(主机名),意味着本地主机(即本地所有接口),端口号为5005
    s.listen(5)  #告诉服务器要监听连接,并且指定5个连接的代办的事物
    asyncore.loop() #启动服务器,循环监听
    #具有清理功能的基本服务器
    
    from asyncore import dispatcher
    import socket, asyncore
    
    PORT=5005
    
    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)
    
        def  handle_accept(self):
            conn, addr=self.accept()
            print 'Connection attempt from', addr[0]
    
    if __name__ =="__main__":
        s=ChatServer(PORT)
        try: asyncore.loop()
        except KeyboardInterrupt: pass

    1.2 CharSession类

      收集来自客服端的数据(文本)并且进行响应;

      async_chat类(位于asynchat模块中):隐藏了大多数基本的套接字读写操作,简单起见,覆盖其中的collect_incoming_data方法(每次从套接字中读取一些bit文本时调用),found_terminator方法(读取一个结束符时调用)

    1)set_terminator方法用于将行终止对象设为网络协议中通常用作终止符的" "

    2)ChatSession对象会将目前读取的数据作为保存为字符串列表data。但读入更多数据时,collect_incoming_data会自动被调用,将新读入的数据追加到列表中。

    3)found_terminiator方法在读到终止对象时被调用

    4)ChatServer保存回话列表

    5)CharServer的handle_accept方法现在创建了新的ChatSession对象,并将其追加到会话列表中

    # 带有ChatSession类的服务器程序
    
    from asyncore import dispatcher
    from asynchat import async_chat
    import  socket,asyncore
    
    PORT=5005
    
    class ChatSession(async_chat):
        def __init__(self,sock):
            async_chat.__init__(self,sock)
            self.set_terminator("
    ")
            self.data=[]
    
        def  collect_incoming_data(self,data):
            self.data.append(data)
        
        def  found_terminator(self):
            line=' '.join(self.data)
            self.data=[]
            #处理该行数据
            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

    1.3 整合

      添加功能:将用户的发言(输入的每一行)广播给其他用户——在服务器端的添加for循环,遍历会话的列表,并且将发言行写到每一个客户端里面。为了能在async_chat对象中写入数据,需要使用push方法。

    from asyncore import dispatcher
    from asynchat import async_chat
    import  socket,asyncore
    
    PORT=5005
    NAME='TestChat'
    
    class ChatSession(async_chat):
        '''
           处理服务器和一个用户之间连接的类
        ''' 
        def __init__(self,server,sock):
            #标准设置任务
            async_chat.__init__(self,server,sock)
            self.server=server
            self.set_terminator("
    ")
            self.data=[]
            # 问候用户
             self.push('Welcome to %s 
    ' % self.server.name)
    
        def  collect_incoming_data(self,data):
            self.data.append(data)
        
        def  found_terminator(self):
           '''
           如果发现一个终止对象,意味着读入了一个完整的行,将其广播给每个人
          '''
            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):
        '''
        接受连接并且产生单个会话的类,还会处理到其他会话的广播
        '''
        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.name=name
            self.sessions=[]
    
        def disconnect(self,session):
            self.sessions.remove(session)
    
        def  broadcast(self,line):
           for session in self.sessions:
               session.push(line+'
    ')
    
        def handle_accept(self):
            conn,addr=self.accept()
            self.sessions.append(ChatSession(self,conn))
    
    if __name__ == '__main__':
        s=ChatServer(PORT)
        try: asyncore.loop()
        except KeyboardInterrupt: print

    2. 改进

    from asyncore import dispatcher
    from asynchat import async_chat
    import socket, asyncore
    
    PORT = 5005
    NAME = 'TestChat'
    
    class EndSession(Exception):pass
    
    class CommandHandler:
            '''
            类似于标准库中cmd.Cmd的简单命令处理程序
            '''
    
            def unknown(self, session, cmd):
                    #响应未知命令
                    session.push('Unknown command: %s
    ' % 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):
                   #响应logout命令
                    raise EndSession
    
    class LoginRoom(Room):
             '''
             为刚刚连接上的用户准备的房间
            '''
            def add(self,session):
                    Room.add(self,session)
                    #当用户进入时,问候他或她
                    self.broadcast('Welcome to %s
    ' % self.server.name)
    
            def unknown(self, session, cmd):
                    #所有未知命令(除了login或者logout外的一起)
                    #会导致一个警告
                    session.push('Please log in 
    Use "login"
    ')
    
            def do_login(self, session, line):
                    name = line.strip()
                   #确保用户输入了名字
                    if not name:
                            session.push('Please enter a name
    ')
                     #确保用户名没有被使用
                    elif name in self.server.users:
                            session.push('The name "%s" is taken.
    ' % name)
                            sessoin.push('Please try again.
    ')
                    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.
    ')
                    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.
    ')
    
            def do_say(self, session, line):
    #处理say命令,用于广播一个单行,用发言的用户的名字作为前缀 self.broadcast(session.name
    + ': ' + line + ' ') def do_look(self, session, line): #处理look命令,该命令用于查看谁在房间里 session.push('The following are in this room: ') for other in self.sessions: session.push(other.name + ' ') def do_who(self, session, line): #处理who命令,该命令用于查看谁登陆了 session.push('The following are logged in: ') for name in self.server.users: session.push(name + ' ') 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): async_chat.__init__(self,sock) self.server = server self.set_terminator(' ') 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) 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.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

    聊天服务器可用命令:
      

  • 相关阅读:
    springboot事物和事物回滚
    MyBatis Sql语句中的转义字符
    使用 vagrant新建Linux虚拟机
    Centos 6.7 安装mongodb
    阿里云windows server 2012 TIME_WAIT CLOSE_WAIT
    使用Eclipse打jar包 包含依赖jar包
    linux crontab定时器
    mysql 存储过程学习笔记
    oracle windows 新建用户授权 导出导入bmp文件
    解决hash冲突的方法
  • 原文地址:https://www.cnblogs.com/zxqstrong/p/4683638.html
Copyright © 2020-2023  润新知