基于python面向?qū)ο蠖嗳肆奶焓?/strong>
1、項(xiàng)目環(huán)境
項(xiàng)目名稱:多人聊天室
項(xiàng)目模式:C/S
開發(fā)環(huán)境:win10+python3.8+pycharm
所需知識(shí):python GUI編程,多線程編程,網(wǎng)絡(luò)編程,數(shù)據(jù)庫編程
2、流程
3、程序設(shè)計(jì)
了解一下服務(wù)器扮演的角色,下面是服務(wù)器的業(yè)務(wù)流程。大致工作模式
服務(wù)器:
客戶端:
- 首先服務(wù)器在指定的端口進(jìn)行監(jiān)聽,等待客戶的鏈接
- 客戶端鏈接到服務(wù)器之后,服務(wù)器開啟單線程來處理該用戶的請(qǐng)求
- 處理線程等待客戶端發(fā)送的請(qǐng)求
- 服務(wù)器根據(jù)客戶端請(qǐng)求類型的不同,調(diào)用不同處理的函數(shù)
- 處理完客戶端請(qǐng)求之后,再次回到第三步繼續(xù)等待處理客戶端新的請(qǐng)求
- 客戶端退出登錄,服務(wù)器也會(huì)關(guān)閉對(duì)客戶端的處理線程,釋放資源
4、響應(yīng)協(xié)議設(shè)計(jì)
-
我們都知道三次握手和四次揮手,這里呢我們約定了客戶端發(fā)送什么樣格式的數(shù)據(jù)給服務(wù)器,服務(wù)器又需要返回什么樣格式的數(shù)據(jù)給客戶端,客戶端會(huì)有不同的請(qǐng)求,所以我們針對(duì)不同的請(qǐng)求個(gè)響應(yīng)定義了需求個(gè)相應(yīng)號(hào),來區(qū)分不同的請(qǐng)求和響應(yīng)
-
網(wǎng)絡(luò)上一般使用json和xml格式來傳輸數(shù)據(jù),但是用他們來傳輸,對(duì)于我們的項(xiàng)目有點(diǎn)復(fù)雜,我們的項(xiàng)目沒有這么復(fù)雜的數(shù)據(jù),我們采用|進(jìn)行分割,然后拿到數(shù)據(jù)進(jìn)行split一下就可以了。
-
登錄響應(yīng)格式: 1001|ret|nickname|username,其中ret
代表服務(wù)器端驗(yàn)證的結(jié)果,如果是0,表示服務(wù)端驗(yàn)證失敗,后面的nickname username
會(huì)為空字符串,若是1 ,表示服務(wù)端驗(yàn)證成功,nickname 為服務(wù)端返回的該用戶的昵稱,username 是該用戶的用戶名。
-
聊天的響應(yīng)格式:1002|nickname|message, nicakname 是為聊天信息發(fā)送者的昵稱,message
是發(fā)送的聊天信息
下面我們定義了服務(wù)端需要的一些常量,以及為了實(shí)現(xiàn)客戶端和服務(wù)端通信定義的一些協(xié)議編號(hào),協(xié)議編號(hào)如下:
#----數(shù)據(jù)協(xié)議相關(guān)配置----
REQUEST_LOGIN = '0001' #登陸請(qǐng)求
REQUEST_CHAT= '0002' #聊天請(qǐng)求
RESPONSE_LOGIN_RESULT = '1001' #登陸結(jié)果響應(yīng)
RESPONSE_CHAT= '1002' #聊天響應(yīng)
DELIMITER = '|' #自定義協(xié)議數(shù)據(jù)分割符
SERVER_IP = '127.0.0.1' #服務(wù)器地址
SERVER_PORT = 8090 #服務(wù)器端口
5、面向?qū)ο蟮乃枷?/h2>
服務(wù)器、客戶端分離設(shè)計(jì)內(nèi)容:
6、服務(wù)器通訊實(shí)現(xiàn)
制作協(xié)議報(bào)頭,響應(yīng)數(shù)據(jù),制定一個(gè)模塊config.py
#----數(shù)據(jù)協(xié)議相關(guān)配置----
REQUEST_LOGIN = '0001' #登陸請(qǐng)求
REQUEST_CHAT= '0002' #聊天請(qǐng)求
RESPONSE_LOGIN_RESULT = '1001' #登陸結(jié)果響應(yīng)
RESPONSE_CHAT= '1002' #聊天響應(yīng)
DELIMITER = '|' #自定義協(xié)議數(shù)據(jù)分割符
SERVER_IP = '127.0.0.1' #服務(wù)器地址
SERVER_PORT = 8090 #服務(wù)器端口#----數(shù)據(jù)庫相關(guān)配置---- #具體參數(shù)自己設(shè)置
DB_HOST='127.0.0.1'#這里是你數(shù)據(jù)庫IP地址
DB_PORT=3306 #這里是你數(shù)據(jù)庫端口
DB_NAME='py_chat' #這里是你數(shù)據(jù)庫名
DB_USER='root' #這里是你數(shù)據(jù)庫登陸賬號(hào)
DB_PASSWD='123456' #這里是你數(shù)據(jù)庫密碼
CHARSET='utf-8'
處理服務(wù)器響應(yīng)字符串的拼接,制定一個(gè)模塊response_protocol.py
from config import *class ResponseProtocol (object):"""服務(wù)器響應(yīng)協(xié)議的格式字符串處理"""@staticmethoddef response_login_result(result: str, nickname: str, username: str) -> str:"""拼接登陸響應(yīng):param result:登陸結(jié)果,0或則1,0標(biāo)識(shí)登陸失敗,1標(biāo)識(shí)登陸成功:param nickname:登陸名,登陸失敗,該值為空字符串:param username:登陸ID,登陸失敗,該值為空字符串:return 登陸結(jié)果相應(yīng)格式字符串"""return DELIMITER.join ([RESPONSE_LOGIN_RESULT, result, nickname, username])@staticmethoddef response_chat(nickname, messages):"""拼接聊天相應(yīng),數(shù)據(jù)格式為:“相應(yīng)協(xié)議編號(hào)|聊天發(fā)送者昵稱|聊天信息”:param nickname: 聊天內(nèi)容發(fā)送者昵稱:param messages: 聊天內(nèi)容:return: 聊天相應(yīng)協(xié)議格式字符串"""return DELIMITER.join([RESPONSE_CHAT, nickname, messages])
7、主體框架搭建
基本邏輯業(yè)務(wù)-服務(wù)端
server.py 模塊定義Server類來處理服務(wù)器業(yè)務(wù)邏輯,該類實(shí)現(xiàn)了服務(wù)器的主體框架
from server_socket import ServerSoket
from socket_warpper import SocketWrapper
from threading import Thread
from config import *
from response_protocol import *
from dbHandle import DBHandleclass Server(object):"""自定義套接字,負(fù)責(zé)初始化服務(wù)器套接字需要的相關(guān)參數(shù)"""def __init__(self):# 初始化套結(jié)字self.server_socket = ServerSoket()# 創(chuàng)建請(qǐng)求的ID和方法關(guān)聯(lián)字典self.requset_handle_function = {}self.register(REQUEST_LOGIN, self.request_login_handle)self.register(REQUEST_CHAT, self.request_chat_handle)# 創(chuàng)建保存當(dāng)前登錄用戶字典self.clients = {}self.db = DBHandle()'''注冊(cè)消息類型和處理函數(shù)到字典'''def register(self, requeset_id, handle_function):self.requset_handle_function[requeset_id] = handle_function'''啟動(dòng)程序'''def startup(self):"""啟動(dòng)器"""while True:print('正在等待客戶端連接')soc, addr = self.server_socket.accept()# print ('獲取到客戶端連接')client_soc = SocketWrapper(soc)# 啟動(dòng)線程處理該用戶請(qǐng)求Thread(target=lambda: self.request_handle(client_soc)).start()'''處理客戶端數(shù)據(jù)'''def request_handle(self, client_soc):while True:# 接收客戶端數(shù)據(jù)recv_data = client_soc.recv_data()if not recv_data:# 沒有接收到數(shù)據(jù)客戶端應(yīng)該已經(jīng)關(guān)閉self.remve_offline_user(client_soc)client_soc.close()break'''解析數(shù)據(jù)'''parse_data = self.parse_request_text(recv_data)'''分析請(qǐng)求類型,并依據(jù)請(qǐng)求類型調(diào)用相應(yīng)的分類處理''''''# 獲得使用的方法名 方法名 = 字典[value] 注: 如 字典[key]可以互相找到字典[value]# 此處 字典[key]=0001 對(duì)應(yīng)得字典[value] = REQUEST_LOGIN#例子:parse_data = '0001|XXX|XXX'parse_data['requset_id'] = ‘0001’requset_handle_function['0001'] = self.request_login_handlehandle_funtion = self.request_login_handle'''handle_funtion = self.requset_handle_function.get(parse_data['requset_id'])if handle_funtion:# 按照方法名調(diào)用方法handle_funtion(client_soc, parse_data)else:# 如果傳輸內(nèi)容不匹配,返回錯(cuò)誤請(qǐng)求self.request_err_handle(client_soc)'''用戶離線操作'''def remve_offline_user(self, client_soc):for username, info in self.clients.items():if info['sock'] == client_soc:print(self.clients[username]['nickname'] + '已經(jīng)離開')del self.clients[username]break'''解析客戶端發(fā)送來的數(shù)據(jù)''''''解析數(shù)據(jù)內(nèi)容'''def parse_request_text(self, recv_data):'''登錄信息登錄信息:0001|username|password聊天信息:0002|username|messages錯(cuò)誤信息:err'''print('解析客戶端數(shù)據(jù):' + recv_data)requset_list = recv_data.split(DELIMITER)requset_data = {}requset_data['requset_id'] = requset_list[0]if requset_data['requset_id'] == REQUEST_LOGIN:requset_data['username'] = requset_list[1]requset_data['password'] = requset_list[2]elif requset_data['requset_id'] == REQUEST_CHAT:requset_data['username'] = requset_list[1]requset_data['messages'] = requset_list[2]return requset_data'''登錄處理'''def request_login_handle(self, client_sock, requet_data):# print('收到登錄請(qǐng)求')username = requet_data['username']password = requet_data['password']# 查詢用戶是否合法ret, nickname, username = self.check_user_login(username, password)# 如果登錄成功,則保存用戶連接套接字if ret == '1':self.clients[username] = {'sock': client_sock, 'nickname': nickname}# 組裝響應(yīng)結(jié)果response_text = ResponseProtocol.response_login_result(ret, nickname, username)# 發(fā)送響應(yīng)結(jié)果client_sock.send_data(response_text)'''聊天處理'''def request_chat_handle(self, client_sock, requet_data):# 獲取消息內(nèi)容username = requet_data['username']messages = requet_data['messages']try:nickname = self.clients[username]['nickname']except:client_sock.send_data('您未登錄,請(qǐng)登錄后再發(fā)消息')return# 拼接發(fā)送給客戶的消息文本msg = ResponseProtocol.response_chat(nickname, messages)# 轉(zhuǎn)發(fā)消息給在線用戶for u_name, info in self.clients.items():if username == u_name:continueinfo['sock'].send_data(msg)print(msg)'''錯(cuò)誤信息處理'''def request_err_handle(self, client_sock):print("傳輸數(shù)據(jù)出錯(cuò)------")client_sock.send_data('數(shù)據(jù)無效,請(qǐng)重新確認(rèn)')'''檢查用戶是否登錄成功,返回檢查結(jié)果(0/失敗,1/成功,昵稱,用戶賬號(hào)'''def check_user_login(self, username, password):# print("正在檢測(cè)是否成功")# 從數(shù)據(jù)庫查詢用戶信息sql = "select * from users where username = '%s' " % usernameresult = self.db.get_one(sql)# 如果沒有查詢結(jié)果,用戶不存在,登錄失敗if not result:# print("用戶不存在,登錄失敗")return '-1', ' ', username# 密碼不匹配,說明密碼錯(cuò)誤,登錄失敗if password != result["password"]:# print("密碼錯(cuò)誤,登錄失敗")return '0', ' ', username# 登錄成功# print("驗(yàn)證正確,登錄成功")print(result['nickname'] + "進(jìn)入聊天室")return '1', result['nickname'], usernameif __name__ == '__main__':Server().startup()
- 這里我們自定義一個(gè)套接字,讓類繼承socket、super找父類的套接字有一個(gè)初始化,不初始化的類型告訴他
import socket
import configclass ServerSoket(socket.socket):"""自定義套接字,負(fù)責(zé)初始化服務(wù)器套接字需要的相關(guān)參數(shù)"""def __init__(self ):#設(shè)置TCP類型#初始化套結(jié)字super(ServerSoket,self).__init__(socket.AF_INET,socket.SOCK_STREAM)self.bind((config.SERVER_IP,config.SERVER_PORT))#設(shè)置為監(jiān)聽模式self.listen(128)
super(ServerSocket,self).init(socket.AF_INET,socket.SOCK_STREAM),綁定地址和端口,這里的參數(shù)不能寫死,因?yàn)槟阋菍懰?#xff0c;以后你要改代碼要找一大堆的代碼,這里我們把它固定在config.py 里面,以后要想改直接到配置相關(guān)項(xiàng)去改。
- 初始化服務(wù)器套接字需要的相關(guān)操作。
from server_socket import ServerSocket
from socket_wrapper import SocketWrapper
from threading import Thread
class Server(object):"""服務(wù)器的核心類"""def __init__(self):# 初始化套結(jié)字self.server_socket = ServerSoket()# 創(chuàng)建請(qǐng)求的ID和方法關(guān)聯(lián)字典self.requset_handle_function = {}self.register(REQUEST_LOGIN, self.request_login_handle)self.register(REQUEST_CHAT, self.request_chat_handle)# 創(chuàng)建保存當(dāng)前登錄用戶字典self.clients = {}self.db = DBHandle()def startup(self):"""啟動(dòng)器"""while True:print('正在等待客戶端連接')soc, addr = self.server_socket.accept()# print ('獲取到客戶端連接')client_soc = SocketWrapper(soc)# 啟動(dòng)線程處理該用戶請(qǐng)求Thread(target=lambda: self.request_handle(client_soc)).start()
首先在__ init__ 方法里創(chuàng)建監(jiān)聽的套接字,當(dāng)我們調(diào)用start方法啟動(dòng)服務(wù)器程序,在該函數(shù)中我們使用while來獲取客戶端的連接,有客戶連接到服務(wù)器,服務(wù)器會(huì)獲取一個(gè)套接字來標(biāo)識(shí)與該客戶的連接,然后我們開啟新的線程來處理客戶端的連接,該線程函數(shù)為Server類中的request_handle方法,該方法接收套接字作為參數(shù),request_handle 方法是服務(wù)端請(qǐng)求處理的核心方法
8、消息處理request_handle 的處理
接收–>解析–>判斷–>處理
def __init__(self):# 初始化套結(jié)字self.server_socket = ServerSoket()# 創(chuàng)建請(qǐng)求的ID和方法關(guān)聯(lián)字典self.requset_handle_function = {}self.register(REQUEST_LOGIN, self.request_login_handle)self.register(REQUEST_CHAT, self.request_chat_handle)# 創(chuàng)建保存當(dāng)前登錄用戶字典self.clients = {}def register(self, requeset_id, handle_function):'''注冊(cè)消息類型和處理函數(shù)到字典'''self.requset_handle_function[requeset_id] = handle_functiondef request_handle(self, client_soc):'''處理客戶端數(shù)據(jù)'''while True:# 接收客戶端數(shù)據(jù)recv_data = client_soc.recv_data()if not recv_data:# 沒有接收到數(shù)據(jù)客戶端應(yīng)該已經(jīng)關(guān)閉self.remve_offline_user(client_soc)client_soc.close()break'''解析數(shù)據(jù)'''parse_data = self.parse_request_text(recv_data)'''分析請(qǐng)求類型,并依據(jù)請(qǐng)求類型調(diào)用相應(yīng)的分類處理''''''# 獲得使用的方法名 方法名 = 字典[value] 注: 如 字典[key]可以互相找到字典[value]# 此處 字典[key]=0001 對(duì)應(yīng)得字典[value] = REQUEST_LOGIN#例子:parse_data = '0001|XXX|XXX'parse_data['requset_id'] = ‘0001’requset_handle_function['0001'] = self.request_login_handlehandle_funtion = self.request_login_handle'''handle_funtion = self.requset_handle_function.get(parse_data['requset_id'])if handle_funtion:# 按照方法名調(diào)用方法handle_funtion(client_soc, parse_data)else:# 如果傳輸內(nèi)容不匹配,返回錯(cuò)誤請(qǐng)求self.request_err_handle(client_soc)
- 我們接受到客戶的數(shù)據(jù)之后看它發(fā)來的數(shù)據(jù)類型是什么,調(diào)用相應(yīng)的處理函數(shù),這里的id類型和方法是唯一的,我們只需要初始化一次就可以,在init初始化。在后面我們不可能只有發(fā)送信息的功能,可能還有圖片,視頻等等在初始化里面加功能id就可以,來梳理思路:假如發(fā)送的消息是0001|uu|11111
調(diào)用 parse_request_text,按照類型分析數(shù)據(jù) ,發(fā)現(xiàn)id=0001,返回 request_data
,分析請(qǐng)求類型,調(diào)用相應(yīng)的處理函數(shù) ,調(diào)用 request_handle_function, 發(fā)現(xiàn)請(qǐng)求的id在里面,開始調(diào)用登錄功能。
9、登錄和聊天功能的處理
- 獲取登錄的用戶名和密碼
- 查詢數(shù)據(jù),是否存在對(duì)應(yīng)的用戶
- 如果登錄成功,保存用戶信息,失敗什么都不做
- 返回登錄結(jié)果給客戶端
'''登錄處理'''def request_login_handle(self, client_sock, requet_data):# print('收到登錄請(qǐng)求')username = requet_data['username']password = requet_data['password']# 查詢用戶是否合法ret, nickname, username = self.check_user_login(username, password)# 如果登錄成功,則保存用戶連接套接字if ret == '1':self.clients[username] = {'sock': client_sock, 'nickname': nickname}# 組裝響應(yīng)結(jié)果response_text = ResponseProtocol.response_login_result(ret, nickname, username)# 發(fā)送響應(yīng)結(jié)果client_sock.send_data(response_text)
'''聊天處理'''def request_chat_handle(self, client_sock, requet_data):# 獲取消息內(nèi)容username = requet_data['username']messages = requet_data['messages']try:nickname = self.clients[username]['nickname']except:client_sock.send_data('您未登錄,請(qǐng)登錄后再發(fā)消息')return# 拼接發(fā)送給客戶的消息文本msg = ResponseProtocol.response_chat(nickname, messages)# 轉(zhuǎn)發(fā)消息給在線用戶for u_name, info in self.clients.items():if username == u_name:continueinfo['sock'].send_data(msg)print(msg)
'''檢查用戶是否登錄成功,返回檢查結(jié)果(0/失敗,1/成功,昵稱,用戶賬號(hào)'''def check_user_login(self, username, password):# print("正在檢測(cè)是否成功")# 從數(shù)據(jù)庫查詢用戶信息sql = "select * from users where username = '%s' " % usernameresult = self.db.get_one(sql)# 如果沒有查詢結(jié)果,用戶不存在,登錄失敗if not result:# print("用戶不存在,登錄失敗")return '-1', ' ', username# 密碼不匹配,說明密碼錯(cuò)誤,登錄失敗if password != result["password"]:# print("密碼錯(cuò)誤,登錄失敗")return '0', ' ', username# 登錄成功# print("驗(yàn)證正確,登錄成功")print(result['nickname'] + "進(jìn)入聊天室")return '1', result['nickname'], username
10、數(shù)據(jù)庫的處理
from pymysql import connect
from config import *class DBHandle(object):'''mysql管理器'''def __init__(self):'''初始化數(shù)據(jù)庫'''self.conn= connect(host=DB_HOST,port=DB_PORT,database=DB_NAME,user=DB_USER,password=DB_PASSWD)self.cursor = self.conn.cursor()# 釋放數(shù)據(jù)庫資源def close_db(self):self.cursor.close()self.conn.close()def get_one(self, sql):#執(zhí)行SQL結(jié)果self.cursor.execute(sql)#獲取查詢結(jié)果query_result = self.cursor.fetchone()#判斷是否有結(jié)果if not query_result:return None#獲得字段名稱列表fileds = [filed[0] for filed in self.cursor.description]#保存返回結(jié)果return_data = {}for filed, value in zip(fileds, query_result):return_data[filed] = value#查詢結(jié)果return return_data
'''用戶離線操作'''def remve_offline_user(self, client_soc):for username, info in self.clients.items():if info['sock'] == client_soc:print(self.clients[username]['nickname'] + '已經(jīng)離開')del self.clients[username]break
- 聊天功能處理:通過服務(wù)器向每一個(gè)登錄在線的人轉(zhuǎn)發(fā)消息,不需要向自己發(fā)消息
‘’‘聊天處理’’’
def request_chat_handle(self, client_sock, requet_data):# 獲取消息內(nèi)容username = requet_data['username']messages = requet_data['messages']try:nickname = self.clients[username]['nickname']except:client_sock.send_data('您未登錄,請(qǐng)登錄后再發(fā)消息')return# 拼接發(fā)送給客戶的消息文本msg = ResponseProtocol.response_chat(nickname, messages)# 轉(zhuǎn)發(fā)消息給在線用戶for u_name, info in self.clients.items():if username == u_name:continueinfo['sock'].send_data(msg)print(msg)
11、客戶端實(shí)現(xiàn)
客戶端采用GUI視圖來寫
登錄窗口顯示
新建項(xiàng)目 Client
新建win_client.py
from tkinter import Tk
from tkinter import Label,Entry,Frame,Button,LEFT,ENDclass WindowLogin (Tk):"""登陸窗口"""def __init__(self):super (WindowLogin, self).__init__ ()# 設(shè)置窗口屬性self.window_init ()# 填充控件self.add_widgets ()# self.on_reset_button_click (lambda :print(self.get_username()))# self.on_login_button_click (lambda: print(self.get_password()))"""初始化窗口屬性"""def window_init(self):#設(shè)置窗口標(biāo)題self.title('登陸窗口')#設(shè)置窗口不能被拉伸self.resizable (False,False)#獲取窗口的位置變量window_width = 255window_height =100screenWidth = self.winfo_screenwidth()screenHeight = self.winfo_screenheight()pos_x = (screenWidth-window_width)/2pos_y = (screenHeight-window_height)/2#設(shè)置窗口大小和位置self.geometry('%dx%d+%d+%d' % (window_width,window_height,pos_x,pos_y))"""添加控件到窗口"""def add_widgets(self):"""添加控件到窗口"""# 用戶名提示標(biāo)簽username_label = Label (self)username_label['text'] = '用戶名:'username_label.grid (row=0, column=0, padx=10, pady=5)# 用戶名輸入文本框username_entry = Entry (self, name='username_entry')username_entry['width'] = 20username_entry.grid (row=0, column=1, padx=10, pady=5)# 密碼提示標(biāo)簽password_label = Label (self)password_label['text'] = '密 碼:'password_label.grid (row=1, column=0)# 密碼輸入文本框password_entry = Entry (self, name='password_entry')password_entry['show'] = '*'username_entry['width'] = 20password_entry.grid (row=1, column=1)# 按鈕區(qū)button_frame = Frame (self, name='button_frame')# 重置按鈕reset_button = Button (button_frame, name='reset_button')reset_button['text'] = '重置'reset_button.pack (side=LEFT, padx=40)# 登錄按鈕login_button = Button (button_frame, name='login_button')login_button['text'] = '登錄'login_button.pack (side=LEFT)button_frame.grid (row=2, columnspan=2, pady=5)def get_username(self):"""獲取用戶名"""return self.children['username_entry'].get ()def get_password(self):"""獲取密碼"""return self.children['password_entry'].get ()def clear_username(self):""" 清空用戶名"""return self.children['username_entry'].delete (0, END)def clear_password(self):""" 清空用戶名"""return self.children['password_entry'].delete (0, END)def on_reset_button_click(self, command):"""重置按鈕的響應(yīng)注冊(cè)"""reset_button = self.children['button_frame'].children['reset_button']reset_button['command'] = commanddef on_login_button_click(self, command):"""登錄按鈕的響應(yīng)注冊(cè)"""login_button = self.children['button_frame'].children['login_button']login_button['command'] = command # 把command函數(shù)賦值給登錄按鈕的command,點(diǎn)擊時(shí)調(diào)用commanddef on_window_close(self, command):"""關(guān)閉窗口的響應(yīng)注冊(cè)"""self.protocol ('WM_DELETE_WINDOW', command)
- 整體采用了grid表格的布局,其中用戶名標(biāo)簽放置在(1,1)第一行第一列位置,對(duì)應(yīng)的用戶名的輸入放置在(1,2),密碼標(biāo)簽放置在(2,1),密碼的輸入放置在(2,2),重置和登錄按鈕放置在第三行居中的位置。
- 由于我們已經(jīng)全局使用了grid表格布局,所有我們將他們放在一個(gè)Frame里面,兩個(gè)按鈕在Frame中水平布局
再將Frame整體放置在窗口的第三行,并占據(jù)兩列。
12、客戶端通訊實(shí)現(xiàn)
通訊模塊
制作協(xié)議報(bào)頭,響應(yīng)數(shù)據(jù),創(chuàng)建一個(gè)模塊config.py
#----數(shù)據(jù)協(xié)議相關(guān)配置----
REQUEST_LOGIN = '0001' #登陸請(qǐng)求
REQUEST_CHAT= '0002' #聊天請(qǐng)求
RESPONSE_LOGIN_RESULT = '1001' #登陸結(jié)果響應(yīng)
RESPONSE_CHAT= '1002' #聊天響應(yīng)
DELIMITER = '|' #自定義協(xié)議數(shù)據(jù)分割符
SERVER_IP = '127.0.0.1' #服務(wù)器地址
SERVER_PORT = 8090 #服務(wù)器端口
CHARSET='utf-8'
處理服務(wù)器響應(yīng)字符串的拼接,制定一個(gè)模塊request_protocol.py
from config import *class RequestProtocol (object):"""服務(wù)器響應(yīng)協(xié)議的格式字符串處理"""@staticmethoddef request_login_result(username,password):"""拼接登陸響應(yīng):param username:登陸用戶名,登陸失敗,該值為空字符串:param password:登陸密碼:return 登陸結(jié)果相應(yīng)格式字符串"""return DELIMITER.join ([REQUEST_LOGIN,username, password])@staticmethoddef request_chat(username, messages):"""拼接聊天相應(yīng),數(shù)據(jù)格式為:“相應(yīng)協(xié)議編號(hào)|聊天發(fā)送者賬號(hào)|聊天信息”:param REQUEST_CHAT::param username: 聊天內(nèi)容發(fā)送者賬號(hào):param messages: 聊天內(nèi)容:return: 聊天相應(yīng)協(xié)議格式字符串"""return DELIMITER.join ([REQUEST_CHAT, username, messages])
13、客戶端業(yè)務(wù)實(shí)現(xiàn)
新建模塊client.py
from request_protocol import RequestProtocol
from window_login import WindowLogin
from client_socket import ClientSocket
from threading import Thread
from config import *
from tkinter.messagebox import showinfoclass Client(object):def __init__(self):"""初始化客戶端資源"""# 初始化登陸窗口self.window = WindowLogin()self.window.on_login_button_click(self.send_login_data)self.window.on_reset_button_click(self.clear_inputs)# 創(chuàng)建客戶端套接字self.conn = ClientSocket()# 初始化消息處理函數(shù)self.response_handle_funtion = {}self.regist(RESPONSE_LOGIN_RESULT, self.response_login_handle)self.regist(RESPONSE_CHAT, self.response_chat_handle)def regist(self, requeset_id, handle_function):"""注冊(cè)消息和消息對(duì)應(yīng)的方法到字典里"""self.response_handle_funtion[requeset_id] = handle_functiondef startup(self):'''開啟窗口'''self.conn.connect()Thread(target=self.response_handle).start()self.window.mainloop()def clear_inputs(self):"""清空窗口內(nèi)容"""self.window.clear_password()self.window.clear_username()def send_login_data(self):username = self.window.get_username()password = self.window.get_password()request_text = RequestProtocol.request_login_result(username, password)self.conn.send_data(request_text)def response_handle(self):"不斷收發(fā)服務(wù)器消息"while True:recv_data = self.conn.recv_data()print('收到服務(wù)器消息:' + recv_data)response_data = self.parse_response_data(recv_data)# 根據(jù)事件類型,調(diào)用指定方法名handle_funtionn = self.response_handle_funtion[response_data['response_id']]if handle_funtionn:handle_funtionn(response_data)@staticmethoddef parse_response_data(recv_data):'''登陸響應(yīng)消息:1001|成功/失敗|昵稱|賬號(hào)聊天響應(yīng)消息:1002|發(fā)送者昵稱|消息內(nèi)容'''# 使用協(xié)議約定的符號(hào)來切割消息response_data_list = recv_data.split(DELIMITER)# 解析消息的各個(gè)組成部分response_data = {}response_data['response_id'] = response_data_list[0]if response_data['response_id'] == RESPONSE_LOGIN_RESULT:response_data['result'] = response_data_list[1]response_data['nickname'] = response_data_list[2]response_data['username'] = response_data_list[3]elif response_data['response_id'] == RESPONSE_CHAT:response_data['nickname'] = response_data_list[1]response_data['message'] = response_data_list[2]return response_datadef response_chat_handle(self, response_data):print('接收到聊天消息~', response_data)def response_login_handle(self, response_data):result = response_data['result']if result !='1':showinfo('提示','賬號(hào)或密碼錯(cuò)誤')returnnickname = response_data['nickname']username = response_data['username']print('%s 的昵稱為 %s ,已經(jīng)登錄聊天室' % (username,nickname))
總結(jié)
以上是生活随笔為你收集整理的基于python面向对象多人聊天室的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。