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


Posted in Python onJuly 07, 2018

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

一、课程介绍

1.简介

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

2.知识点

服务器端涉及到asyncoreasynchatsocket这几个模块,客户端用到了telnetlibwxtimethread这几个模块。

3.所需环境

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

$ sudo apt-get install python-wxtools

密码为shiyanlou

4.项目效果截图

登录窗口

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

聊天窗口

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

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 EndSession
class 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-8
from asyncore import dispatcher
from asynchat import async_chat
import socket, asyncore
PORT = 6666 #端口
class EndSession(Exception):
  """
  自定义会话结束时的异常
  """
  pass
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)
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 EndSession
class 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
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))
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-8
import wx
import telnetlib
from time import sleep
import 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 != '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自动化测试实例解析
Sep 28 Python
python实现log日志的示例代码
Apr 28 Python
Python使用min、max函数查找二维数据矩阵中最小、最大值的方法
May 15 Python
python 定义给定初值或长度的list方法
Jun 23 Python
Python常见MongoDB数据库操作实例总结
Jul 24 Python
python3实现指定目录下文件sha256及文件大小统计
Feb 25 Python
详解利用python+opencv识别图片中的圆形(霍夫变换)
Jul 01 Python
python函数的万能参数传参详解
Jul 26 Python
seek引发的python文件读写的问题及解决
Jul 26 Python
基于django ManyToMany 使用的注意事项详解
Aug 09 Python
Python基于smtplib模块发送邮件代码实例
May 29 Python
图解Python中深浅copy(通俗易懂)
Sep 03 Python
Python实现的txt文件去重功能示例
Jul 07 #Python
Django 多语言教程的实现(i18n)
Jul 07 #Python
python利用requests库进行接口测试的方法详解
Jul 06 #Python
python生成密码字典的方法
Jul 06 #Python
Python 3.x 判断 dict 是否包含某键值的实例讲解
Jul 06 #Python
使用python中的in ,not in来检查元素是不是在列表中的方法
Jul 06 #Python
python 实现将字典dict、列表list中的中文正常显示方法
Jul 06 #Python
You might like
星际争霸任务指南——神族
2020/03/04 星际争霸
mysql建立外键
2006/11/25 PHP
php 数组动态添加实现代码(最土团购系统的价格排序)
2011/12/30 PHP
PHP 过滤页面中的BOM(实现代码)
2013/06/29 PHP
jquery+php实现导出datatables插件数据到excel的方法
2015/07/06 PHP
php json_encode与json_decode详解及实例
2016/12/13 PHP
laravel 输出最后执行sql 附:whereIn的使用方法
2019/10/10 PHP
JavaScript 验证浏览器是否支持javascript的方法小结
2009/05/17 Javascript
javascript调试说明
2010/06/07 Javascript
JS定时关闭窗口的实例
2013/05/22 Javascript
javascript禁制后退键(Backspace)实例代码
2013/11/15 Javascript
Javascript学习笔记之函数篇(四):arguments 对象
2014/11/23 Javascript
jQuery中:password选择器用法实例
2015/01/03 Javascript
jQuery实现ajax的叠加和停止(终止ajax请求)
2016/08/08 Javascript
JSON对象 详解及实例代码
2016/10/18 Javascript
Vue中父组件向子组件通信的方法
2017/07/11 Javascript
Mint UI组件库CheckList使用及踩坑总结
2018/12/20 Javascript
如何利用ES6进行Promise封装总结
2019/02/11 Javascript
JS写滑稽笑脸运动效果
2020/05/28 Javascript
vscode 插件开发 + vue的操作方法
2020/06/05 Javascript
js实现菜单跳转效果
2020/12/11 Javascript
Pyramid添加Middleware的方法实例
2013/11/27 Python
Python编程之属性和方法实例详解
2015/05/19 Python
Python编程实现蚁群算法详解
2017/11/13 Python
Python创建一个空的dataframe,并循环赋值的方法
2018/11/08 Python
django框架cookie和session用法实例详解
2019/12/10 Python
浅谈在django中使用redirect重定向数据传输的问题
2020/03/13 Python
python实现图片转字符画的完整代码
2021/02/21 Python
phonegap常用事件总结(必看篇)
2017/03/31 HTML / CSS
世界上最大的在线学习和教学市场:Udemy
2017/11/08 全球购物
杭州-飞时达软件有限公司.net笔面试
2012/04/28 面试题
新闻学毕业生自荐信
2013/11/15 职场文书
国际商务英语专业求职信
2014/07/08 职场文书
2015年个人自我剖析材料
2014/12/29 职场文书
三好学生竞选稿范文
2019/08/21 职场文书
Python实现滑雪小游戏
2021/09/25 Python