侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

Python实现基于C/S架构的聊天室功能详解

Python  /  管理员 发布于 7年前   193

本文实例讲述了Python实现基于C/S架构的聊天室功能。分享给大家供大家参考,具体如下:

一、课程介绍

1.简介

本次项目课是实现简单聊天室程序的服务器端和客户端。

2.知识点

服务器端涉及到asyncore、asynchat和socket这几个模块,客户端用到了telnetlib、wx、time和thread这几个模块。

3.所需环境

本次课中编写客户端需要用到wxPython,它是一个GUI工具包,请先使用下面的命令安装:

$ sudo apt-get install python-wxtools

密码为shiyanlou

4.项目效果截图

登录窗口

聊天窗口

5.源代码下载

git clone https://github.com/shiyanlou/pythonchat.git

说明:如果你不理解上述代码的下载方式或者下载后在环境中找不到代码,可以点击查看这里

二、项目实战(服务器端)

1.服务器类

首先需要一个聊天服务器,这里继承asyncore的dispatcher类来实现,代码如下

class ChatServer(dispatcher):  """  聊天服务器  """  def __init__(self, port):    dispatcher.__init__(self)    self.create_socket(socket.AF_INET, socket.SOCK_STREAM)    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)

2.会话类

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

class ChatSession(async_chat):  """  负责和单用户通信  """  def __init__(self, server, sock):    async_chat.__init__(self, sock)    self.server = server    self.set_terminator('\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)  def found_terminator(self):    '当客户端的一条数据结束时的处理'    line = ''.join(self.data)    self.data = []    try:      self.room.handle(self, line)    except EndSession:      self.handle_close()  def handle_close(self):    async_chat.handle_close(self)    self.enter(LogoutRoom(self.server))

3.命令解释器

现在就需要一个命令解释器能够解释用户的命令,例如登录、查询在线用户和发消息等,代码如下:

class CommandHandler:  """  命令处理类  """  def unknown(self, session, cmd):    '响应未知命令'    session.push('Unknown command: %s\n' % cmd)  def handle(self, session, line):    '命令处理'    if not line.strip():      return    parts = line.split(' ', 1)    cmd = parts[0]    try:      line = parts[1].strip()    except IndexError:      line = ''    meth = getattr(self, 'do_' + cmd, None)    try:      meth(session, line)    except TypeError:      self.unknown(session, cmd)

4.房间

接下来就需要实现聊天室的房间了,这里我们定义了三种房间,分别是用户刚登录时的房间、聊天的房间和退出登录的房间,这三种房间都有一个公共的父类,代码如下:

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):    '向所有的用户发送指定消息'    for session in self.sessions:      session.push(line)  def do_logout(self, session, line):    '退出房间'    raise EndSessionclass LoginRoom(Room):  """  刚登录的用户的房间  """  def add(self, session):    '用户连接成功的回应'    Room.add(self, session)    session.push('Connect Success')  def do_login(self, session, line):    '登录命令处理'    name = line.strip()    if not name:      session.push('UserName Empty')    elif name in self.server.users:      session.push('UserName Exist')    else:      session.name = name      session.enter(self.server.main_room)class ChatRoom(Room):  """  聊天用的房间  """  def add(self, session):    '广播新用户进入'    session.push('Login Success')    self.broadcast(session.name + ' has entered the room.\n')    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')  def do_say(self, session, line):    '客户端发送消息'    self.broadcast(session.name + ': ' + line + '\n')  def do_look(self, session, line):    '查看在线用户'    session.push('Online Users:\n')    for other in self.sessions:      session.push(other.name + '\n')class LogoutRoom(Room):  """  用户退出时的房间  """  def add(self, session):    '从服务器中移除'    try:      del self.server.users[session.name]    except KeyError:      pass

5.服务器端完整代码

#!/usr/bin/python# encoding: utf-8from asyncore import dispatcherfrom asynchat import async_chatimport socket, asyncorePORT = 6666 #端口class EndSession(Exception):  """  自定义会话结束时的异常  """  passclass CommandHandler:  """  命令处理类  """  def unknown(self, session, cmd):    '响应未知命令'    session.push('Unknown command: %s\n' % cmd)  def handle(self, session, line):    '命令处理'    if not line.strip():      return    parts = line.split(' ', 1)    cmd = parts[0]    try:      line = parts[1].strip()    except IndexError:      line = ''    meth = getattr(self, 'do_' + cmd, None)    try:      meth(session, line)    except TypeError:      self.unknown(session, cmd)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):    '向所有的用户发送指定消息'    for session in self.sessions:      session.push(line)  def do_logout(self, session, line):    '退出房间'    raise EndSessionclass LoginRoom(Room):  """  刚登录的用户的房间  """  def add(self, session):    '用户连接成功的回应'    Room.add(self, session)    session.push('Connect Success')  def do_login(self, session, line):    '登录命令处理'    name = line.strip()    if not name:      session.push('UserName Empty')    elif name in self.server.users:      session.push('UserName Exist')    else:      session.name = name      session.enter(self.server.main_room)class ChatRoom(Room):  """  聊天用的房间  """  def add(self, session):    '广播新用户进入'    session.push('Login Success')    self.broadcast(session.name + ' has entered the room.\n')    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')  def do_say(self, session, line):    '客户端发送消息'    self.broadcast(session.name + ': ' + line + '\n')  def do_look(self, session, line):    '查看在线用户'    session.push('Online Users:\n')    for other in self.sessions:      session.push(other.name + '\n')class LogoutRoom(Room):  """  用户退出时的房间  """  def add(self, session):    '从服务器中移除'    try:      del self.server.users[session.name]    except KeyError:      passclass ChatSession(async_chat):  """  负责和单用户通信  """  def __init__(self, server, sock):    async_chat.__init__(self, sock)    self.server = server    self.set_terminator('\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)  def found_terminator(self):    '当客户端的一条数据结束时的处理'    line = ''.join(self.data)    self.data = []    try:      self.room.handle(self, line)    except EndSession:      self.handle_close()  def handle_close(self):    async_chat.handle_close(self)    self.enter(LogoutRoom(self.server))class ChatServer(dispatcher):  """  聊天服务器  """  def __init__(self, port):    dispatcher.__init__(self)    self.create_socket(socket.AF_INET, socket.SOCK_STREAM)    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)if __name__ == '__main__':  s = ChatServer(PORT)  try:    asyncore.loop()  except KeyboardInterrupt:    print

三、项目实战(客户端)

完成了服务器端后,就需要实现客户端了,这里客户端连接服务器使用了telnetlib模块。

1.登录窗口

这里的图形界面包选择了wxPython,前面有安装说明,登录窗口通过继承wx.Frame类来实现,代码如下:

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 != 'Connect Success':        self.showDialog('Error', 'Connect Fail!', (95, 20))        return      con.write('login ' + str(self.userName.GetLineText(0)) + '\n')      response = con.read_some()      if response == 'UserName Empty':        self.showDialog('Error', 'UserName Empty!', (135, 20))      elif response == 'UserName Exist':        self.showDialog('Error', 'UserName Exist!', (135, 20))      else:        self.Close()        ChatFrame(None, -2, title = 'ShiYanLou Chat Client', size = (500, 350))    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.聊天窗口

聊天窗口中最主要的就是向服务器发消息并接受服务器的消息,这里通过子线程来接受,代码如下:

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)    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')      self.message.Clear()  def lookUsers(self, event):    '查看当前在线用户'    con.write('look\n')  def close(self, event):    '关闭窗口'    con.write('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)

3.客户端完整代码

#!/usr/bin/python# encoding: utf-8import wximport telnetlibfrom time import sleepimport 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 != 'Connect Success':        self.showDialog('Error', 'Connect Fail!', (95, 20))        return      con.write('login ' + str(self.userName.GetLineText(0)) + '\n')      response = con.read_some()      if response == 'UserName Empty':        self.showDialog('Error', 'UserName Empty!', (135, 20))      elif response == 'UserName Exist':        self.showDialog('Error', 'UserName Exist!', (135, 20))      else:        self.Close()        ChatFrame(None, -2, title = 'ShiYanLou Chat Client', size = (500, 350))    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)    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')      self.message.Clear()  def lookUsers(self, event):    '查看当前在线用户'    con.write('look\n')  def close(self, event):    '关闭窗口'    con.write('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 = (280, 200))  app.MainLoop()

四、小结

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

更多关于Python相关内容可查看本站专题:《Python Socket编程技巧总结》、《Python数据结构与算法教程》、《Python函数使用技巧总结》、《Python字符串操作技巧汇总》、《Python入门与进阶经典教程》及《Python文件与目录操作技巧汇总》

希望本文所述对大家Python程序设计有所帮助。


  • 上一条:
    朴素贝叶斯分类算法原理与Python实现与使用方法案例
    下一条:
    Python定时任务sched模块用法示例
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客