gif动图中的内容bug已被修改,如下项目结构是最新版本内容,代码已更新
一. 成果预览
二.源码分享
目录结构
main.py
from src import LoginUser
if __name__ == "__main__":
# 程序启动 用户信息校验
LoginUser.LoginUser()
LoginUser.py
import json
import threading
from multiprocessing import Process
from tkinter import *
from tkinter import messagebox
from PIL import Image, ImageTk
from src import Server, Client
user_tempList = []
class LoginUser:
def __init__(self):
self.root = Tk()
self.root.title("系统登录")
self.root.geometry("400x300")
# 画布放置图片
img = Image.open('../static/1.png')
photo = ImageTk.PhotoImage(img)
Label(self.root, image=photo).grid(row=0, column=0)
# 标签 用户名密码
Label(self.root, text='用户名:').place(x=100, y=120)
Label(self.root, text='密码:').place(x=100, y=160)
# 用户名输入框
self.var_username = StringVar()
self.entry_userame = Entry(self.root, textvariable=self.var_username, font=("Arial", 10))
self.entry_userame.place(x=160, y=120)
# 密码输入框
self.var_password = StringVar()
self.entry_password = Entry(self.root, textvariable=self.var_password, font=("Arial", 10))
self.entry_password.place(x=160, y=160)
# 读取用户数据
self.readUserData()
# 登录按钮
bt_login = Button(self.root, text='登录', command=self.login)
bt_login.place(x=160, y=230)
# 注册按钮
bt_login = Button(self.root, text='注册', command=self.writeUserData)
bt_login.place(x=200, y=230)
self.root.mainloop()
# 验证逻辑
def login(self):
# 输入框获取用户名密码
usr_name = self.var_username.get()
usr_pwd = self.var_password.get()
flag = False
# 查库
for user in user_tempList:
if usr_name == user.get('username') and usr_pwd == user.get('password'):
flag = True
# 如果库中数据和用户输入匹配。登陆成功,打开用户端和服务端窗口
if flag:
login_success = "登录成功"
messagebox.showinfo(message=login_success)
self.root.destroy() # 摧毁窗口
# 开启线程运行服务端
threading.Thread(target=Server.Server).start()
# 额外进程运行客户端
Process(target=Client.Client).start()
# 查库失败,给出提示信息
else:
messagebox.showinfo(message="账号或密码错误!")
self.entry_userame.delete('0', 'end')
self.entry_userame.focus_set()
self.entry_password.delete('0', 'end')
# 读取用户数据
def readUserData(self):
try:
with open('../data/loginData.json', 'r', encoding='utf-8') as f:
# json加载文件内容
data = json.load(f)
# 如果json有内容
if data is not None or data != '':
for i in range(len(data)):
username = data[i]['username']
password = data[i]['password']
user_tempList.append({'username': username, 'password': password})
except:
messagebox.showerror(message="读取用户数据库失败")
# 将用户注册信息落库
def writeUserData(self):
# 追加新用户
username = self.var_username.get()
password = self.var_password.get()
if username != "" and password != "":
user_tempList.append({'username': username, 'password': password})
try:
with open('../data/loginData.json', 'w', encoding='utf-8') as f:
# 写入临时json列表的内容
json.dump(user_tempList, f, ensure_ascii=False)
except:
messagebox.showerror(message="写入用户信息记录失败")
messagebox.showinfo(message="用户信息注册成功")
# 更新用户数据
self.readUserData()
if __name__ == "__main__":
LoginUser()
# LoginUser.readUserData()
# LoginUser.writeUserData()
# LoginUser.login()
server.py
import json
import tkinter.messagebox
from tkinter import *
from PIL import Image, ImageTk, ImageSequence
import time
import socket
import sys
import threading
# 临时存储读的json数据
json_temp = []
class Server:
# 初始化创建socket套接字
def __init__(self):
# 获取tcp/ip套接字
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定(主机,端口号)到套接字
self.server.bind(('localhost', 8081))
# 开始TCP监听
self.server.listen(5)
# 被动接受TCP客户的连接,(阻塞式)等待连接的到来
self.client, self.addr = self.server.accept()
# 调用GUI界面
self.view()
# 析构函数,当程序退出时,执行该函数,主要是为了关闭创建的socket套接字
def __del__(self):
# 关闭套接字
self.server.close()
self.client.close()
# GUI界面绘制
def view(self):
# 初始化win窗口
self.init = Tk()
# 窗口标题
self.init.title("服务端聊天工具")
# 创建frame容器
self.frmLT = Frame(width=500, height=280)
self.frmLC = Frame(width=500, height=100)
self.frmLB = Frame(width=500, height=30)
self.frmRT = Frame(width=300, height=500)
self.frmT = Frame(width=500, height=20)
# 创建聊天框标题
self.w = Label(self.init, text="服务端信息框", width=60)
self.w.place(x=20, y=20)
# 聊天框消息列表
self.txtMsgList = Text(self.frmLT)
self.txtMsgList.tag_config('green-color', foreground='#008C00') # 创建tag
# 按钮
self.btnSend = Button(self.frmLB, text="发送", width=8, command=self.insertMsg)
self.btnCancel = Button(self.frmLB, text='取消', width=8, command=self.cancelMsg)
# 发送的消息文本
self.txtMsg = Text(self.frmLC)
# 右边栏图片背景设置
self.imgInfo = tkinter.PhotoImage(file="../static/渊洁.gif")
self.lblImage = tkinter.Label(self.frmRT, image=self.imgInfo, width=300)
self.lblImage.image = self.imgInfo
# 定时任务监听线程连接,获取消息
self.init.after(1000, self.getListenSocket) # 这里调用了after方法,即隔一段时间执行事件
# 窗口布局
self.frmT.grid(row=0, column=0, padx=1, pady=3)
self.frmLT.grid(row=1, column=0, columnspan=2, padx=1, pady=2)
self.frmLC.grid(row=2, column=0, columnspan=2, padx=1, pady=3)
self.frmLB.grid(row=3, column=0, columnspan=2)
self.frmRT.grid(row=1, column=2, rowspan=3, padx=2, pady=3)
# 固定大小
self.frmT.grid_propagate(0)
self.frmLT.grid_propagate(0)
self.frmLC.grid_propagate(0)
self.frmLB.grid_propagate(0)
self.frmRT.grid_propagate(0)
# 按钮布局
self.btnSend.grid(row=2, column=0)
self.btnCancel.grid(row=2, column=1)
self.txtMsgList.grid()
self.txtMsg.grid()
self.lblImage.grid()
# 读记录
self.readChatRecord()
# 循环调用窗口
self.init.mainloop()
# 清屏消息
def cancelMsg(self): # 取消信息
self.txtMsgList.delete('0.0', END)
'''
创建线程对象threading.Thread()
target执行运行的函数
'''
def getListenSocket(self):
t = threading.Thread(target=self.getSocketMsg)
# 设置为守护线程
t.setDaemon(True)
# 开启线程
t.start()
# 回调监听,更新消息 这里与上面的after方法一致,这样就能每隔一段时间执行
self.init.after(1000, self.getListenSocket)
'''
此函数是为了判断对方有没有发送消息,如果有,就在文本框显示对方发送的消息
'''
def getSocketMsg(self):
msg = self.client.recv(1024)
if msg is not None:
msg = msg.decode('utf-8')
strMsg = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + '\n'
self.txtMsgList.insert(END, strMsg, 'green-color')
self.txtMsgList.insert(END, "客户端发来的消息:" + msg + "\n")
# 数据落库,写入json文件
self.writeFile(strMsg, "客户端发来的消息:" + msg+ "\n")
# 将输入内容放入消息列表
def insertMsg(self): # 这是按钮事件,即button点击事件,点击后,发送文本框消息
sendMsg = self.txtMsg.get(1.0, END)
sendMsg1 = sendMsg
self.client.send(sendMsg1.encode('utf-8'))
sendTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + '\n'
self.txtMsgList.insert(END, sendTime, 'green-color')
self.txtMsgList.insert(END, sendMsg + "\n")
# 数据落库,写入json文件
self.writeFile(sendTime, "你发送的消息:" + sendMsg)
# 清除输入框的消息内容
self.txtMsg.delete(1.0, END)
# 读取json记录文件消息
def readChatRecord(self):
try:
with open('../data/chatRecordServer.json', 'r', encoding='utf-8') as f:
# json加载文件内容
json_data = json.load(f)
# 如果json有内容
if json_data is not None or json_data != '':
for i in range(len(json_data)):
sendTime = json_data[i]['sendTime']
sendMsg = json_data[i]['sendMsg']
# 将json中保留的数据放入消息列表中
self.txtMsgList.insert(END, sendTime, 'green-color')
self.txtMsgList.insert(END, sendMsg + "\n")
# 临时变量保留读取的json数据,之后方便追加写入
json_temp.append({'sendTime': sendTime, 'sendMsg': sendMsg})
except:
tkinter.messagebox.showerror(message="读取历史记录失败")
# 写入json记录文件消息
def writeFile(self, sendTime, sendMsg):
# 追加新消息
json_temp.append({'sendTime': sendTime, 'sendMsg': sendMsg})
try:
with open('../data/chatRecordServer.json', 'w', encoding='utf-8') as f:
# 写入临时json列表的内容
json.dump(json_temp, f, ensure_ascii=False)
f.flush()
except:
tkinter.messagebox.showerror(message="写入历史记录失败")
if __name__ == "__main__":
Server()
client.py
import json
import tkinter.messagebox
from tkinter import *
import cv2
import numpy
from PIL import Image, ImageTk, ImageSequence
import time
import socket
import sys
import threading
# 临时存储读的json数据,在此基础做追加写入
json_temp = []
class Client:
# 初始化创建socket套接字
def __init__(self):
# 获取tcp/ip套接字
'''
family:
socket.AF_INET:指定通过IPV4进行连接
socket.AF_INET6:指定通过IPV6进行通信
type :
socket.SOCK_STREAM:通过TCP进行通信
socket.SOCK_DGRAM: 通过udp进行通信
'''
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定(主机,端口号)到套接字
self.client.connect(('localhost', 8081))
# 调用GUI界面
self.view()
def __del__(self):
# 关闭套接字
self.client.close()
# GUI界面绘制
def view(self):
# 初始化win窗口
self.init = Tk()
# 窗口标题
self.init.title("客户端聊天工具")
# 创建frame容器
self.frmLT = Frame(width=500, height=280)
self.frmLC = Frame(width=500, height=100)
self.frmLB = Frame(width=500, height=30)
self.frmRT = Frame(width=300, height=500)
self.frmT = Frame(width=500, height=20)
# 创建聊天框标题
self.w = Label(self.init, text="客户端信息框", width=60)
self.w.place(x=20, y=20)
# 聊天框消息列表
self.txtMsgList = Text(self.frmLT)
self.txtMsgList.tag_config('green-color', foreground='#008C00') # 创建tag
# 按钮
self.btnSend = Button(self.frmLB, text="发送", width=8, command=self.insertMsg)
self.btnCancel = Button(self.frmLB, text='取消', width=8, command=self.cancelMsg)
# 发送的消息文本
self.txtMsg = Text(self.frmLC)
# 右边栏图片背景设置
self.imgInfo = tkinter.PhotoImage(file="../static/1.gif")
self.lblImage = tkinter.Label(self.frmRT, image=self.imgInfo, width=300)
self.lblImage.image = self.imgInfo
# 开启gif任务
thr = threading.Thread(target=self.gif)
thr.setDaemon(True)
thr.start()
# 定时任务监听线程连接,获取消息
self.init.after(1000, self.getListenSocket) # 这里调用了after方法,即隔一段时间执行事件
# 窗口布局
self.frmT.grid(row=0, column=0, padx=1, pady=3)
self.frmLT.grid(row=1, column=0, columnspan=2, padx=1, pady=3)
self.frmLC.grid(row=2, column=0, columnspan=2, padx=1, pady=3)
self.frmLB.grid(row=3, column=0, columnspan=2)
self.frmRT.grid(row=1, column=2, rowspan=3, padx=2, pady=3)
# 固定大小
self.frmT.grid_propagate(0)
self.frmLT.grid_propagate(0)
self.frmLC.grid_propagate(0)
self.frmLB.grid_propagate(0)
self.frmRT.grid_propagate(0)
self.btnSend.grid(row=2, column=0)
self.btnCancel.grid(row=2, column=1)
self.txtMsgList.grid()
self.txtMsg.grid()
self.lblImage.grid()
# 读记录
self.readChatRecord()
# 循环调用窗口
self.init.mainloop()
# 清屏消息
def cancelMsg(self): # 取消信息
self.txtMsgList.delete('0.0', END)
# gif动图
def gif(self):
pic_name = "../static/1.gif"
im = Image.open(pic_name)
while True:
for frame in ImageSequence.Iterator(im): # 使用迭代器
frame = frame.convert('RGB')
cv2_frame = numpy.array(frame)
show_frame = cv2.cvtColor(cv2_frame, cv2.COLOR_RGB2BGR)
cv2.imshow(pic_name, show_frame)
cv2.waitKey(250)
'''
创建线程对象threading.Thread()
target执行运行的函数
'''
def getListenSocket(self):
t = threading.Thread(target=self.getSocketMsg)
# 设置为守护线程
t.setDaemon(True)
# 开启线程
t.start()
# 回调监听,更新消息
self.init.after(1000, self.getListenSocket)
'''
此函数是为了判断对方有没有发送消息,如果有,就在文本框显示对方发送的消息
'''
def getSocketMsg(self):
msg = self.client.recv(1024)
if msg is not None:
msg = msg.decode('utf-8')
strMsg = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + '\n'
self.txtMsgList.insert(END, strMsg, 'green-color')
self.txtMsgList.insert(END, "服务端发来的消息:" + msg + "\n")
# 数据落库,写入json文件
self.writeFile(strMsg, "服务端发来的消息:" + msg+ "\n")
# 将输入内容放入消息列表
def insertMsg(self):
sendMsg = self.txtMsg.get(1.0, END)
sendMsg1 = sendMsg
self.client.send(sendMsg1.encode('utf-8'))
sendTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + '\n'
self.txtMsgList.insert(END, sendTime, 'green-color')
self.txtMsgList.insert(END, sendMsg + "\n")
# 数据落库,写入json文件
self.writeFile(sendTime, "你发送的的消息:" + sendMsg)
# 清除输入框的消息内容
self.txtMsg.delete(1.0, END)
# 读取json记录文件消息
def readChatRecord(self):
try:
with open('../data/chatRecordClient.json', 'r', encoding='utf-8') as f:
# json加载文件内容
json_data = json.load(f)
# 如果json有内容
if json_data is not None or json_data != '':
for i in range(len(json_data)):
sendTime = json_data[i]['sendTime']
sendMsg = json_data[i]['sendMsg']
# 将json中保留的数据放入消息列表中
self.txtMsgList.insert(END, sendTime, 'green-color')
self.txtMsgList.insert(END, sendMsg + "\n")
# 临时变量保留读取的json数据,之后方便追加写入
json_temp.append({'sendTime': sendTime, 'sendMsg': sendMsg})
except:
tkinter.messagebox.showerror(message="读取历史记录失败")
# 写入json记录文件消息
def writeFile(self, sendTime, sendMsg):
# 追加新消息
json_temp.append({'sendTime': sendTime, 'sendMsg': sendMsg})
try:
with open('../data/chatRecordClient.json', 'w', encoding='utf-8') as f:
# 写入临时json列表的内容
json.dump(json_temp, f, ensure_ascii=False)
f.flush()
except:
tkinter.messagebox.showerror(message="写入历史记录失败")
if __name__ == "__main__":
Client()
总结
1.本人主攻java开发。对于py存储json的操作很不适应,不是面向对象的写法。而是无限套娃。。。我并没有套娃,所以你会发现json格式很奇怪。
2.上述程序还有很多值得优化的地方。比如开窗体我额外开了一个进程。因为我试了好多种方法。多线程都无法在引用目标文件的实例对象中加载多个。
3. 程序中的IO流操作需要急切的优化,程序中写操作是读取文件的所有信息,之后暂存列表等待追加。最后覆盖原文件。可以考虑只追加到文件末。