用Python开发你的第一款聊天软件

您所在的位置:网站首页 交友网站制作教程 用Python开发你的第一款聊天软件

用Python开发你的第一款聊天软件

2023-03-26 04:13| 来源: 网络整理| 查看: 265

在本实验中,我们将实现一个简单的图形界面聊天系统。我们可以通过图形客户端登录聊天室,并与其他成员进行聊天。

本教程由实验楼120发布在实验楼,完整教程、代码及在线练习地址:Python 实现文字聊天室(更多项目请查看Python学习路径)

一、实验介绍

1、知识点

asyncore 、asynchat模块使用wxPython 图形开发

2、实验环境

python3.5二、原理解析

由于 Python 是一门带 GIL 的语言,所以在 Python 中使用多线程处理IO操作过多的任务并不是很好的选择。同时聊天服务器将同多个 socket 进行通信,所以我们可以基于 asyncore 模块实现聊天服务器。aysncore 模块是一个异步的 socket 处理器,通过使用该模块将大大简化异步编程的难度。asynchat 模块在 asyncore 模块的基础上做了进一步封装,简化了基于文本协议的通信任务的开发难度。

既然要开发聊天程序,那必然需要设计聊天时使用的协议。为了简单起见,我们将要开发的聊天服务器只支持文本协议,通过command message的方式调用相关的操作。比如如果客户端发送以下文本,将执行相应的操作

# 登录操作 login\n # 在聊天室中发表 hello 内容 say hello\n # 查看聊天室在线用户 look\n # 退出登录 logout\n

以上协议流中,login, say, look, logout 就是相关协议代码。

然后使用下面的命令在 /home/shiyanlou/Code 目录下创建我们需要的 server.py 和 client.py 文件:

$ touch ~/Code/server.py $ touch ~/Code/client.py三、服务器类

这里我们首先需要一个聊天服务器类,通过继承 asyncore 的 dispatcher 类来实现,我们编写 server.py 文件:

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)

这里需要补充说明的是,对于 asyncore 和 asynchat 模块来讲,在 python3.6 中,使用 asyncio 模块代替,但是实验环境中我们使用的是 python 3.5 ,也由于 wxPython 对于Linux 下 CPython 的支持,所以我们依然使用 python 3.5。

1、会话类

有了服务器类还需要能维护每个用户的连接会话,这里继承 asynchat 的 async_chat 类来实现,在 server.py 文件中定义,代码如下:

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

2、协议命令解释器

在之前的分析中,我们设计了聊天服务器的协议,我们需要实现协议命令的相应方法,具体来说就是处理用户登录,退出,发消息,查询在线用户的代码。在 server.py 文件中定义,

class CommandHandler: """ 命令处理类 """ def unknown(self, session, cmd): # 响应未知命令 # 通过 aynchat.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)

3、房间

接下来就需要实现聊天室的房间了,这里我们定义了三种房间,分别是用户刚登录时的房间、聊天的房间和退出登录的房间,这三种房间都继承自 CommandHandler,在 server.py 文件中定义,代码如下:

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 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 serve run at '0.0.0.0:{0}'".format(PORT)) asyncore.loop() except KeyboardInterrupt: print("chat server exit")四、登陆窗口

完成了服务器端后,就需要实现客户端了。客户端将基于 wxPython 模块实现。 wxPython 模块是 wxWidgets GUI 工具的 Python 绑定。所以通过 wxPython 模块我们就可以实现 GUI 编程了。同时我们的聊天协议基于文本,所以我们和服务器之间的通信将基于 telnetlib 模块实现。

登录窗口通过继承 wx.Frame 类来实现,编写 client.py 文件,代码如下:

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

1、聊天窗口

聊天窗口中最主要的就是向服务器发消息并接受服务器的消息,这里通过子线程来接收消息,在 client.py 文件中定义,代码如下:

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 = ICANN Verification Required() con = telnetlib.Telnet() LoginFrame(None, -1, title="Login", size=(320, 250)) app.MainLoop()五、执行首先,我们执行 server.py ,如下图所示:这时,我们再打开一个终端,执行 client.py 文件,如下图:输入对应的信息之后,点击 Login ,再次重复上一步骤,使用另一用户名 shiyanlou002登陆,如下图:在最终的示例中,我们可以分别通过 shiyanlou001 和 shiyanlou002 的客户端发送消息,此时,所有的在线用户都可以收到对应的消息。六、小结

最后就可以运行程序进行聊天了,注意需要先启动服务器再启动客户端。这个项目中使用了 asyncore 的 dispatcher 来实现服务器,asynchat 的 asyn_chat 来维护用户的连接会话,用 wxPython 来实现图形界面,用 telnetlib 来连接服务器,在子线程中接收服务器发来的消息,由此一个简单的聊天室程序就完成了。

详细教程在Python 实现文字聊天室更多编程练手项目:全部课程


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3