- 利用Python tkinter和Python socket實做簡單版多人聊天室
- 利用python tkinter modual設計聊天室的UI。
- 有帳號及密碼的註冊及登錄功能,並且在登錄時會進行核對帳密的動作。
- 聊天室分為公共頻道與私人頻道,可以透過右側listbox欄位來選擇要在哪一個頻道進行發言。
- 用戶之間可以進行傳送檔案(包括圖片、文件等)動作。
- 會保存聊天室紀錄,並在下次登陸時會載入之前的聊天紀錄
- 聊天室在網路上之間的對話傳送,皆利用Python Crypto module中的AES加密演算法進行加密
-
encryption & decryption
from Crypto.Cipher import AES from Crypto import Random key = b'fdj27pFJ992FkHQb' def encrypt(data): code = Random.new().read(AES.block_size) cipher = AES.new(key, AES.MODE_CFB, code) return code + cipher.encrypt(data) def decrypt(data): return AES.new(key, AES.MODE_CFB, data[:16]).decrypt(data[16:])
-
Socket's Send & Recv
- 發送package前會在最前頭加上明確地package size的一個2 byte('>H')。
- 接收package時先接收這個2 byte('>H'),獲取將要接收的package,然後再接收相對應size的package。
import struct import json max_buff_size = 1024 def pack(data): return struct.pack('>H', len(data)) + data def send(socket, data_dict): socket.send(pack(encrypt(json.dumps(data_dict).encode('utf-8')))) def recv(socket): data = b'' surplus = struct.unpack('>H', socket.recv(2))[0] socket.settimeout(5) while surplus: recv_data = socket.recv(max_buff_size if surplus > max_buff_size else surplus) data += recv_data surplus -= len(recv_data) socket.settimeout(None) return json.loads(decrypt(data))
-
帳號管理相關的function
- 包刮從文件(users.dat)中加載已註冊users的帳密資料(賬號和密碼對應的MD5值)、註冊用戶、驗證用戶(看看密碼的MD5值是否和文件中的值相同)、將所有已註冊用戶的信息保存到文件中。
def load_users(): try: return pickle.load(open('users.dat', 'rb')) # r: read, b: byte except: return {} def user_certificate(user, password): if user in users.keys() and users[user] == password: return True return False def user_register(user, password): if user not in users.keys(): users[user] = password save_users() return True else: return False def save_users(): pickle.dump(users, open('users.dat', 'wb')) # w: write, b: byte
-
聊天紀錄管理相關的function
- 每條聊天記錄為key-value形式,key為(sender,receiver),value為(sender,time,msg),包括從文件(chat_history.dat)當中載入聊天紀錄、把聊天紀錄儲存至文件當中
def load_history(): try: return pickle.load(open('chat_history.dat', 'rb')) # w: write, b: byte except: return {} def get_history(sender, receiver): if receiver == '': key = ('', '') else: key = get_key(sender, receiver) return chat_history[key] if key in chat_history.keys() else [] def get_key(sender, receiver): return(sender, receiver) if (receiver, sender) not in chat_history.keys() else (receiver, sender) def append_history(sender, receiver, message): if receiver == '': key = ('', '') else: key = get_key(sender, receiver) if key not in chat_history.keys(): chat_history[key] = [] chat_history[key].append((sender, time.strftime('%m/%d %Y - %H:%M:%S', time.localtime(time.time())), message)) save_history() def save_history(): pickle.dump(chat_history, open('chat_history.dat', 'wb')) # w: write, b: byte
-
Server端
- 服務端採用socketserver的BaseRequestHandler Class,可自動處理並發送請求。代表每當有一個客戶端請求連接時,都會new一個BaseRequestHandler Class,然後在一個thread中處理相關請求。
class Handler(socketserver.BaseRequestHandler): clients = {} def setup(self): self.user = '' self.file_peer = '' self.authed = False def handle(self): while True: # ...... def main(): global server, users, chat_history, application print("Server running...") users = load_users() chat_history = load_history() application = socketserver.ThreadingTCPServer((HOST, PORT), Handler) application.serve_forever() if __name__ == '__main__': main()
-
multicast
- 當有成員加入聊天室或離開聊天室,server都會透過利用multicast向仍在線上的所有user進行通知
- server:
server = None MCAST_GRP = '224.111.1.1' MCAST_PORT = 5007 def multicast(user): global server server.sendto(str(user).encode('utf-8'), (MCAST_GRP, MCAST_PORT)) class Handler(socketserver.BaseRequestHandler): clients = {} def setup(self): self.user = '' self.file_peer = '' self.authed = False def handle(self): while True: data = encryption.recv(self.request) # multicast to all online client multicast(self.user) def main(): global server MULTICAST_TTL = 2 server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) server.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, MULTICAST_TTL) if __name__ == '__main__': main()
- client:
udp_socket = None MCAST_GRP = '224.111.1.1' MCAST_PORT = 5007 # open UDP socket udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # multicast setting udp_socket.bind(("", MCAST_PORT)) # UDP bind mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY) udp_socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) recv_message = udp_socket.recv(10240).decode() print("user: %s, join the chat" % recv_message)
1. How do you UDP multicast in Python?
2. 為應用程式設計圖形化介面,使用Python Tkinter 模組
6. Python tkinter filedialog 開啟檔案對話框
7. What is the difference between \ and \\ in file path
8. Python UDP Server/Client 網路通訊教學
10. time — Time access and conversions
14. Python 3的f-Strings:增強的字串格式語法(指南)
15. Python 以 PyCryptodome 實作 AES 對稱式加密方法教學與範例
16. Encrypt & Decrypt using PyCrypto AES 256
18. Python 計算 MD5 與 SHA 雜湊教學與範例
19. How To Use Images as Backgrounds in Tkinter?
20. How to change font and size of buttons in Tkinter Python










