網絡圖形化界面多人聊天室 - Linux
Windows版本:https://www.cnblogs.com/noonjuan/p/12078524.html
在Python實現網絡多人聊天室基礎上,添加圖形化界面,實現網絡圖形化界面多人聊天室。
代碼結構:
chatroom
├── client.py
├── server.py
└── settings.py
思路:
server.py
首先,在主進程(__main__)中啟動兩個進程,一個處理與客戶端的連接和消息接收以及和圖形化界面的信息傳輸,在終端中打印運行日記;另一個進程處理圖形化界面,在這個進程中,新開一個線程,監聽Pipe管道,實現與終端進程的信息交流。
client.py
結構與server.py相似,有兩個進程——終端進程和圖形化界面進程,但是新增了客戶登錄輸入用戶名的窗口,從這個窗口中獲取用戶名,使用管道將用戶名傳輸給終端進程,終端進程再將用戶名傳給服務器等待登錄請求驗證,獲得服務器發來的登錄請求驗證成功信息后,通過管道發送給圖形化進程中的管道監聽線程,使得圖形化界面進程獲得登錄成功信息,進入聊天室。
注意:
本項目運行環境為Ubuntu 16.04,可以運行。我嘗試了一下在Windows 10下運行,發現需要將__main__主進程下的全局變量作為參數發給進程,而且在windows下運行會報AttributeError: module 'signal' has no attribute 'SIGKILL'錯誤,具體原因:https://blog.csdn.net/polyhedronx/article/details/81988335。我將SIGKILL改為了SIGTERM,運行中在關閉窗口時卻會報PermissionError: [WinError 5] 拒絕訪問錯誤。除此之外,還有許多的地方會報錯,具體原因:https://segmentfault.com/a/1190000013681586。
運行截圖:
settings.py:
# settings.py HOST = 'localhost' PORT = 5555 buffersize = 1024 ADDR = HOST, PORT login_code = '' for i in [bin(ord(i)) for i in 'login']: login_code += i logout_code = '' for i in [bin(ord(i)) for i in 'logout']: logout_code += i exit_code = '' for i in [bin(ord(i)) for i in 'exit']: exit_code += i if __name__ == '__main__': for v in dir(): if not v.startswith('__'): print(v, eval(v))
server.py
# server.py import os import signal from socket import * from tkinter import * from settings import * from select import select from threading import Thread from time import ctime, sleep from tkinter.scrolledtext import ScrolledText from multiprocessing import Process, Pipe, Value def terminal(): '實現終端操作' shm_terminal_pid.value = os.getpid() # 開啟服務器 s = socket() s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) try: s.bind(ADDR) except: # 如果端口已經被占用 print('Port is already in use now!') os.kill(shm_gui_pid.value, signal.SIGKILL) os._exit(0) s.listen() # IO多路復用,監聽客戶端連接通信以及保持與gui的通信 rlist = [s, pipe_terminal] wlist = [] xlist = [] while True: # 阻塞等待IO事件 print('Waiting for connection...') try: rs, ws, xs = select(rlist, wlist, xlist) except KeyboardInterrupt: # 如果服務器退出了,通知所有客戶端並退出 for c in rlist[2:]: c.send(exit_code.encode()) for r in rlist: r.close() break for r in rs: if r is s: # 接收客戶端連接 print('IO: server') conn, addr = s.accept() print('Connected from', addr) rlist.append(conn) elif r is pipe_terminal: # 接收與pipe_gui的信息 print('IO: pipe_terminal') data = pipe_terminal.recv() # 將接收到的信息發送給所有客戶端 print(data) for c in rlist[2:]: c.send(data.encode()) else: # 接收客戶端信息 print('IO: client') data = r.recv(buffersize) if not data: # 如果客戶端退出了,關閉與客戶端的連接,並告知其他客戶端 print('客戶端退出了') r.close() rlist.remove(r) else: # 如果發來的是登錄驗證信息 if data.decode().startswith(login_code): print(data.decode()) username = data.decode().split(' ')[1] if username not in users: # 驗證成功,將成功信息發送給客戶端,並告知其他客戶端新用戶加入 data = login_code + ' Success' r.send(data.encode()) users.append(username) data = ctime() + '\n' + username + ': ' + '加入了聊天室\n' pipe_terminal.send(data) for c in rlist[2:]: if c is not r: c.send(data.encode()) else: data = login_code + ' Failure' r.send(data.encode()) elif data.decode().startswith(logout_code): print(data.decode()) username = data.decode().split(' ')[1] users.remove(username) else: # 將客戶端發送的信息群發給其他客戶端 for c in rlist[2:]: if c is not r: c.send(data) print(data.decode()) # 並將信息發送給pipe_gui以顯示在gui上 pipe_terminal.send(data.decode()) def gui(): '實現圖形化界面操作' # 設置共享內存 shm_gui_pid.value = os.getpid() main = Tk() main.title('Chatroom - Administrator') ent = Entry(main, width=100) cnt = ScrolledText(main) cnt.pack(expand=True, fill=BOTH) ent.pack(expand=True, fill=BOTH) ent.focus() main.bind('<Return>', lambda event: write(widgets)) widgets = {} widgets['ent'] = ent widgets['cnt'] = cnt thread_bridge = Thread(target=bridge, args=(widgets, )) thread_bridge.start() main.protocol('WM_DELETE_WINDOW', exit) mainloop() pipe_gui.close() def exit(): print('Exit!') pipe_gui.send(exit_code) sleep(0.1) os.kill(shm_terminal_pid.value, signal.SIGKILL) os._exit(0) def bridge(widgets): # 監聽與pipe_terminal的通信,將獲得的信息顯示在gui上 while True: print('IO: pipe_gui') data = pipe_gui.recv() print(data) widgets['cnt'].insert(END, data) def write(widgets): print('Gui <Return> Event') # 打印ent文本到cnt文本框中去 data = ctime() + '\n' + 'Administrator: ' + widgets['ent'].get() + '\n' widgets['cnt'].insert(END, data) widgets['ent'].delete(0, END) # 將信息發送給pipe_terminal pipe_gui.send(data) if __name__ == '__main__': # 創建用戶信息 users = [] # 共享內存,保存pid shm_gui_pid = Value('i', 0) shm_terminal_pid = Value('i', 0) # 創建管道,實現終端與圖形化界面的通信 pipe_terminal, pipe_gui = Pipe() # 創建兩個進程,分別實現終端和圖形化界面操作 process_terminal = Process(target=terminal) process_gui = Process(target=gui) # 開始進程 process_terminal.start() process_gui.start() # 回收進程 process_terminal.join() process_gui.join()
client.py
# client.py import os import signal from socket import * from tkinter import * from settings import * from select import select from threading import Thread from time import ctime, sleep from tkinter.scrolledtext import ScrolledText from multiprocessing import Process, Pipe, Value from tkinter.messagebox import showerror, showinfo def terminal(): '實現終端操作' shm_terminal_pid.value = os.getpid() # 開啟客戶端連接 c = socket() try: c.connect(ADDR) except: # 如果無法連接到客戶端 os.kill(shm_gui_pid.value, signal.SIGKILL) print('Failed to connect to server') os._exit(0) print('Connected to', ADDR) # IO多路復用,監聽服務端通信以及保持與gui的通信 rlist = [c, pipe_terminal, pipe_validate_terminal] wlist = [] xlist = [] # 服務器關閉信號 flag = False while True: if flag: break # 阻塞等待IO事件 try: rs, ws, xs = select(rlist, wlist, xlist) except KeyboardInterrupt: # 如果客戶端ctrl-c退出程序 for r in rlist: r.close() break for r in rs: if r is c: # 接收服務端的信息 print('IO: client') data = c.recv(buffersize) if not data: print('服務器關閉了') for r in rlist: r.close() flag = True else: # 如果發來的是登錄驗證結果信息 if data.decode().startswith(login_code): print(data.decode()) status_code = data.decode().split(' ')[1] if status_code == 'Success': pipe_validate_terminal.send(login_code) else: pipe_validate_terminal.send('bad') # 如果發來的消息是服務器退出消息 elif data.decode() == exit_code: pipe_gui.send('管理員關閉了服務器') os.kill(shm_gui_pid.value, signal.SIGKILL) os._exit(0) else: print(data.decode()) # 將信息發送給pipe_gui pipe_terminal.send(data.decode()) elif r is pipe_terminal: # 接收pipe_gui的信息 print('IO: pipe_terminal') data = pipe_terminal.recv() # 並把消息發送給服務端 c.send(data.encode()) elif r is pipe_validate_terminal: # 驗證管道 data = pipe_validate_terminal.recv() c.send(data.encode()) def gui(): '實現圖形化界面操作' shm_gui_pid.value = os.getpid() # 登錄界面 login() main = Tk() main.title('Chatroom - ' + curuser) ent = Entry(main, width=100) cnt = ScrolledText(main) cnt.pack(expand=True, fill=BOTH) ent.pack(expand=True, fill=BOTH) ent.focus() main.bind('<Return>', lambda event: write(widgets)) widgets = {} widgets['ent'] = ent widgets['cnt'] = cnt # 開啟一個線程,監聽pipe_terminal傳過來的信息 thread_bridge = Thread(target=bridge, args=(widgets, )) thread_bridge.start() main.protocol('WM_DELETE_WINDOW', exit_main) mainloop() pipe_gui.close() thread_bridge.join() def exit_main(): data = ctime() + '\n' + curuser + ': ' + '退出了聊天室\n' pipe_gui.send(data) print(data) data = logout_code + ' ' + curuser pipe_validate_gui.send(data) print(data) sleep(0.1) os.kill(shm_terminal_pid.value, signal.SIGKILL) os._exit(0) def bridge(widgets): # 監聽與pipe_terminal的通信,將獲得的信息顯示在gui上 while True: print('IO: pipe_gui') data = pipe_gui.recv() print(data) widgets['cnt'].insert(END, data) def write(widgets): print('Gui <Return> Event') # 打印ent文本到cnt文本框中去 data = ctime() + '\n' + curuser + ': ' + widgets['ent'].get() + '\n' widgets['cnt'].insert(END, data) widgets['ent'].delete(0, END) # 將信息發送給pipe_terminal pipe_gui.send(data) def login(): top = Tk() top.title('chatroom - login') Label(top, text='username:').grid(row=0, column=0) ent = Entry(top) ent.grid(row=0, column=1) ent.focus() btn = Button(top, text='confirm', command=lambda: validate(top, ent)) btn.grid(row=1, columnspan=2) top.bind('<Return>', lambda event: validate(top, ent)) top.protocol('WM_DELETE_WINDOW', exit_login) mainloop() def validate(top, ent): print('validate') if not ent.get(): showerror('Login Error', 'Empty Username!') else: username = ent.get() # 將用戶名發送給terminal,再發送給服務器以驗證 pipe_validate_gui.send(login_code + ' ' + username) data = pipe_validate_gui.recv() if data == login_code: global curuser curuser = username showinfo('Login Successful', 'Welcome to Internet Chatroom.') top.destroy() else: showerror('Login Failure', 'Username already exists!') ent.delete(0, END) def exit_login(): os._exit(0) if __name__ == '__main__': # 當前用戶名 curuser = '' # 共享內存 shm_gui_pid = Value('i', 0) shm_terminal_pid = Value('i', 0) # 創建管道,實現終端與圖形化界面的通信 pipe_terminal, pipe_gui = Pipe() # 創建管道,實現login->client->server的登錄驗證 pipe_validate_gui, pipe_validate_terminal = Pipe() # 創建兩個進程,分別實現終端和圖形化界面操作 process_terminal = Process(target=terminal) process_gui = Process(target=gui) # 開始進程 process_terminal.start() process_gui.start() # 回收進程 process_terminal.join() process_gui.join()