python多线程+socket实现聊天室(最终版)


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流操作需要急切的优化,程序中写操作是读取文件的所有信息,之后暂存列表等待追加。最后覆盖原文件。可以考虑只追加到文件末。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

最难不过坚持丶渊洁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值