◆張萍萍 紀志堅
(青島大學自動化與電氣工程學院 山東 266071)
基于Python的聊天軟件的設(shè)計與實現(xiàn)
◆張萍萍 紀志堅
(青島大學自動化與電氣工程學院 山東 266071)
本系統(tǒng)是一個局域網(wǎng)聊天軟件,是以Python語言為基礎(chǔ),結(jié)合數(shù)據(jù)庫技術(shù),多線程編程技術(shù),運用TCP模式的Socket編程技巧實現(xiàn)一個服務(wù)器與多個客戶端互聯(lián),服務(wù)器存儲并轉(zhuǎn)發(fā)客戶端發(fā)來的數(shù)據(jù),從而實現(xiàn)一對一的客戶端之間的通信。服務(wù)器將客戶端發(fā)來的消息進行廣播,客戶端接收服務(wù)器發(fā)送的消息,通過內(nèi)置標志位判斷是否進行接收,從而實現(xiàn)聊天室群聊功能。本論文主要闡述了軟件開發(fā)過程中運用軟件開發(fā)瀑布模型所走過的需求分析,概要設(shè)計,詳細設(shè)計,編碼以及單元測試,集成測試等軟件開發(fā)與測試流程。通過嚴謹?shù)木幋a規(guī)范,全面的版本控制以及細致的缺陷跟蹤力求設(shè)計出的軟件功能完善,界面美觀,性能優(yōu)越。
Python語言;MySQL數(shù)據(jù)庫;多線程編程;TCP協(xié)議;Socket編程
當今主流的聊天工具有很多,例如 QQ、MSN、雅虎通,這些聊天軟件已能滿足用戶聊天的需求。但由于這些軟件的不開源性,對于很多公司企業(yè)來說,為了保證其信息的安全性,都不得不開發(fā)設(shè)計出一款自己的聊天軟件。因此,從當前情況來說,一款安全、實時的聊天軟件不僅能夠保證員工之間的交流,同時也能保證企業(yè)內(nèi)部之間信息交流的安全,這對于一個企業(yè)來說是極其重要的。
基于這種情況,本文設(shè)計開發(fā)出了該聊天軟件,其最大的特點就是能夠動態(tài)、實時的完成信息的傳遞以及能更有效的處理用戶的請求。此外,也因其功能單一而更易于維護和更新。
由于本系統(tǒng)是由更先進的面向?qū)ο缶幊陶Z言Python編寫,程序中大量引用的類庫都具有平臺無關(guān)性,這使得最終系統(tǒng)具有良好的跨平臺特性[1]??梢詫⒎?wù)器端部署在Windows系統(tǒng)下而將客戶端安裝在Linux系統(tǒng)中,反之亦然。
1.1 可行性分析
(1)經(jīng)濟可行性:該聊天工具是一個小型的系統(tǒng),因此就開發(fā)成本來說,只要有一臺電腦,就可以開發(fā)出來,因此實際的成本我們也可以忽略不計。
(2)技術(shù)可行性:該聊天系統(tǒng)采用Python 作為編程語言,利用MySql 作為后臺數(shù)據(jù)庫。首先,對于網(wǎng)絡(luò)編程Python提供了豐富的類庫,使得我們能夠很好的進行網(wǎng)絡(luò)通信。其次,MySQL數(shù)據(jù)庫本身具有體積小、速度快的特點。而該聊天系統(tǒng)的存儲數(shù)據(jù)量不會很大,因此將MySQL 數(shù)據(jù)庫作為后臺數(shù)據(jù)庫是一個不錯的選擇[2]。綜上,該系統(tǒng)在技術(shù)上來說也是可行的。
(3)運行可行性:該系統(tǒng)是個比較小的系統(tǒng),因此對于軟件和硬件的要求都不是很高,現(xiàn)在的電腦基本上都能滿足其要求。運行所需軟件要求:
操作系統(tǒng):Windows Win7/Linux 及以上
Python:Python 2.7及以上
運行所需硬件要求:
CPU :不作要求
內(nèi)存:256M 及以上
1.2 需求分析
(1)服務(wù)器端功能需求:能夠處理用戶發(fā)送的各種請求(聊天信息、傳送文件、添加好友等)并準確的轉(zhuǎn)發(fā)到指定用戶;能夠向數(shù)據(jù)庫注冊用戶信息;能夠向數(shù)據(jù)庫寫入離線消息;
(2)客戶端功能需求:用戶可以登錄;用戶能夠注冊聊天賬號;用戶能夠添加或者刪除好友;用戶能夠發(fā)送文本聊天信息;用戶能夠進行文件傳輸;用戶能夠進行聊天室群聊;
(3)系統(tǒng)用例圖:通過對系統(tǒng)的需求分析,可以識別出系統(tǒng)有兩個參與者,一個是用戶,另一個是系統(tǒng)管理員(在實際的代碼實現(xiàn)中,配置好服務(wù)器端的 IP地址及端口號后服務(wù)器自動響應(yīng)客戶端請求,自動建立連接,關(guān)閉連接無需人為干預)。基于對系統(tǒng)的分析分別繪制出如圖1,圖2所示的用戶請求和系統(tǒng)處理用戶請求用例圖[3]。
圖1 用戶請求服務(wù)用例圖
圖2 系統(tǒng)處理用戶請求用例
系統(tǒng)處理用戶請求用例圖說明:
(1)Regeist:處理用戶注冊請求。
(2)Login:處理用戶登錄請求。
(3)addFriend:處理用戶添加好友請求。
(4)removeFriend:處理用戶刪除好友請求。
(5)textChat:處理用戶文本聊天請求。
(6)fileTrans:處理用戶文件傳輸請求。
(7)roomChat:處理用戶聊天室聊天請求。
(8)check user:用戶注冊時,驗證用戶名是否已經(jīng)存在。
(9)Confirm:用戶登錄時,驗證用戶名密碼是否匹配。
(10)isOnLine:標識用戶的在線狀態(tài)。
2.1 概要設(shè)計
2.1.1 整體框架設(shè)計
本聊天系統(tǒng)主要采用了C/S 結(jié)構(gòu),服務(wù)端和客戶端之間通過Socket 進行連接通信。服務(wù)端主要任務(wù)是:連接數(shù)據(jù)庫和處理客戶端的各種請求;客戶端主要是為用戶提供各種服務(wù),然后將服務(wù)請求發(fā)送給服務(wù)端[4]。
2.1.2 模塊設(shè)計
根據(jù)前期的系統(tǒng)需求分析,設(shè)計出局域網(wǎng)聊天系統(tǒng)的各個功能模塊[5],如下圖所示。
圖3 客戶端與服務(wù)器端連接框架圖
圖4 客戶端之間連接框架
圖5 各功能模塊圖
2.1.3 數(shù)據(jù)庫設(shè)計
通過前期對系統(tǒng)進行需求分析以及對各個功能模塊的設(shè)計,確定數(shù)據(jù)庫存儲用兩張表實現(xiàn),分別是用戶信息表(userInfo)、歷史信息表(history)表結(jié)構(gòu)設(shè)計[6]如圖6、圖7所示。
圖6 用戶信息表
圖7 聊天記錄表
2.2 詳細設(shè)計
2.2.1 客戶端的設(shè)計
客戶端主要是實現(xiàn)用戶所需功能,當用戶啟動一個客戶端時,用戶可以通過圖形畫界面上的一些功能按鈕開啟某項對應(yīng)的功能。當用戶點擊某個按鈕時,會產(chǎn)生一個相應(yīng)的事件,客戶端監(jiān)聽到該事件后,啟動相應(yīng)的程序去處理該事件。并將該事件的請求發(fā)送到服務(wù)器,然后客戶端等待服務(wù)器的響應(yīng)[7]。這兩項功能分別由init.py和Server.py兩個模塊實現(xiàn),init模塊包含建立客戶端連接套接字,接收用戶發(fā)送的請求,將請求發(fā)送給服務(wù)器端。Server模塊是一個獨立的模塊要先于客戶端程序運行,實現(xiàn)的功能為建立服務(wù)器端連接套接字,綁定 IP和端口地址,監(jiān)聽客戶端請求,并做出響應(yīng)[8]??蛻舳说牧鞒虉D如圖8所示。
圖8 客戶端程序流程圖
2.2.2 登錄設(shè)計
本局域網(wǎng)聊天軟件名稱為 PyChat本文所有的截圖是本軟件的1.0版本,py為Python的簡寫,同時Python腳本文件后綴名為.py,Chat意為“聊天,閑談”,在開發(fā)本軟件時用到的集成開發(fā)環(huán)境為PyCharm,PyChat的名字也與之相關(guān)聯(lián)。
用戶登錄界面用于接收用戶輸入的用戶名和密碼,登錄界面對用戶輸入的用戶名密碼進行驗證(同數(shù)據(jù)庫中用戶信息表進行比對),驗證成功則彈出聯(lián)系人列表界面正常登錄,驗證失敗則提示用戶注冊。新用戶進行注冊時,注冊界面復用登錄界面,由“注冊”按鈕調(diào)用數(shù)據(jù)庫模塊的相關(guān)代碼,用戶輸入的用戶名,密碼信息插入數(shù)據(jù)庫用戶信息表中,插入成功后彈出提示窗口提示用戶牢記用戶名,唯一ID,密碼。登錄界面如圖9所示,注冊成功提示界面如圖10所示。
圖9 登錄窗口
圖10 注冊成功提示界面
本軟件代碼部分共分為三個.py文件,分別為 init.py,Server.py,,oprate_db.py,登錄界面由init.py文件中的Window類實現(xiàn),此類初始化聊天窗口,創(chuàng)建兩個Label控件分別提示帳號、密碼,兩個Text控件用于接收用戶輸入的用戶名密碼,兩個Button控件用來產(chǎn)生鼠標點擊事件,觸發(fā)數(shù)據(jù)庫操作函數(shù)。當用戶需要注冊新用戶時只需將自己想要的用戶名,密碼輸入到輸入框中點擊注冊按鈕即可[9]。
注冊按鈕調(diào)用 Mysql.register()方法將用戶信息入庫。點擊登錄按鈕調(diào)用Client.Contact_window()類,初始化聯(lián)系人列表界面。
2.2.3 聯(lián)系人列表設(shè)計
聯(lián)系人列表用于展示自己的聯(lián)系人,聯(lián)系人列表包括添加好友按鈕,聊天室按鈕,好友。界面效果如圖11所示。
圖11 聯(lián)系人列表
圖12 添加好友
聯(lián)系人列表主要分為三部分,添加好友、聊天室、聯(lián)系人,用戶點擊添加好友時調(diào)用 init().Contact_window()彈出好友添加界面如圖12所示,好友添加成功后,以ID的形式顯示在聯(lián)系人中,點擊聯(lián)系人按鈕調(diào)用init.Chat()彈出聊天界面。當用戶點擊聊天室按鈕時,彈出聊天界面,登錄系統(tǒng)的所有用戶可以在此窗口中進行聊天室群聊。
2.2.4 聊天窗口設(shè)計
聊天窗口用于接收用戶輸入的聊天信息,同時將接收到的聊天信息顯示到消息屏幕中,聊天窗口如圖13 所示。
圖13 聊天窗口
用戶將需要發(fā)送的信息輸入到窗口下方的文本輸入框中,Client.send()方法將用戶輸入數(shù)據(jù)進行提取,將提取后的信息封裝到Client建立的Socket中,用于同服務(wù)器進行通信。接收消息時,接收到的消息將顯示到輸入框上方的接收信息框中。
本章展示底層通信原理代碼,本聊天軟件分為通信部分(也是核心部分)和界面部分,最終的軟件實現(xiàn)是通信部分與界面部分的集成。本章展示底層通信原理代碼,本聊天軟件分為通信部分(也是核心部分)和界面部分,最終的軟件實現(xiàn)是通信部分與界面部分的集成。
3.1 服務(wù)器部分
#!/usr/bin/env python
# coding: utf-8
import socket
import threading
"""定義用到的變量,建立服務(wù)器端套接字"""
con = threading.Condition()
HOST = '127.0.0.1'
PORT = 8888 # Arbitrary non-privileged port
data = ''
ADDR = (HOST,PORT)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Server init success!'
s.bind(ADDR)
s.listen(10)
print 'Waiting for client connect'
#Function for handling connections. This will be used to create threads
# 接受數(shù)據(jù)存到tmp中 tmp傳給NotifyAll方法中變成data
"""此方法用于接收客戶端發(fā)送的消息,有用戶離線時輸出提示消息"""
def ClientThreadIn(conn, nick):
global data
global id
#infinite loop so that function do not terminate and thread do not end.
while True:
#Receiving from client
try:
temp = conn.recv(1024)
if not temp:
conn.close()
return
NotifyAll(temp)
print data
except:
NotifyAll(nick + " leaves the room!")
print data
return
#came out of loop
"""此方法用于喚醒其他線程,是實現(xiàn)多線程的一部分"""
def NotifyAll(sss):
global data
if con.acquire():
data = sss
con.notifyAll()
con.release()
"""此方法將消息從服務(wù)器端發(fā)送給客戶端"""
def ClientThreadOut(conn, nick):
global data
while True:
if con.acquire():
con.wait()
if data:
try:
conn.send(data)
con.release()
except:
con.release()
return
"""主循環(huán)保證服務(wù)器端開啟后一直處于運行狀態(tài)
并不間斷接收客戶端發(fā)來的消息,根據(jù)實際需要將消息轉(zhuǎn)發(fā)給特定用戶
如果用戶進行的是聊天室群聊,則將消息廣播給所有用戶,調(diào)用多線程
方法接收消息與發(fā)送消息同步進行"""
while True:
#wait to accept a connection - blocking call
conn, addr = s.accept()
print 'Connected with ' + addr[0] + ':' + str(addr[1])
nick = conn.recv(1024)
NotifyAll('Welcome ' + nick + ' to the room!')
print data
print str((threading.activeCount() + 1) / 2) + ' person(s)!'
# conn.send(data)
threading.Thread(target = ClientThreadIn , args = (conn,nick)).start()
threading.Thread(target = ClientThreadOut , args = (conn,nick)).start()
s.close()
3.2 客戶端部分
#!/usr/bin/env python
# coding: utf-8
# author: Paula
# date: Mar-o3-2017
"""導入用到的包"""
from Tkinter import *
from oprate_db import * from time import sleep
import socket
import threading
"""將客戶端的實現(xiàn)部分封裝在此類中"""
class Client(object):
global msg_trans
msg_trans=''
def __init__(self,myId,name):
"""初始化socket"""
self.sock = socket.socket(socket.AF_INET ,socket.SOCK_STREAM)
self.sock.connect(('127.0.0.1',8888))
self.inString = ''
self.outString = ''
self.nick =name
self.sock.send(self.nick)
self.myId =myId
self.fromid = ''
def DealOut(self,s,toid,myid,outstring):
"""輸出函數(shù),將用戶輸入的信息通過建立的 socket連接發(fā)送到服務(wù)器端"""
toId = toid
myId = myid
msg = []
self.outString = outstring
self.outString = self.nick + ': ' + self.outString
msg.append(toId)
msg.append(myId)
msg.append(self.outString)
mssage=str(msg)
s.send(mssage)
def DealIn(self,s):
"""數(shù)據(jù)接入函數(shù),將服務(wù)器發(fā)來的數(shù)據(jù)進行分揀,接收發(fā)送給自己的消息
或聊天室消息"""
global msg_trans
while True:
self.inString = s.recv(1024)
# print '2---7:', self.inString[2:7]
if self.inString:
If self.inString[2:7]==self.myId or self.inString[2:7]=='10000':
self.fromid = self.inString[11:16]
msg_trans = self.inString[21:-4]
print 'deal in',msg_trans
def mainrecv(self):
"""線程接收函數(shù),封裝多線程方法和數(shù)據(jù)接入函數(shù)"""
thin = threading.Thread(target = self.DealIn, args = (self.sock,)) thin.start()
# return
def mainsend(self,toid,outstr):
"""線程發(fā)送函數(shù),封裝多線程方法和數(shù)據(jù)發(fā)送函數(shù)"""
if self.inString[2:7]=='10000':
to = '10000'
elif self.fromid=='':
to = toid
else:
to = self.fromid
outstring = outstr
# self.sock.send(self.nick)
thout = threading.Thread(target = self.DealOut, args = (self.sock,to,self.myId,outstring,))
thout.start()
測試部分主要分為,單元測試、集成測試,分別對應(yīng)了開發(fā)過程中的編碼、概要設(shè)計、需求分析部分,經(jīng)過系統(tǒng)的測試可以暴露軟件中存在的缺陷,提高代碼可靠性,最終符合用戶需求。
4.1 單元測試
單元測試是(模塊測試)是開發(fā)者編寫的一小段代碼,用于檢測被測代碼的一個很小的、很明確的功能是否正確。通常而言,一個單元測試是用于判斷某個特定條件(或者場景)下某個特定函數(shù)的行為,是對單個的軟件單元或者一組相關(guān)的軟件單元進行的測試,是代碼級別的測試。
經(jīng)過測試客戶端一對一單聊功能正常,客戶端正常連接服務(wù)器,客戶端可以接收其他用戶發(fā)來的消息,并將自己的消息發(fā)還給其他用戶。
4.2 集成測試
圖14 客戶端一對一聊天單元測試
集成測試又叫組裝測試或聯(lián)合測試,是單元測試的多級擴展,是在單元測試的基礎(chǔ)上進行的一種有序測試,這種測試需要將所有模塊按照設(shè)計需求,逐步裝配成高層的功能模塊,并進行測試,直到整個軟件成為一個整體,集成測試旨在檢驗軟件單元之間的接口關(guān)系,以期望通過測試發(fā)現(xiàn)各軟件單元接口之間存在的問題,最終把經(jīng)過測試的單元組成符合設(shè)計要求的軟件。集成測試驗證程序和概要設(shè)計說明的一致性,任何不符合該說明的程序模塊行為都應(yīng)該加以記載并上報,因此集成測試是發(fā)現(xiàn)和改正模塊接口錯誤的重要階段。
集成測試暴露出的問題:
(1)底層通信實現(xiàn)時未用類進行封裝,導致代碼耦合度高降低了模塊獨立性。
(2)底層通信實現(xiàn)時未預留足夠的接口,導致界面無法與功能進行對接。
(3)接口間無法傳遞數(shù)值。
經(jīng)過嚴謹?shù)募蓽y試主要暴露了以上三個問題,通過不斷對代碼進行優(yōu)化,對功能的內(nèi)部實現(xiàn)進行封裝,使用類變量和全局變量解決參數(shù)傳遞問題,定義了多個接口用于界面調(diào)用相應(yīng)的功能代碼,很好的解決的如上問題,降低了模塊的耦合程度,提高了模塊獨立性。
本次開發(fā)的程序是PyChat的1.0版本,本文設(shè)計聊天軟件以其信息傳遞迅速、占用系統(tǒng)資源少、易于維護、功能簡單實用、信息安全、占用較少的網(wǎng)絡(luò)資源等優(yōu)勢在企業(yè)、學校級別的大型局域網(wǎng)絡(luò)中擁有廣闊的運用前景。部署此局域網(wǎng)聊天系統(tǒng)僅需一臺服務(wù)器就可實現(xiàn)具有跨平臺特性的實時信息通訊。不過軟件還有很多不完善的地方,還有很多缺陷等待我去改進。在測試部分發(fā)現(xiàn),字體大小和字體顏色等功能還未實現(xiàn);系統(tǒng)的健壯性有待提高,非正常流程結(jié)束程序會導致端口占用無法開啟新的客戶端等缺陷。因此我需要進一步提高軟件的可用性,豐富軟件的功能。
[1]Barry.P .林琪等譯.Head First Python[M].北京:中國電力出版社,2012.
[2]Mark Summerifield.王弘博,孫傳義譯.Python3程序開發(fā)指南(第二版)[M].人民郵電出版社,2011.
[3]Magnus Lie Hetland.司維,曾軍崴,譚穎華譯.Python基礎(chǔ)教程[M].北京:人民郵電出版社,2010.
[4]蔡建平.軟件綜合開發(fā)案例教程--Linux、GCC、MySQL、Socket、Gtk+與開源案例[M](1版).北京:清華大學出版社,2011.
[5] Ben Forta.鐘鳴,劉曉霞譯.SQL必知必會[M].北京:人民郵電出版社,2013.
[6]John Goerzen.莫遲譯.Python網(wǎng)絡(luò)編程基礎(chǔ)[M].北京:電子工業(yè)出版社,2007.
[7]Wesley J.Chun.宋吉廣譯.Python核心編程[M].北京:人民郵電出版社,2008.
[8]沈潔元.簡明 Python教程[M].北京:清華大學出版社,2009.
[9]Lasse Koskela.李貝譯.測試驅(qū)動開發(fā)的藝術(shù)[M].人民郵電出版社,2010.