Simple Chat Application for Python
一、知识点介绍:
asyncore 、asynchat模块使用
由于 Python 是一门带 GIL 的语言,所以在 Python 中使用多线程处理IO操作过多的任务并不是很好的选择。同时聊天服务器将同多个 socket 进行通信,所以我们可以基于 asyncore 模块实现聊天服务器。aysncore 模块是一个异步的 socket 处理器,通过使用该模块将大大简化异步编程的难度。asynchat 模块在 asyncore 模块的基础上做了进一步封装,简化了基于文本协议的通信任务的开发难度。
- 异步socket处理器-asyncore
python 3.6 版后已移除: 请使用asyncio模块
替代
asyncore模块提供了以异步的方式写入套接字服务的客户端和服务器的基础架构。
模块主要包括:
asyncore.loop(…) #用于循环监听网络事件。loop()函数负责检测一个字典,字典中保存dispatcher的实例,这个字典被称为channel。
asyncore.dispatcher.__init__(self) #一个底层套接字对象的简单封装,相当于一个socket对象。此类有少数由异步循环调用的,用来事件处理的函数。
dispatcher类中的writable()和readable()在检测到一个socket可以写入或者数据到达的时候被调用,并返回一个bool值,决定是否调用handle_read或者handle_write,也就是说,一旦检测到可读或可写,就调用handle_read/handle_write。打开asyncore.py可以看到,dispatcher类中定义的方法writable和readable的定义相当的简单:
asyncore.dispatcher_with_send类 #一个 dispatcher的子类,添加了简单的缓冲输出能力,对简单的客户端很有用。
每次创建一个dispatcher对象,都可以看做我们需要处理的一个socket(可以TCP也可以是UDP,甚至是一些不常用的),都会把自己加入到一个默认的dict里面去(当然也可以自己指定channel)。当对象被加入到channel中的时候,socket的行为都已经被定义好,程序只需要调用loop(),一切功能就实现了。
- 异步 socket 指令/响应 处理器-asynchat
此模块建立在asyncore模块的基础上,简化了异步客户端和服务器,并使处理元素由任意字符串终止或长度可变的协议变得更加容易。 asynchat定义了子类的抽象类async_chat,提供了collect_incoming_data()和found_terminator()方法的实现。它使用与asyncore相同的异步循环,并且两种通道类型asyncore.dispatcher和asynchat.async_chat可以在通道中自由混合。通常,asyncore.dispatcher服务器通道在接收到传入的连接请求时会生成新的asynchat.async_chat通道对象。
asynchat.async_chat.__init__(self, sock) #此类是asyncore.dispatcher的抽象子类。一般使用其collect_incoming_data()和found_terminator()方法。
async_chat.collect_incoming_data() #接收数据。
async_chat.found_terminator() #当输入数据流符合由 set_terminator() 设置的终止条件时被调用。
async_chat.set_terminator() #设置终止条件。
async_chat.push() #向通道压入数据以确保其传输。
二、代码实现
服务器端
1.服务器类
这里我们首先需要一个聊天服务器类,通过继承 asyncore 的 dispatcher 类来实现
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(('0.0.0.0', port))
self.listen(5)
self.users = {}
self.main_room = ChatRoom(self)
def handle_accept(self):
conn, addr = self.accept()
ChatSession(self, conn)
2.会话类
有了服务器类还需要能维护每个用户的连接会话,这里通过继承 asynchat 的 async_chat 类来实现。
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))
3.协议命令解释器
我们需要实现协议命令的相应方法,具体来说就是处理用户登录,退出,发消息,查询在线用户的代码。
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)
4. 聊天室
接下来就需要实现聊天室的房间了,这里我们定义了三种房间,分别是用户刚登录时的房间、聊天的房间和退出登录的房间,这三种房间都继承自 CommandHandler,代码如下:
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 server run at '0.0.0.0{0}'".format(PORT))
asyncore.loop()
except KeyboardInterrupt:
print("chat server exit")
客户端
1.登录窗口
完成了服务器端后,就需要实现客户端了。客户端将基于 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='H4ck3R 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()
2.聊天窗口
聊天窗口中最主要的就是向服务器发消息并接受服务器的消息,这里通过子线程来接收消息,继续在 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 = wx.App()
con = telnetlib.Telnet()
LoginFrame(None, -1, title="Login", size=(320, 250))
app.MainLoop()
三、运行
首先,我们执行 server.py ,如下图所示:
这时,我们再另一台机器(我的是虚拟机)打开一个终端,执行 client.py 文件,输入服务端的地址、端口,及自己在聊天室的ID,点击 Login ,即可进入
此时,点击Users,将显示处于聊天室的当前用户:
- 同样,在另外一台机器中重复上述操作:
以下是模拟的两个用户的对话(当然,也可以更多用户),但只要在线的用户都可以收到对话消息。此时,再次点击Users,显示处于聊天室的两个用户:
Simple Chat Application for Python的更多相关文章
- ASP.NET AJAX web chat application
ASP.NET AJAX web chat application The project illustrates how to design a simple AJAX web chat appli ...
- openresty+websocket+redis simple chat
openresty 很早就支持websocket了,但是早期的版本cosocket是单工的,处理起来比较麻烦参见邮件列表讨论 websocket chat,后来的版本cosocket是双全工的,就可以 ...
- Python创建命令行应用的工具 tools for command line application in python
工具1:Docopt 地址:http://docopt.org/ 这个工具是根据模块的文档注释来确定参数的.注释分为两部分:Usage, option. \``` Usage: naval_fate ...
- webSocket开发chat application过程
本次使用websocket开发chat的功能已经接近尾声,等到压力测试结束之后就可以上线了.在此记录一下整个开发过程. ---------------------------------------- ...
- A WCF-WPF Chat Application
http://www.codeproject.com/Articles/25261/A-WCF-WPF-Chat-Application
- Writing a Simple YARN Application 从hadoop生态抽出yarn ,单独使用yarn
Apache Hadoop 2.9.1 – Hadoop: Writing YARN Applications https://hadoop.apache.org/docs/current/hadoo ...
- Simple BeamSearch Codes for Python
Code from: https://github.com/SeitaroShinagawa/simple_beamsearch probs = [[[],[0.3,0.7]], [[0],[0.1, ...
- Node.js + Web Socket 打造即时聊天程序嗨聊
前端一直是一块充满惊喜的土地,不仅是那些富有创造性的页面,还有那些惊赞的效果及不断推出的新技术.像node.js这样的后端开拓者直接将前端人员的能力扩大到了后端.瞬间就有了一统天下的感觉,来往穿梭于前 ...
- SingalR--demo
原文链接 : http://www.asp.net/signalr/overview/getting-started/tutorial-getting-started-with-signalr-and ...
随机推荐
- echar图柱状图和折线图混合加双侧y轴坐
代码如下: floorSalesBar(){//方法名====这个方法应该放在methods中并在mounted中调用哦 methods let _this = this; let myChart = ...
- redhat7安装
------------恢复内容开始------------ 新建虚拟机向导(自定义) 指定虚拟机安装位置,把他放在固态硬盘提升他的运行速度(不推荐,一般将位置定为非系统盘) 选择系统镜像文件 开机选 ...
- 洛谷P1003 铺地毯 模拟
这一题就是一个很普通的模拟,每次输入的时候存储四个角的值 把四个角的横纵坐标存储在一排.然后在倒序遍历一遍,查找的时候就看所要查找的坐标在不在这个范围内,如果找到了就标记一下再输出,如果没有找到就输出 ...
- Selenium系列(十) - 针对Select下拉框的操作和源码解读
如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...
- 我们是怎么实现gRPC CodeFirst-生成proto
前言: gRPC默认是ProtoFirst的,即先写 proto文件,再生成代码,需要人工维护proto,生成的代码也不友好,所以出现了gRPC CodeFirst,下面来说说我们是怎么实现gRPC ...
- Java网络编程——Socket
网络是连接不同计算机的媒介,不同的计算机依靠网络来互相通信,即传递数据. Java中与网络编程相关的部分主要是Socket(套接字),它作为一种抽象的结构,实现了与通信相关的各类方法,构成一套完整的通 ...
- msys2 mingw64安装
(1)安装msys2 (2)更新\etc\pacman.d\下的源文件 mirrorlist.msys Server = http://repo.msys2.org/msys/$arch/ Serve ...
- 使用条件随机场模型解决文本分类问题(附Python代码)
对深度学习感兴趣,热爱Tensorflow的小伙伴,欢迎关注我们的网站!http://www.tensorflownews.com.我们的公众号:磐创AI. 一. 介绍 世界上每天都在生成数量惊人的文 ...
- 线程间交换数据的Exchanger
作者:Steven1997 链接:https://www.jianshu.com/p/9b59829fb191 来源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处. Exc ...
- ANTLR随笔(二)
安装ANTLR 作者的电脑是MAC的操作系统macOS Catalina 10.15.2. 安装步骤后linux操作的系统的一样, Windows系统大致步骤一样,但是环境变量等配置有差别,作者很久没 ...