网络图形化界面多人聊天室 - 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() + ' ' + username + ': ' + '加入了聊天室 ' 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() + ' ' + 'Administrator: ' + widgets['ent'].get() + ' ' 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() + ' ' + curuser + ': ' + '退出了聊天室 ' 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() + ' ' + curuser + ': ' + widgets['ent'].get() + ' ' 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()