基本聊天程式
先來個基本的測試例子:
Main.py
from asyncore import dispatcherimport socket,asyncorePORT = 11223class 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
上面是伺服器段程式,接受到串連之後馬上在伺服器上顯示對方IP,但是不保持串連。用戶端測試的話很簡單,可以自己寫一個socket的串連,也可以為了省事直接本地開啟telnet就行了,開啟telnet的方法是,在開始菜單裡搜尋:
然後確定,之後cmd裡直接telnet就行了。
運行上面的main.py,然後用戶端連結服務端:
Cmd->telnet : open 127.0.0.1 11223
上面就是一個最基本的Python伺服器指令碼流程。接下來實現一個基本的聊天程式用來練手。
實現的準系統(虛擬茶會話) 測試環境python 3.6.0[python基礎教程上的代碼不能直接在3.6.0版本上跑,以下是改過的虛擬茶會話]
功能:
login name 登入房間
logout 退出房間
say XXXX 發言
look 查看同一個房間內的人
who 查看誰登陸了當前伺服器
測試截圖
簡單說下設計結構:
CommandHandler類 : 處理命令的,函數存在則執行,不存在則直接走unknown函數。結合著try和getattr函數python可以直接嘗試去執行一個不確定存不存在的函數。[我一直在想,別的語言要怎麼實現這個東西,是建立虛擬工廠。對了想起來了,乾脆就建立一些函數,然後把函數名字格式化封裝在一個void指標容器裡的了。函數格式化要統一]
Room類:表示一個房間,裡面有一些對房間人[連結]資料結構的增加刪除操作,同時還有相關廣播函數,用於把訊息發給所有人。
LoginRoom類,裡面有成功登陸房間,登陸房間失敗以及登陸操作,比如問候登陸者,同時通知別人有人登陸等細節。
LogoutRoom類,登出房間類,使用者登出房間時候進行的一些資料結構處理。
ChatRoom類,聊天室類,主要就是封裝了一些功能函數。比如say look who等等。
ChatSession,ChatServer 類基本的伺服器程式需要的,分別繼承async_chat和dispatcher,處理一些伺服器參數,以及重載設定一些處理函數等。
詳細代碼如下[注意本代碼測試於python3.6.0]
#!/usr/bin/env python3__author__ = 'tcstory'from asyncore import dispatcherfrom asynchat import async_chatimport socket, asyncore PORT=5005NAME='TestChat' class EndSession(Exception): pass class CommandHandler: ''' Simple command handler similar to cmd.Cmd from the standard library ''' def unknown(self, session, cmd): 'Respond to an unknown command' session.push('Unkonw command: {0}\r\n'.format(cmd).encode()) def handle(self, session, line): 'Handle a received line from a given session' if not 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 one or more users(sessions).it takes care of basic command handling and broadcasting. ''' def __init__(self,server): self.server=server self.sessions=[] def add(self,session): 'A session(user) has entered the room' self.sessions.append(session) def remove(self,session): 'A session (user) has left the room' self.sessions.remove(session) def broadcast(self,line): 'Send a line to all sessions in the room' for session in self.sessions: session.push(line.encode()) def do_logout(self,session,line): 'Respond to the logout command' raise EndSession class LoginRoom(Room): ''' A room meant for a single person who has just connected ''' def add(self,session): Room.add(self,session) #When a user enters,greet him/her self.broadcast('Welcome to {0}\r\n'.format(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'.encode()) def do_login(self,session,line): name=line.strip() #Make sure the user has entered a name if not name: session.push('Please enter a name\r\n'.encode()) #Make sure that the name isn't in use elif name in self.server.users: session.push('The name {0} is taken.\r\n'.format(name).encode()) session.push('Please try again.\r\n'.encode()) else: #The name is OK,os 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 can chat with the others in the room ''' def add(self,session): #Notify everyone that a new user has entered self.broadcast('{0} has entered the room.\r\n'.format(session.name)) 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('{0} has left the room.\r\n'.format(session.name)) def do_say(self,session,line): self.broadcast(('{0}: '+line+'\r\n').format(session.name)) def do_look(self,session,line): 'Handles the look command,used to see who is in a room' session.push('The following are in this room:\r\n'.encode()) for other in self.sessions: session.push('{0}\r\n'.format(other.name).encode()) def do_who(self,session,line): 'Handles the who command ,used to see who is logged in' session.push('The following are logged in:\r\n'.encode()) for name in self.server.users: session.push('{0}\r\n'.format(name).encode()) class LogoutRoom(Room): ''' A simple room for a single user.Its sole purpose 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 the communication with a single user ''' def __init__(self,server,sock): # async_chat.__init__(self,sock) super().__init__(sock) self.server=server self.set_terminator(b'\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.decode('utf-8')) 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): super().__init__() # 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()