所用模块

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

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

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():
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 类 - 初始 聊天室基类

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

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 = name
session.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] = 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"))

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

流程梳理

初始状态

  • 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  查看当前所有用户

python 实现聊天室的更多相关文章

  1. python socket 聊天室

    socket 发送的时候,使用的是全双工的形式,不是半双工的形式.全双工就是类似于电话,可以一直通信.并且,在发送后,如果又接受数据,那么在这个接受到数据之前,整个过程是不会停止的.会进行堵塞,堵塞就 ...

  2. Python socket聊天室程序

    http://blog.csdn.net/calling_wisdom/article/details/42524745 今天用python的socket模块实现了一个聊天室的程序 虽然功能比较简单, ...

  3. Python 简单聊天室

    #coding=utf-8 from socket import * from threading import Thread import time udpSocket = socket(AF_IN ...

  4. 基于select的python聊天室程序

    python网络编程具体参考<python select网络编程详细介绍>. 在python中,select函数是一个对底层操作系统的直接访问的接口.它用来监控sockets.files和 ...

  5. Python Socket 编程——聊天室示例程序

    上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和客户端的代码了解基本的 Python Socket 编程模型.本文再通过一个例子来加强一下对 Socket 编程的 ...

  6. Python聊天室

    小编心语:锵锵锵!各位看官注意了啊,走过路过表错过!上篇博文主要介绍了基于基于Server-Sent Event的简单在线聊天室,相信不管各位是大牛.小牛还是跟小编一样的小白,可能觉得看得不够过瘾,区 ...

  7. python tornado websocket 多聊天室(返回消息给部分连接者)

    python tornado 构建多个聊天室, 多个聊天室之间相互独立, 实现服务器端将消息返回给相应的部分客户端! chatHome.py // 服务器端, 渲染主页 --> 聊天室建立web ...

  8. 小小聊天室 Python实现

    相对于Java方式的聊天室,Python同样可以做得到.而且可以做的更加的优雅.想必少了那么多的各种流的Python Socket,你一定会喜欢的. 至于知识点相关的内容,这里就不多说了. UDP方式 ...

  9. 如何用Python编写一个聊天室

    一.课程介绍 1.简介 本次项目课是实现简单聊天室程序的服务器端和客户端. 2.知识点 服务器端涉及到asyncore.asynchat和socket这几个模块,客户端用到了telnetlib.wx. ...

随机推荐

  1. 2019年1月份A项目面试纪要

    2019年1月份A项目面试纪要 本周二(1月22号),笔者接到了A项目的电话面试.这个面试来自A项目的客户,客户的后勤模块的几个顾问组成阵容强大的面试官团队.参加这个面试,让笔者感触良多,自己虽然在S ...

  2. 【Dojo 1.x】笔记7 配置对象dojoConfig的内容1:has属性、加载器的属性

    说完了出身,即出身自dojo/_base/目录下的config模块,那就要好好讲讲这对象有什么可以写的属性了. 1. has属性 官方说是用于更好的特征检测的,具体有什么用现在还不得知. 例如: &l ...

  3. Belgrade Azure 2019-2-11活动感悟

    这是<国外线下技术俱乐部建设>系列文章之一.   活动网址:https://www.meetup.com/Azure-UG-Srbija/events/258673179/ 活动内容:Az ...

  4. Python+ITchart实现微信机器人对指定的朋友和群自动回复

    这里我主要用了3个机器人,可以切换. 1.图灵机器人  (傻的不行,一直在问别人问题,没有限制) http://www.tuling123.com 2.showApi上的图灵机器人 (感觉最聪明,可以 ...

  5. python模块(os,sys,hashlib,collections)

    列出目录下所有文件 os.listdir('dirname'):列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式返回. 创建文件夹: os.mkdir('文件夹')    创建文件夹 os ...

  6. c/c++ 多线程 绕过mutex的保护

    多线程 绕过mutex的保护 mutex,能够解决线程安全的问题,但它不是万能的.下面的例子虽然使用了mutex,但是恶意注入了一个外部函数,导致把被mutex保护的双向链表,让一个外部的指针指向了, ...

  7. iOS证书配置与管理

    证书: 证书: 命名 特点 团队管理 开发证书 iOS Development 不与App ID对应 表示拥有开发应用的资格 一般只需一个,通过导出p12文件,分发给其他电脑安装: 生产证书 iOS ...

  8. cesium加载纽约市3dtiles模型

    const tileset = new Cesium.Cesium3DTileset({ url: '../../assets/data/NewYork/tileset.json' }); viewe ...

  9. 周末班:Python基础之模块

    什么是模块 什么是模块? 常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀. 但其实import加载的模块分为四个通用类别: 1 使用python编写 ...

  10. Win10系统如何在防火墙里开放端口

    Win10系统如何在防火墙里开放端口(下面傻瓜式教学) 然后怎么做呢?????? 下一步.下一步.下一步.下一步.下一步.下一步.下一步.下一步.下一步.下一步......... 随便起个名字 KO