网络图形化界面多人聊天室
在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'错误,具体原因:。我将SIGKILL改为了SIGTERM,运行中在关闭窗口时却会报PermissionError: [WinError 5] 拒绝访问错误。除此之外,还有许多的地方会报错,具体原因:。
运行截图:
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()