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方法用于将行终止对象设为网络协议中通常用作终止符的"\r\n"

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("\r\n")
        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("\r\n")
        self.data=[]
        # 问候用户
         self.push('Welcome to %s \r\n' % 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+'\r\n')

    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\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):
               #响应logout命令
                raise EndSession

class LoginRoom(Room):
         '''
         为刚刚连接上的用户准备的房间
        '''
        def add(self,session):
                Room.add(self,session)
                #当用户进入时,问候他或她
                self.broadcast('Welcome to %s\r\n' % self.server.name)

        def unknown(self, session, cmd):
                #所有未知命令(除了login或者logout外的一起)
                #会导致一个警告
                session.push('Please log in \nUse "login"\r\n')

        def do_login(self, session, line):
                name = line.strip()
               #确保用户输入了名字
                if not name:
                        session.push('Please enter a name\r\n')
                 #确保用户名没有被使用
                elif name in self.server.users:
                        session.push('The name "%s" is taken.\r\n' % name)
                        sessoin.push('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 + ' 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):
#处理say命令,用于广播一个单行,用发言的用户的名字作为前缀 self.broadcast(session.name
+ ': ' + line + '\r\n') def do_look(self, session, line): #处理look命令,该命令用于查看谁在房间里 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): #处理who命令,该命令用于查看谁登陆了 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): #当会话(用户)进入要删除的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('\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) 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

聊天服务器可用命令:
  

 

posted @ 2015-07-28 17:50  zxqstrong  阅读(1106)  评论(0编辑  收藏  举报