Python实现网络图形化界面多人聊天室 - Windows
项目名称:网络多人聊天室图形界面版本
项目思路:
server.py
服务端文件,主进程中,创建图形化界面,询问地址(主机名,端口),点击开始进入聊天室。
创建子进程,开始网络连接,使用select.select循环接收客户端连接请求,使用timeout不断检查与主进程间队列(multiprocessing.Queues)的情况
client.py
客户端文件,主进程中,创建图形化界面,询问地址(主机名,端口),点击开始以连接到客户端。
创建子进程,开始网络连接。
settings.py
默认设置
readme.txt
# settings.py
# 默认设置 HOST = "127.0.0.1"
PORT = 5555
ADDR = HOST, PORT def center(root, width=300, height=150):
# 设置窗口居中
screenWidth = root.winfo_screenwidth()
screenHeight = root.winfo_screenheight()
x = (screenWidth - width) / 2
y = (screenHeight - height) / 2
root.geometry("%dx%d+%d+%d" % (width, height, x, y))
settings.py
# server.py
# 服务端代码 from time import ctime
from multiprocessing import Process, Queue
from select import select
from socket import *
from settings import *
from tkinter import *
from tkinter.scrolledtext import ScrolledText
from tkinter import messagebox def main_gui():
# 主窗口
root = Tk() # 设置窗口居中
center(root) # 设置窗口其他属性
root.title("多人聊天室主窗口")
root.resizable(0, 0)
root.configure(bg="white")
# root.iconbitmap("python.ico") # 添加主机名(HOST)以及端口号(PORT)等输入框
pad = 10
Label(root, text="主机名(Host):").grid(row=0, column=0, padx=pad, pady=pad)
ent_host = Entry(root)
ent_host.insert(0, HOST)
ent_host.grid(row=0, column=1, padx=pad, pady=pad)
Label(root, text="端口号(Port):").grid(row=1, column=0, padx=pad, pady=pad)
ent_port = Entry(root)
ent_port.insert(0, PORT)
ent_port.grid(row=1, column=1, padx=pad, pady=pad) # 组件列表
widgets = {
"ent_host": ent_host,
"ent_port": ent_port
} # 添加确认按钮
btn_cfm = Button(root, text="新建网络聊天室", command=lambda:validate(root, widgets))
btn_cfm.grid(rowspan=2, columnspan=2, padx=pad, pady=pad) # 绑定事件
root.bind("<Return>", lambda event:validate(root, widgets)) # 主循环事件
root.mainloop() def validate(root, widgets):
# 确认按钮事件,检查是否输入有误
host, port = widgets["ent_host"].get(), widgets["ent_port"].get() # 如果端口号不是纯数字
try:
port = int(port)
except:
messagebox.showerror("错误", "端口号必须为数字!")
return # 弹出错误窗口
if not host or not port:
messagebox.showerror("错误", "主机名或端口不可为空!")
return # 有效地址
addr = (host, port) # 检查是否套接字成功
try:
server = socket(AF_INET, SOCK_STREAM)
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server.bind(addr)
server.listen(10)
except Exception as e:
messagebox.showerror("错误", f"无法在{addr}上生成套接字!")
print(e)
return
else:
# 生成两个队列
# queue负责在子进程中循环get主进程的信息
# queueu负责在父进程中循环get子进程的消息
queue = Queue()
queueu = Queue() # 生成子进程
process = Process(target=inet, args=(server, queue, queueu))
process.daemon = True
process.start() # 创建聊天室页面
chatroom_gui(root, queue, queueu) def chatroom_gui(r, queue, queueu):
# 聊天室页面
r.destroy()
root = Tk() # 设置窗口居中
center(root, 500, 500) # 最小化窗口
root.minsize(350, 350) # 菜单栏
menubar = Menu(root)
menubar.add_command(label="新建", command=None)
menubar.add_command(label="信息", command=None)
menubar.add_command(label="退出", command=root.destroy)
root.config(menu=menubar) # 文本框
text = ScrolledText(root)
text.pack(fill=BOTH) # 输入框
ent = Entry(root, bg='gray', bd=3)
ent.pack(fill=BOTH, side=BOTTOM)
ent.focus_set() # 绑定事件
root.bind("<Return>", lambda event:send(ent, queue, text)) # 设置窗口其他属性
root.title("多人聊天室 - 管理员")
root.configure(bg="white")
root.iconbitmap("python.ico") # 主循环函数
root.after(1000, recv, root, queueu, text) def recv(root, queueu, text):
if not queueu.empty():
data = queueu.get()
text.insert(END, data)
root.after(1000, recv, root, queueu, text) def send(ent, queue, text):
now = ":".join(ctime().split()[3].split(":"))
data = "[" + now + "] " + "管理员:" + ent.get() + "\n"
queue.put(data)
text.insert(END, data)
ent.delete(0, END) def inet(server, queue, queueu):
# 子进程
rlist = [server]
wlist = []
xlist = [] while True:
# 接收队列信息
if not queue.empty():
data = queue.get()
for conn in rlist:
if conn is not server:
conn.send(bytes(data, "UTF-8")) rs, ws, xs = select(rlist, wlist, xlist, 1) for r in rs:
if r is server:
conn, addr = r.accept()
rlist.append(conn)
else:
try:
data = r.recv(1024)
except:
rlist.remove(r)
else:
queueu.put(data.decode())
for conn in rlist:
if conn is not server:
conn.send(data) def main():
# 主进程
main_gui() if __name__ == "__main__":
main()
server.py
# client.py
# 客户端代码 import sys
from time import sleep, ctime
from multiprocessing import Process, Queue
from socket import *
from settings import *
from tkinter import *
from tkinter.scrolledtext import ScrolledText
from tkinter import messagebox def main_gui():
# 主窗口
root = Tk() # 设置窗口居中
center(root, 300, 200) # 设置窗口其他属性
root.title("多人聊天室主窗口")
root.resizable(0, 0)
root.configure(bg="white")
root.iconbitmap("python.ico") # 添加主机名(HOST)以及端口号(PORT)等输入框
pad = 10
Label(root, text="主机名(Host):").grid(row=0, column=0, padx=pad, pady=pad)
ent_host = Entry(root)
ent_host.insert(0, HOST)
ent_host.grid(row=0, column=1, padx=pad, pady=pad)
Label(root, text="端口号(Port):").grid(row=1, column=0, padx=pad, pady=pad)
ent_port = Entry(root)
ent_port.insert(0, PORT)
ent_port.grid(row=1, column=1, padx=pad, pady=pad)
Label(root, text="用户名(User):").grid(row=2, column=0, padx=pad, pady=pad)
ent_user = Entry(root)
ent_user.grid(row=2, column=1, padx=pad, pady=pad)
ent_user.focus_set() # 组件列表
widgets = {
"ent_host": ent_host,
"ent_port": ent_port,
"ent_user": ent_user
} # 添加确认按钮
btn_cfm = Button(root, text="加入目标聊天室", command=lambda:validate(root, widgets))
btn_cfm.grid(rowspan=2, columnspan=2, padx=pad, pady=pad) # 绑定事件
root.bind("<Return>", lambda event:validate(root, widgets)) # 主循环事件
root.mainloop() def validate(root, widgets):
# 确认按钮事件,检查是否输入有误
host, port, user = widgets["ent_host"].get(), widgets["ent_port"].get(), widgets["ent_user"].get() # 如果端口号不是纯数字
try:
port = int(port)
except:
messagebox.showerror("错误", "端口号必须为数字!")
return # 弹出错误窗口
if not host or not port or not user:
messagebox.showerror("错误", "主机名或端口或用户名不可为空!")
return # 有效地址
addr = (host, port) # 检查是否套接字成功
try:
client = socket(AF_INET, SOCK_STREAM)
client.connect(addr)
except Exception as e:
messagebox.showerror("错误", f"无法在{addr}上生成套接字!")
print(e)
return
else:
# 生成两个队列
# queue负责在子进程中循环get主进程的信息
# queueu负责在父进程中循环get子进程的消息
queue = Queue()
queueu = Queue() # 发送成功建立连接信息
client.send(bytes(f"{user}进入了聊天室。\n", "UTF-8")) # 生成子进程
process = Process(target=inet, args=(client, queue, queueu, user))
process.daemon = True
process.start() # 创建聊天室页面
chatroom_gui(root, queue, queueu, user) def chatroom_gui(r, queue, queueu, user):
# 聊天室页面
r.destroy()
root = Tk() # 设置窗口居中
center(root, 500, 500) # 最小化窗口
root.minsize(350, 350) # 菜单栏
menubar = Menu(root)
menubar.add_command(label="信息", command=None)
menubar.add_command(label="退出", command=root.destroy)
root.config(menu=menubar) # 文本框
text = ScrolledText(root)
text.pack(fill=BOTH) # 输入框
ent = Entry(root, bg='gray', bd=3)
ent.pack(fill=BOTH, side=BOTTOM)
ent.focus_set() # 绑定事件
root.bind("<Return>", lambda event:send(ent, queue, user)) # 设置窗口其他属性
root.title(f"多人聊天室 - {user}")
root.configure(bg="white")
# root.iconbitmap("python.ico") # 设置退出方法
root.protocol("WM_DELETE_WINDOW", lambda: exit(root, queue, user)) # 主循环函数
root.after(1000, recv, root, queueu, text) def exit(root, queue, user):
data = user + "退出了聊天室。\n"
queue.put(data)
sleep(1)
root.destroy()
sys.exit(0) def recv(root, queueu, text):
if not queueu.empty():
data = queueu.get()
if data == 404:
messagebox.showerror("错误", "服务端关闭了连接!")
sys.exit(0)
text.insert(END, data)
root.after(1000, recv, root, queueu, text) def send(ent, queue, user):
now = ":".join(ctime().split()[3].split(":"))
data = "[" + now + "] " + user + ":" + ent.get() + "\n"
queue.put(data)
ent.delete(0, END) def inet(client, queue, queueu, user):
# 子进程
client.setblocking(0)
while True:
if not queue.empty():
data = queue.get()
if data:
data = bytes(data, "UTF-8")
client.send(data)
try:
data = client.recv(1024)
except BlockingIOError as e:
continue
except:
queueu.put(404)
else:
data = data.decode()
queueu.put(data) def main():
# 主进程
main_gui() if __name__ == "__main__":
main()
client.py
运行截图: