Python实现基于C/S架构的聊天室功能详解
作者:juggd 发布时间:2022-06-14 11:43:38
本文实例讲述了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 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:
三、项目实战(客户端)
完成了服务器端后,就需要实现客户端了,这里客户端连接服务器使用了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程序设计有所帮助。
来源:https://www.cnblogs.com/rrxc/p/4530626.html
猜你喜欢
- TeX 排版中文字体嵌入问题,兼谈不拘小节的中文字体设计原文:http://yulewang.spaces.live.com/blog/cn
- 做项目的时候,一位同事导数据的时候,不小心把一个表中的数据全都搞重了,也就是说,这个表里所有的记录都有一条重复的。这个表的数据是千万级的,而
- Python2.x使用过程中,中文乱码解决最耳熟能详的方法就是在代码前加上#-*- coding:utf-8 –*-那么为什么需要这么做呢?
- str()将参数转换为字符串类型a = 26print('我有'+str(a)+'个苹果')输出:我有26个
- asp编程中我们经常要处理字符串,比如一个新闻列表,在我们编写asp程序的时候就要考虑到新闻标题的长度不确定性,因为有的文章标题可能很长,可
- Python引入了一个机制:引用计数。引用计数python内部使用引用计数,来保持追踪内存中的对象,Python内部记录了对象有多少个引用,
- 通过exec可以执行动态Python代码,类似Javascript的eval功能;而Python中的eval函数可以计算Python表达式,
- 我们这里试输出一段字符:<%response.write Server.UrlEncode("织梦幻影")%>
- 由于我们分发的python应用可能运行在64位环境,也可能运行在32位环境,所以我们需要为同一套应用代码配置两套打包环境,怎么配置?步骤如下
- 【原文地址】 Tip/Trick: Supporting Full Screen Mode with Silverlight 【原文发表日期
- 这是经常用的一个分页存储过程 希望大家指点不足 代码如下:USE [a6756475746] GO /****** Object
- 1. logging日志的介绍在现实生活中,记录日志非常重要,比如:银行转账时会有转账记录;飞机飞行过程中,会有个黑盒子(飞行数据记录器)记
- 跨浏览器方法function getStyle(elem, cssprop, cssprop2){ if(elem.currentStyle
- 列表生成式语法:[x*x for x in range(0,10)] //列表生成式,这里是中括号//结果 [0, 1, 4, 9, 16,
- eval()函数eval() 函数用来执行一个字符串表达式,并返回表达式的值。语法eval(expression[, globals[, l
- 前段时间,接到一个需求,要求下载某一个网站的视频,然后自己从网上查阅了相关的资料,在这里做一个总结。1. m3u8文件m3u8是苹果公司推出
- Python获取pid和进程名字1、安装psutilpip install psutil如果pip不识别,就进入下载的python目录下面执
- 虽然 prometheus 已有大量可直接使用的 exporter 可供使用,以满足收集不同的监控指标的需要。例如,node exporte
- Dreamweaver MX 2004的强大功能以及更加完善的人性化设置已经深受大家喜爱。在此笔者就谈
- number(<p>,<s>)精度p取值范围1~38有效位s取值范围-84~127最高整数位数=p-ss正数,小数点