Python實現(xiàn)基于C/S架構(gòu)的聊天室功能詳解
本文實例講述了Python實現(xiàn)基于C/S架構(gòu)的聊天室功能。分享給大家供大家參考,具體如下:
一、課程介紹
1.簡介
本次項目課是實現(xiàn)簡單聊天室程序的服務(wù)器端和客戶端。
2.知識點
服務(wù)器端涉及到asyncore、asynchat和socket這幾個模塊,客戶端用到了telnetlib、wx、time和thread這幾個模塊。
3.所需環(huán)境
本次課中編寫客戶端需要用到wxPython,它是一個GUI工具包,請先使用下面的命令安裝:
$ sudo apt-get install python-wxtools
密碼為shiyanlou
4.項目效果截圖
登錄窗口

聊天窗口

5.源代碼下載
git clone https://github.com/shiyanlou/pythonchat.git
說明:如果你不理解上述代碼的下載方式或者下載后在環(huán)境中找不到代碼,可以點擊查看這里
二、項目實戰(zhàn)(服務(wù)器端)
1.服務(wù)器類
首先需要一個聊天服務(wù)器,這里繼承asyncore的dispatcher類來實現(xiàn),代碼如下
class ChatServer(dispatcher):
"""
聊天服務(wù)器
"""
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.會話類
有了服務(wù)器類還需要能維護(hù)每個用戶的連接會話,這里繼承asynchat的async_chat類來實現(xiàn),代碼如下:
class ChatSession(async_chat):
"""
負(fù)責(zé)和單用戶通信
"""
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):
'從當(dāng)前房間移除自身,然后添加到指定房間'
try:
cur = self.room
except AttributeError:
pass
else:
cur.remove(self)
self.room = room
room.add(self)
def collect_incoming_data(self, data):
'接受客戶端的數(shù)據(jù)'
self.data.append(data)
def found_terminator(self):
'當(dāng)客戶端的一條數(shù)據(jù)結(jié)束時的處理'
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.命令解釋器
現(xiàn)在就需要一個命令解釋器能夠解釋用戶的命令,例如登錄、查詢在線用戶和發(fā)消息等,代碼如下:
class CommandHandler:
"""
命令處理類
"""
def unknown(self, session, cmd):
'響應(yīng)未知命令'
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.房間
接下來就需要實現(xiàn)聊天室的房間了,這里我們定義了三種房間,分別是用戶剛登錄時的房間、聊天的房間和退出登錄的房間,這三種房間都有一個公共的父類,代碼如下:
class Room(CommandHandler):
"""
包含多個用戶的環(huán)境,負(fù)責(zé)基本的命令處理和廣播
"""
def __init__(self, server):
self.server = server
self.sessions = []
def add(self, session):
'一個用戶進(jìn)入房間'
self.sessions.append(session)
def remove(self, session):
'一個用戶離開房間'
self.sessions.remove(session)
def broadcast(self, line):
'向所有的用戶發(fā)送指定消息'
for session in self.sessions:
session.push(line)
def do_logout(self, session, line):
'退出房間'
raise EndSession
class LoginRoom(Room):
"""
剛登錄的用戶的房間
"""
def add(self, session):
'用戶連接成功的回應(yīng)'
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):
'廣播新用戶進(jìn)入'
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):
'客戶端發(fā)送消息'
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):
'從服務(wù)器中移除'
try:
del self.server.users[session.name]
except KeyError:
pass
5.服務(wù)器端完整代碼
#!/usr/bin/python
# encoding: utf-8
from asyncore import dispatcher
from asynchat import async_chat
import socket, asyncore
PORT = 6666 #端口
class EndSession(Exception):
"""
自定義會話結(jié)束時的異常
"""
pass
class CommandHandler:
"""
命令處理類
"""
def unknown(self, session, cmd):
'響應(yīng)未知命令'
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):
"""
包含多個用戶的環(huán)境,負(fù)責(zé)基本的命令處理和廣播
"""
def __init__(self, server):
self.server = server
self.sessions = []
def add(self, session):
'一個用戶進(jìn)入房間'
self.sessions.append(session)
def remove(self, session):
'一個用戶離開房間'
self.sessions.remove(session)
def broadcast(self, line):
'向所有的用戶發(fā)送指定消息'
for session in self.sessions:
session.push(line)
def do_logout(self, session, line):
'退出房間'
raise EndSession
class LoginRoom(Room):
"""
剛登錄的用戶的房間
"""
def add(self, session):
'用戶連接成功的回應(yīng)'
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):
'廣播新用戶進(jìn)入'
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):
'客戶端發(fā)送消息'
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):
'從服務(wù)器中移除'
try:
del self.server.users[session.name]
except KeyError:
pass
class ChatSession(async_chat):
"""
負(fù)責(zé)和單用戶通信
"""
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):
'從當(dāng)前房間移除自身,然后添加到指定房間'
try:
cur = self.room
except AttributeError:
pass
else:
cur.remove(self)
self.room = room
room.add(self)
def collect_incoming_data(self, data):
'接受客戶端的數(shù)據(jù)'
self.data.append(data)
def found_terminator(self):
'當(dāng)客戶端的一條數(shù)據(jù)結(jié)束時的處理'
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):
"""
聊天服務(wù)器
"""
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
三、項目實戰(zhàn)(客戶端)
完成了服務(wù)器端后,就需要實現(xiàn)客戶端了,這里客戶端連接服務(wù)器使用了telnetlib模塊。
1.登錄窗口
這里的圖形界面包選擇了wxPython,前面有安裝說明,登錄窗口通過繼承wx.Frame類來實現(xiàn),代碼如下:
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.聊天窗口
聊天窗口中最主要的就是向服務(wù)器發(fā)消息并接受服務(wù)器的消息,這里通過子線程來接受,代碼如下:
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):
'發(fā)送消息'
message = str(self.message.GetLineText(0)).strip()
if message != '':
con.write('say ' + message + '\n')
self.message.Clear()
def lookUsers(self, event):
'查看當(dāng)前在線用戶'
con.write('look\n')
def close(self, event):
'關(guān)閉窗口'
con.write('logout\n')
con.close()
self.Close()
def receive(self):
'接受服務(wù)器的消息'
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):
'發(fā)送消息'
message = str(self.message.GetLineText(0)).strip()
if message != '':
con.write('say ' + message + '\n')
self.message.Clear()
def lookUsers(self, event):
'查看當(dāng)前在線用戶'
con.write('look\n')
def close(self, event):
'關(guān)閉窗口'
con.write('logout\n')
con.close()
self.Close()
def receive(self):
'接受服務(wù)器的消息'
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()
四、小結(jié)
最后就可以運行程序進(jìn)行聊天了,注意需要先啟動服務(wù)器再啟動客戶端。這個項目中使用了asyncore的dispatcher來實現(xiàn)服務(wù)器,asynchat的asyn_chat來維護(hù)用戶的連接會話,用wxPython來實現(xiàn)圖形界面,用telnetlib來連接服務(wù)器,在子線程中接受服務(wù)器發(fā)來的消息,由此一個簡單的聊天室程序就完成了。
更多關(guān)于Python相關(guān)內(nèi)容可查看本站專題:《Python Socket編程技巧總結(jié)》、《Python數(shù)據(jù)結(jié)構(gòu)與算法教程》、《Python函數(shù)使用技巧總結(jié)》、《Python字符串操作技巧匯總》、《Python入門與進(jìn)階經(jīng)典教程》及《Python文件與目錄操作技巧匯總》
希望本文所述對大家Python程序設(shè)計有所幫助。
相關(guān)文章
python整合ffmpeg實現(xiàn)視頻文件的批量轉(zhuǎn)換
python/Matplotlib繪制復(fù)變函數(shù)圖像教程

