英文捉鸡点 这里 python
源码中能够看到其实本质上就对 select 以及 socket 的进一步封装git
Python的asyncore模块提供了以异步的方式写入套接字服务的客户端和服务器的基础结构。github
英捉鸡 , 这里网络
该模块创建在asyncore
基础架构之上,简化了异步客户端和服务器,而且更容易处理元素被任意字符串终止或者长度可变的协议。session
本次项目开发所须要用到的模块和接口架构
用于建立 server_socket 套接字app
总体操做相似于 socket 的使用异步
import asynchat import asyncore # 定义端口 PORT = 6666 # 定义结束异常类 class EndSession(Exception): pass class ChatServer(asyncore.dispatcher): """ 聊天服务器 """ def __init__(self, port): asyncore.dispatcher.__init__(self) # 建立socket self.create_socket() # 设置 socket 为可重用 self.set_reuse_addr() # 监听端口 self.bind(('', port)) self.listen(5) self.users = {} self.main_room = ChatRoom(self) def handle_accept(self): conn, addr = self.accept() ChatSession(self, conn)
用于维护聊天室
重写了 collect_incoming_data 用于数据存放
以及 found_terminator 来进行结束标志
以及 handle_close 来进行结束操做
class ChatSession(asynchat.async_chat): """ 负责和客户端通讯 """ def __init__(self, server, sock): asynchat.async_chat.__init__(self, sock) self.server = server self.set_terminator(b'\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.decode("utf-8")) def found_terminator(self): # 当客户端的一条数据结束时的处理 line = ''.join(self.data) self.data = [] try: self.room.handle(self, line.encode("utf-8")) # 退出聊天室的处理 except EndSession: self.handle_close() def handle_close(self): # 当 session 关闭时,将进入 LogoutRoom asynchat.async_chat.handle_close(self) self.enter(LogoutRoom(self.server))
用于自定义协议, 相似于开发 httpserver 的时候的 协议格式定制处理
咱们预设了4种命令分别由 其同名函数进行分发处理
class CommandHandler: """ 命令处理类 """ def unknown(self, session, cmd): # 响应未知命令 # 经过 asynchat.async_chat.push 方法发送消息 session.push(('Unknown command {} \n'.format(cmd)).encode("utf-8")) def handle(self, session, line): line = line.decode() # 命令处理 if not line.strip(): return parts = line.split(' ', 1) cmd = parts[0] try: line = parts[1].strip() except IndexError: line = '' # 经过协议代码执行相应的方法 method = getattr(self, 'do_' + cmd, None) try: method(session, line) except TypeError: self.unknown(session, cmd)
Room 类继承了 CommandHandler 能够处理聊天室中的命令处理
主要用于维护一个存有全部用户的 sessions 列表以及 广播发送信息处理
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): # 向全部的用户发送指定消息 # 使用 asynchat.asyn_chat.push 方法发送数据 for session in self.sessions: session.push(line) def do_logout(self, session, line): # 退出房间 raise EndSession
用户登陆后须要广播一条信息 xxx 加入聊天室
class LoginRoom(Room): """ 处理登陆用户 """ def add(self, session): # 用户链接成功的回应 Room.add(self, session) # 使用 asynchat.asyn_chat.push 方法发送数据 session.push(b'Connect Success') def do_login(self, session, line): # 用户登陆逻辑 name = line.strip() # 获取用户名称 if not name: session.push(b'UserName Empty') # 检查是否有同名用户 elif name in self.server.users: session.push(b'UserName Exist') # 用户名检查成功后,进入主聊天室 else: session.name = name session.enter(self.server.main_room)
class LogoutRoom(Room): """ 处理退出用户 """ def add(self, session): # 从服务器中移除 try: del self.server.users[session.name] except KeyError: pass
class ChatRoom(Room): """ 聊天用的房间 """ def add(self, session): # 广播新用户进入 session.push(b'Login Success') self.broadcast((session.name + ' has entered the room.\n').encode("utf-8")) 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.\n').encode("utf-8")) def do_say(self, session, line): # 客户端发送消息 self.broadcast((session.name + ': ' + line + '\n').encode("utf-8")) def do_look(self, session, line): # 查看在线用户 session.push(b'Online Users:\n') for other in self.sessions: session.push((other.name + '\n').encode("utf-8"))
if __name__ == '__main__': s = ChatServer(PORT) try: print("chat server run at '0.0.0.0:{0}'".format(PORT)) asyncore.loop() except KeyboardInterrupt: print("chat server exit")
import wx import telnetlib from time import sleep import _thread as thread class LoginFrame(wx.Frame): """ 登陆窗口 """ def __init__(self, parent, id, title, size): # 初始化,添加控件并绑定事件 wx.Frame.__init__(self, parent, id, title) self.SetSize(size) self.Center() self.serverAddressLabel = wx.StaticText(self, label="Server Address", pos=(10, 50), size=(120, 25)) self.userNameLabel = wx.StaticText(self, label="UserName", pos=(40, 100), size=(120, 25)) self.serverAddress = wx.TextCtrl(self, pos=(120, 47), size=(150, 25)) self.userName = wx.TextCtrl(self, pos=(120, 97), size=(150, 25)) self.loginButton = wx.Button(self, label='Login', pos=(80, 145), size=(130, 30)) # 绑定登陆方法 self.loginButton.Bind(wx.EVT_BUTTON, self.login) self.Show() def login(self, event): # 登陆处理 try: serverAddress = self.serverAddress.GetLineText(0).split(':') con.open(serverAddress[0], port=int(serverAddress[1]), timeout=10) response = con.read_some() if response != b'Connect Success': self.showDialog('Error', 'Connect Fail!', (200, 100)) return con.write(('login ' + str(self.userName.GetLineText(0)) + '\n').encode("utf-8")) response = con.read_some() if response == b'UserName Empty': self.showDialog('Error', 'UserName Empty!', (200, 100)) elif response == b'UserName Exist': self.showDialog('Error', 'UserName Exist!', (200, 100)) else: self.Close() ChatFrame(None, 2, title='ShiYanLou Chat Client', size=(500, 400)) except Exception: self.showDialog('Error', 'Connect Fail!', (95, 20)) def showDialog(self, title, content, size): # 显示错误信息对话框 dialog = wx.Dialog(self, title=title, size=size) dialog.Center() wx.StaticText(dialog, label=content) dialog.ShowModal()
class ChatFrame(wx.Frame): """ 聊天窗口 """ def __init__(self, parent, id, title, size): # 初始化,添加控件并绑定事件 wx.Frame.__init__(self, parent, id, title) self.SetSize(size) self.Center() self.chatFrame = wx.TextCtrl(self, pos=(5, 5), size=(490, 310), style=wx.TE_MULTILINE | wx.TE_READONLY) self.message = wx.TextCtrl(self, pos=(5, 320), size=(300, 25)) self.sendButton = wx.Button(self, label="Send", pos=(310, 320), size=(58, 25)) self.usersButton = wx.Button(self, label="Users", pos=(373, 320), size=(58, 25)) self.closeButton = wx.Button(self, label="Close", pos=(436, 320), size=(58, 25)) # 发送按钮绑定发送消息方法 self.sendButton.Bind(wx.EVT_BUTTON, self.send) # Users按钮绑定获取在线用户数量方法 self.usersButton.Bind(wx.EVT_BUTTON, self.lookUsers) # 关闭按钮绑定关闭方法 self.closeButton.Bind(wx.EVT_BUTTON, self.close) thread.start_new_thread(self.receive, ()) self.Show() def send(self, event): # 发送消息 message = str(self.message.GetLineText(0)).strip() if message != '': con.write(('say ' + message + '\n').encode("utf-8")) self.message.Clear() def lookUsers(self, event): # 查看当前在线用户 con.write(b'look\n') def close(self, event): # 关闭窗口 con.write(b'logout\n') con.close() self.Close() def receive(self): # 接受服务器的消息 while True: sleep(0.6) result = con.read_very_eager() if result != '': self.chatFrame.AppendText(result)
if __name__ == '__main__': app = wx.App() con = telnetlib.Telnet() LoginFrame(None, -1, title="Login", size=(320, 250)) app.MainLoop()
初始状态
用户操做
To build a functioning async_chat subclass your input methods collect_incoming_data() and found_terminator() must handle the data that the channel receives asynchronously. The methods are described below