python 实现聊天室
所用模块
asyncore
官方介绍, 源码
英文捉鸡点 这里
源码中可以看到其实本质上就对 select 以及 socket 的进一步封装
简单说明
Python的asyncore模块提供了以异步的方式写入套接字服务的客户端和服务器的基础结构。
主要包括
- asyncore.loop(…) - 用于循环监听网络事件。loop()函数负责检测一个字典,字典中保存dispatcher的实例。
- asyncore.dispatcher类 - 一个底层套接字对象的简单封装。这个类有少数由异步循环调用的,用来事件处理的函数。
- dispatcher类中的writable()和readable()在检测到一个socket可以写入或者数据到达的时候被调用,并返回一个bool值,决定是否调用handle_read或者handle_write。
- asyncore.dispatcher_with_send类 - 一个 dispatcher的子类,添加了简单的缓冲输出能力,对简单的客户端很有用。
可用方法
- handle_read():当socket有可读的数据的时候执行这个方法,可读的数据的判定条件就是看方法readable()返回为True还是False。即readable()返回为True的时候执行该方法。
- handle_write():当socket有可写的数据的时候执行这个方法,可写的数据的判定条件就是看方法writable()返回为True还是False。即writable()返回为True的时候执行该方法。
- handle_expt():当socket通信过程中出现OOB异常的时候执行该方法。
- handle_connect():当有客户端连接的时候,执行该方法进行处理。
- handle_close():可连接关闭的时候执行该方法。
- handle_error():当通信过程中出现异常并且没有在其他的地方进行处理的时候执行该方法。
- handle_accept():当作为server socket监听的时候,有客户端连接的时候,利用这个方法进行处理。
- readable():缓冲区是否有可读数据。
- writable():缓冲区是否有可写数据。
- create_socket(family, type):创建一个socket连接。
- connect(address):连接一个socket server。
- send(data):发送数据。
- recv(buffer_size):收取数据到内存缓冲中。
- listen(backlog):server socket开始监听。
- bind(address):server socket绑定某个地址和端口。
- accept():当有客户端连接的时候,执行该方法接受客户端连接。
- close():关闭socket。
- asyncore.loop([timeout[, use_poll[, map[, count]]]])
- 进入轮询循环直到所有打开的通道已被关闭或计数通过。
- 所有的参数都是可选的。
- count参数默认为None,只有当所有通道都被关闭时循环才会终止。
- timeout参数设置为select()或poll()调用设置超时,以秒为单位,默认为30秒。
- use_poll参数,如果为true ,则表示 poll()优先于select(),默认值为False。
- map是包含监控的channel的字典。channel关闭时会从map中删除。不指定map时会使用全局map。
- Channel(asyncore.dispatcher , asynchat.async_chat和其子类的实例)可以自由地混合在map上)。
- asyncore.dispatcher_with_send
- dispatcher的子类,增加了简单的缓冲输出,对于简单的客户端有用。
- 详细资料参考:asynchat.async_chat。
- class asyncore.file_dispatcher
- 封装了文件描述符或文件对象及映射参数(可选)供poll()和loop()函数使用的文件分发器。
- 它提供了文件对象或其他具备fileno()方法的对象,调用fileno()并传递到file_wrapper构造函数。
- 可用于UNIX。
- class asyncore.file_wrapper
- 接收整数文件描述符并调用os.dup()复制句柄,这样原句柄可以关闭,而文件包装器不受影响。
- 该类封装了大量方法以模拟socket给file_dispatcher类使用。
- 可用于UNIX。
asynchat
官方介绍, 源码
英捉鸡 , 这里
简单说明
该模块建立在asyncore
基础架构之上,简化了异步客户端和服务器,并且更容易处理元素被任意字符串终止或者长度可变的协议。
主要包括
- asynchat.async_chat类 - 这个类是asyncore.dispatcher的抽象子类。一般使用其collect_incoming_data()和found_terminator()方法。
- collect_incoming_data() - 接收数据。
- found_terminator() - 当输入数据流符合由 set_terminator() 设置的终止条件时被调用。
- set_terminator() - 设置终止条件。
- push() - 向通道压入数据以确保其传输。
聊天室开发
接口
本次项目开发所需要用到的模块和接口
asyncore
- dispacher
- loop
- handle_read
- handle_write
asynchat
- collect_incoming_data
- set_terminator
- tound_terminator
- push
- handle_close
流程
- 用户连接
- 登记用户
- 建立会话
- 处理用户消息
聊天室代码剖析
服务端
ChatServer 类 - 套接字处理
用于创建 server_socket 套接字
整体操作类似于 socket 的使用
import asynchat import asyncore# 定义端口 PORT = 6666# 定义结束异常类 class EndSession(Exception):passclass 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)
ChatSession 类 - 会话处理
用于维护聊天室
重写了 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 = serverself.set_terminator(b'\n')self.data = []self.name = Noneself.enter(LoginRoom(server))def enter(self, room):# 从当前房间移除自身,然后添加到指定房间try:cur = self.roomexcept AttributeError:passelse:cur.remove(self)self.room = roomroom.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))
CommandHandler 类 - 命令处理
用于自定义协议, 类似于开发 httpserver 的时候的 协议格式定制处理
我们预设了4种命令分别由 其同名函数进行分发处理
- do_login 登录
- de_logout 登出
- do_say 发送消息
- do_look 查看在线用户
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():returnparts = 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 类 - 初始 聊天室基类
Room 类继承了 CommandHandler 可以处理聊天室中的命令处理
主要用于维护一个存有所有用户的 sessions 列表以及 广播发送信息处理
class Room(CommandHandler):"""包含多个用户的环境,负责基本的命令处理和广播"""def __init__(self, server):self.server = serverself.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
LoginRoom 类 - 用户登录处理
用户登录后需要广播一条信息 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 = namesession.enter(self.server.main_room)
Loginout 类 - 退出聊天室处理
class LogoutRoom(Room):"""处理退出用户"""def add(self, session):# 从服务器中移除try:del self.server.users[session.name]except KeyError:pass
ChatRoom 类 - 聊天处理
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] = sessionRoom.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"))
mian - 主函数处理
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 threadclass 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))returncon.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()
流程梳理
初始状态
- socket 创建后 handle_accept 执行来调用了 ChatSession ,
- ChatSession 的 初始化方法中 执行了 enter 方法需要 LoginRoom 的实例化作为参数
- LoginRoom 继承自 Room , 且 没有自己定义 初始化方法因此, 利用 Room 进行初始化
- Room 初始化方法中创建了一个 sessions 列表, 此列表用于维护 用户会话
- enter 方法中执行了一个 add 方法, LoginRoom 和 其基类中的 Room 中都有 add 方法
- 根据 python 面向对象的定义, 执行的是 LoginRoom 中的 add , 此 add 方法中又再次执行了一个 Room.add
- 最终还是执行到了 Room 中的 add 方法, 即往 sessions 列表中加入了这个会话. 以上设计的是初始化方法
用户操作
- 初始的接口程序经由 found_terminator 进行发起 ( 官方解释如下 )
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
- 然后又此方法分发在 CommandHandler 类中进行字符串的分解以及反射分别到 do_ 开头的4个方法进行分发执行
- do_login 相关的验证后, 分流到 ChatRoom 中进行相关的 do_say / do_look 操作
- do_say 经由 广播 ( Room.broadcast ) 进行想的操作
- do_logout 直接退出
- do_look 查看当前所有用户
转载于:https://www.cnblogs.com/shijieli/p/10662419.html
python 实现聊天室相关推荐
- Python在线聊天室(windows)
Python在线聊天室(windows) 1.:准备系统参数配置模块 (settings.py) #服务器地址 HOST = 'localhost' #服务器端口 PORT = 9999 #服务器管道 ...
- 基于Python的聊天室
基于Python的聊天室 文章目录 基于Python的聊天室 一.引言 1.1 背景和意义 1.2 系统要实现的功能 1.2.1 用户登录 1.2.2 群发消息 1.2.3 一对一聊天 1.2.4 发 ...
- 基于python的聊天室_Python实现文字聊天室
你是否想过用所学的Python开发一个图形界面的聊天室程序啊? 像这样的: image 如果你想开发这样一个有点怀旧风格的聊天程序,那么可以接着看: 要开发这个聊天程序,你需要具备以下知识点: asy ...
- Python实现聊天室全双工通信
学校的作业,用TCPSocket实现聊天室,课程中的只是简单的一对一对话,网上有用IO多路复用select的,有多线程的. 多线程的想过,服务器为每一个客户机开一个线程,很好实现但开销太大,我当初设计 ...
- 如何在基于python的聊天室中实现表情接收功能
注:本聊天室涉及TCP.SNMP协议 发送表情功能要点主要有两部分:表情按钮点击事件.字典存放表情. 表情按钮点击事件:发送表情时,参数是发的表情图标记,发送后将按钮销毁. 本次选用四个表情,每一个表 ...
- python qq聊天室
1. 群聊聊天室 功能: 类似QQ群功能 有人进入聊天室树要输入姓名,姓名不能重复 有人进入聊天室时,其他人会收到通知: xxx 进入聊天室 一个人发消息,其他人会收到: xxx: xxxxxx 有人 ...
- Python在线聊天室
基于Django&Websocket实现在线聊天室 痛点 痛点解决方案 轮询 长轮询 Websocket Django配置Websocket 前端连接Websockt 痛点 服务端(后端)需要 ...
- python基于udp的网络聊天室再用tkinter显示_Python实现网络聊天室的示例代码(支持多人聊天与私聊)...
实验名称: 网络聊天室 功能: i. 掌握利用Socket进行编程的技术 ii. 掌握多线程技术,保证双方可以同时发送 iii. 建立聊天工具 iv. 可以和单人聊天 v. 可以和多个人同时进行聊天 ...
- 基于Python Tkiner、thread与socket实现的简单多人聊天室,在Python中创建TCP服务器与客户端进行通信
基于Python Tkiner.thread与socket实现的简单多人聊天室,在Python中创建TCP服务器与客户端进行通信 完整代码下载地址:基于Python Tkiner.thread与soc ...
最新文章
- 量子计算机真随机数,量子真随机数发生器研究取得进展
- 美多商城之订单(我的订单)
- js和html以及css的区别,html、css、js中的区别与关系
- 邓海建:让网约车成为智慧城市的“老司机”
- 总量迈过90万亿元关口 中国经济交出稳健答卷
- JavaScript函数的调用
- Vim 上手指南【基础、中级】
- yii2笔记: 单元测试
- 数据结构之基环树——骑士,Island,旅行加强版,Number of Simple Paths,Traffic Network in Numazu,Card Game
- 在linux 5上配置戴尔MD3220i
- 4. time datetime 时间模块
- div里嵌套了img底部会有白块问题和图片一像素问题解决
- AngularJS 之 ng-model
- python—装饰器@lru_cache在递归中的使用
- html5扫雷代码,使用js开发网页版 扫雷(附代码详解)
- matlab数字图像处理系统
- Linux命令:查看服务器IP地址
- 浙江省乡村快递寄件数据分析-快递100百递指数
- 北航计算机博士后,北京航空航天大学博士后待遇
- SpringBoot整合JDBC的Druid数据源