Python语言基于Socket实现聊天室(包含登录、注册功能)

一、聊天室的整体架构

1.twisted:实现服务端,以及事件驱动
  • Twisted是用Python实现的基于事件驱动的网络引擎框架。
2.socket:实现客户端,以及事件驱动
3.tkinter:实现客户端界面
4.MySQL:用户信息存储,以及登录、注册逻辑处理

二、聊天室的具体实现

1.服务端实现事件驱动
#--coding:utf-8--
# @Time    : 2020/11/30/030 23:04
# @Author  : panyuangao
# @File    : chat_room_server.py
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver  # 事件处理器
from twisted.internet import reactor
from model import login_model,register_model,chat_model
import json

class Chat(LineReceiver):
    def __init__(self, users):
        self.users = users

    def connectionLost(self, reason):#断开连接时候自动触发,从users字典去掉连接对象
        if self in self.users.keys():
            del self.users[self]

    def dataReceived(self, data):  # 对返回内容开始做处理,只要收到客户端消息,自动触发此方法
        data = data.decode('utf-8')
        data_dict = json.loads(data)
        #根据type字段的值,进入对应的逻辑
        if data_dict["type"] == "login":
            login_model.login(self,data_dict)
        elif data_dict["type"] == "register":
            register_model.register(self,data_dict)
        elif data_dict["type"] == "chat":
            chat_model.chat(self,data_dict)

class ChatFactory(Factory):#实现了一个工厂类,只会实例化一次
    def __init__(self):
        self.users = {
     }  # 昵称映射到聊天实例,key-->登录成功的连接对象 value:昵称

    def buildProtocol(self, addr):#此方法必须要实现
        print(type(addr),addr)
        return Chat(self.users)#返回一个处理具体业务请求的对象,参数传递了字典:存所有登录成功的连接对象

if __name__ == '__main__':#设定监听端口和对象
    reactor.listenTCP(1200, ChatFactory())#使用Tcp协议,实例化ChatFactory
    print ("开始进入监听状态...")
    reactor.run()#开始监听
2.服务端的逻辑处理
  • 登录逻辑
#--coding:utf-8--
# @Time    : 2020/12/28/028 23:07
# @Author  : panyuangao
# @File    : login.py
# @PROJECT : chatRoom
from db import user_db
import json

def login(self,data_dict):#登录逻辑
    account = data_dict["account"].strip()
    password = data_dict["password"].strip()
    data = {
     }
    if account and password:
        code,msg,nickname = login_check(account, password)
    elif not account:
        code = "700001"
        msg = "登录账号不能为空"
    elif not password:
        code = "700002"
        msg = "登录密码不能为空"

    if code == "000000":
        self.users[self] = nickname
        data["nickname"] = nickname
    data["type"] = "login"
    data["code"] = code
    data["msg"] = msg
    data = json.dumps(data)
    self.sendLine(data.encode("utf-8"))

def login_check(account, password): #登录校验
    findUser = user_db.findUser(account)
    findNickname = user_db.findNickname(account,password)
    if len(findUser) == 0:
        data = ("700003", "账号【%s】未注册,请注册后再登录!" % account, None)
    elif len(findNickname) == 0:
        data = ("700004", "密码错误,请重新输入", None)
    else:
        nickname = findNickname[0][0]
        data = ("000000", "账号【%s】登录成功" % account, nickname)
    return data
  • 注册逻辑
#--coding:utf-8--
# @Time    : 2020/12/28/028 23:10
# @Author  : panyuangao
# @File    : register.py
# @PROJECT : chatRoom
from db import user_db
import json

def register(self, data_dict):  # 注册逻辑
    account = data_dict["account"].strip()
    password = data_dict["password"].strip()
    nickname = data_dict["nickname"].strip()
    data = {
     }
    if account and password and nickname:
        code, msg = register_check(account, password, nickname)
    elif not account:
        code = "600002"
        msg = "注册账号不能为空"
    elif not password:
        code = "600003"
        msg = "注册密码不能为空"
    elif not nickname:
        code = "600004"
        msg = "注册昵称不能为空"

    if code == "000000":
        self.users[self] = nickname
        data["nickname"] = nickname

    data["type"] = "register"
    data["code"] = code
    data["msg"] = msg
    data = json.dumps(data)
    self.sendLine(data.encode("utf-8"))


def register_check(account,password,nickname):
    findUser = user_db.findUser(account)
    if len(findUser) > 0:
        data = ("600005","账号【%s】已注册" %account)
    else:
        user_db.insertUser(account,password,nickname)
        data = ("000000","账号【%s】注册成功,点击“确定”进入聊天页面" %account)
    return data
  • 聊天逻辑
#--coding:utf-8--
# @Time    : 2020/12/28/028 23:52
# @Author  : panyuangao
# @File    : chat_model.py
# @PROJECT : chatRoom
import json
def chat(self, data_dict):  # 聊天逻辑
    message = data_dict["message"].strip()
    for user, nickname in self.users.items():
        data = {
     }
        data["type"] = "chat"
        current_nickname = self.users[self]
        data["nickname"] = "%s" % current_nickname
        data["isMy"] = "no"
        if user == self:
            data["isMy"] = "yes"
        data["message"] = message
        data = json.dumps(data)
        user.sendLine(data.encode("utf-8"))
  • 数据库操作
#--coding:utf-8--
# @Time    : 2020/12/27/027 22:41
# @Author  : panyuangao
# @File    : login.py
# @PROJECT : chatRoom
from db import handle_mysql

def findUser(account):
    mysql = handle_mysql.MySQL()
    sql_select_account = "SELECT * FROM user WHERE account = '%s'" %account
    user = mysql.select(sql_select_account)
    mysql.conn_close()
    return user

def findNickname(account,password):
    mysql = handle_mysql.MySQL()
    sql_select_account_password = "SELECT nickname FROM user WHERE account = '%s' and password = '%s'" % (account, password)
    nickname = mysql.select(sql_select_account_password)
    mysql.conn_close()
    return nickname

def insertUser(account, password, nickname):
    mysql = handle_mysql.MySQL()
    sql_insert = "INSERT INTO user(account,password,nickname) VALUES('%s','%s','%s')" % (account, password, nickname)
    mysql.insert(sql_insert)
    mysql.conn_close()
3.客户端实现事件驱动、以及页面跳转逻辑
#--coding:utf-8--
# @Time    : 2020/12/15/015 0:07
# @Author  : panyuangao
# @File    : login.py
# @PROJECT : chatRoom
import tkinter
from tkinter import messagebox
import json,time
import threading
import select
from socket import *
from client import client_draw

class ChatRoom(object):
    def connect(self): #配置连接
        self.s = socket(AF_INET, SOCK_STREAM)
        remote_host = gethostname() #获取计算机名称
        port = 1200  #设置端口号
        self.s.connect((remote_host, port))  # 发起连接
        print("从%s成功连接到%s" %(self.s.getsockname(),self.s.getpeername()))
        return self.s

    def recive(self,s): # 监听消息
        self.my = [s]
        while True:
         rs, ws, es = select.select(self.my, [], [])
         if s in rs:
             try:
                 data = s.recv(1024).decode('utf-8')
                 data_dict = json.loads(data)
                 type = data_dict["type"]
                 if type == "login":
                     if "000000" == data_dict["code"]:
                         nickname = data_dict["nickname"]
                         self.chat_interface(nickname)
                     else:
                         tkinter.messagebox.showinfo(title='登录提示', message=data_dict["msg"])
                 elif type == "register":
                     if "000000" == data_dict["code"]:
                         nickname = data_dict["nickname"]
                         tkinter.messagebox.showinfo(title='进入聊天室', message=data_dict["msg"])
                         self.chat_interface(nickname)
                     else:
                         tkinter.messagebox.showinfo(title='注册提示', message=data_dict["msg"])
                 elif type == "chat":
                     message = data_dict["message"]
                     nickname = data_dict["nickname"]
                     isMy = data_dict["isMy"]
                     times = " "+ nickname + "\t" + time.strftime("%H:%M:%S",time.localtime())+ '\n'
                     self.txtMsgList.insert(tkinter.END, times,"DimGray")
                     if "yes" == isMy:
                        self.txtMsgList.insert(tkinter.END,  " "+ message + "\n\n",'DarkTurquoise')
                     else:
                         self.txtMsgList.insert(tkinter.END, " " + message + "\n\n", 'Black')
                     self.txtMsgList.see(tkinter.END) # 插入消息时,自动滚动到底部

             except Exception as e:
                 print(e)
                 exit()


    def register_interface(self): # 进入注册界面
        client_draw.draw_register(self)

    def chat_interface(self,nickname): #进入聊天页面
        client_draw.draw_chat(self,nickname)

    def return_login_interface(self): #返回登录页面
        self.label_nickname.destroy() #将不需要控件先销毁
        self.input_nickname.destroy()
        self.label_password.destroy()
        self.input_password.destroy()
        client_draw.draw_login(self)


    def verify_register(self): # 获取输入框内容,进行注册验证
        account = self.input_account.get()
        password = self.input_password.get()
        nickname = self.input_nickname.get()
        try:
            register_data = {
     }
            register_data["type"] = "register"
            register_data["account"] = account
            register_data["password"] = password
            register_data["nickname"] = nickname
            data = json.dumps(register_data)
            self.s.send(data.encode('utf-8'))
        except Exception as e:
            print(e)

    def verify_login(self): # 获取输入框内容,进行登录信息验证
        account = self.input_account.get()
        password = self.input_password.get()
        try:
          login_data = {
     }
          login_data["type"] = "login"
          login_data["account"] = account
          login_data["password"] = password
          data = json.dumps(login_data)
          self.s.send(data.encode('utf-8'))
        except Exception as e:
          print(e)


    def sendMsg(self):#获取输入框内容,发送消息
        message = self.txtMsg.get('0.0', tkinter.END).strip()
        if not message:
            tkinter.messagebox.showinfo(title='发送提示', message="发送内容不能为空,请重新输入")
            return
        self.txtMsg.delete('0.0', tkinter.END)
        try:
          chat_data = {
     }
          chat_data["type"] = "chat"
          chat_data["message"] = message
          data = json.dumps(chat_data)
          self.s.send(data.encode('utf-8'))
        except Exception as e:
          print(e)

    def sendMsgEvent(self,event):#发送消息事件
        if event.keysym =='Return':
            self.sendMsg()

    def on_closing(self):  # 聊天页面,点击右上角退出时执行
        if messagebox.askokcancel("退出提示", "是否离开聊天室?"):
            self.root.destroy()

def main():
    chatRoom = ChatRoom()
    client = chatRoom.connect()
    t = threading.Thread(target=chatRoom.recive, args=(client,)) # 创建一个线程,监听消息
    t.start()
    chatRoom.root = tkinter.Tk()  # 创建主窗口,用于容纳其它组件
    client_draw.draw_login(chatRoom) # 登录界面控件创建、布局
    tkinter.mainloop() # 进入事件(消息)循环


if __name__ == '__main__':
    main()


4.客户端的界面布局
#--coding:utf-8--
# @Time    : 2020/12/29/029 22:48
# @Author  : panyuangao
# @File    : client_draw.py
# @PROJECT : chatRoom
import tkinter

def draw_login(self): #登录页面
    self.root.title("聊天室登录页面")  # 给主窗口设置标题内容
    self.root.geometry('450x300') # 设置主窗口大小
    self.canvas = tkinter.Canvas(self.root, height=200, width=500)  # 创建画布
    self.label_account = tkinter.Label(self.root, text='账 号')  # 创建一个`Label`名为`账 号: `
    self.label_password = tkinter.Label(self.root, text='密 码')  # 创建一个`Label`名为`密 码: `
    self.input_account = tkinter.Entry(self.root, width=30)  # 创建一个账号输入框,并设置尺寸
    self.input_password = tkinter.Entry(self.root, show='*', width=30)  # 创建一个密码输入框,并设置尺寸
    self.login_button = tkinter.Button(self.root, command=self.verify_login, text="登 录", width=10) #登录按钮
    self.register_button = tkinter.Button(self.root, command=self.register_interface, text="注 册", width=10) #注册按钮

    # 登录页面各个控件进行布局
    self.label_account.place(x=90, y=70)
    self.label_password.place(x=90, y=150)
    self.input_account.place(x=135, y=70)
    self.input_password.place(x=135, y=150)
    self.login_button.place(x=120, y=235)
    self.register_button.place(x=250, y=235)


def draw_register(self): #注册页面控件创
    self.login_button.destroy()
    self.register_button.destroy()
    self.root.title("聊天室注册页面")
    self.root.geometry('450x300')
    self.canvas = tkinter.Canvas(self.root, height=200, width=500)  # 创建画布
    self.label_nickname = tkinter.Label(self.root, text='昵 称')  # 创建一个"Label",名为:"昵 称"
    self.input_nickname = tkinter.Entry(self.root, width=30)  # 创建一个昵称输入框,并设置尺寸
    self.register_submit_button = tkinter.Button(self.root, command=self.verify_register, text="提交注册", width=10) #创建注册按钮
    self.return_login_button = tkinter.Button(self.root, command=self.return_login_interface, text="返回登录",width=10)  # 创建注册按钮


    self.label_account.place(x=90, y=70)
    self.label_password.place(x=90, y=130)
    self.input_account.place(x=135, y=70)
    self.input_password.place(x=135, y=130)
    self.label_nickname.place(x=90, y=190)
    self.input_nickname.place(x=135, y=190)
    self.register_submit_button.place(x=120, y=235)
    self.return_login_button.place(x=250, y=235)


def draw_chat(self,nickname):
    self.root.title("【%s】的聊天室页面" %nickname)  # 给主窗口设置标题内容
    self.root.geometry('520x560')
    # 创建frame容器
    self.frmLT = tkinter.Frame(width=500, height=320)
    self.frmLC = tkinter.Frame(width=500, height=150)
    self.frmLB = tkinter.Frame(width=500, height=30)

    self.txtMsgList = tkinter.Text(self.frmLT)
    self.txtMsgList.tag_config('DimGray', foreground='#696969',font=("Times", "11"))  #设置消息时间字体样式
    self.txtMsgList.tag_config('DarkTurquoise', foreground='#00CED1', font=("Message", "13"),spacing2=5) #设置自己的消息字体样式
    self.txtMsgList.tag_config('Black', foreground='#000000', font=("Message", "13"), spacing2=5)  # 设置其它人的消息字体样式

    self.txtMsg = tkinter.Text(self.frmLC)
    self.txtMsg.bind("", self.sendMsgEvent) # 触发键盘的回车按键事件,发送消息
    self.btnSend = tkinter.Button(self.frmLB, text='发送', width=12, command=self.sendMsg)
    self.labSend = tkinter.Label(self.frmLB, width=55) #创建空的Label在左边占个位置,便于发送按钮靠右

    # 窗口布局
    self.frmLT.grid(row=0, column=0, columnspan=2, padx=10, pady=10)
    self.frmLC.grid(row=1, column=0, columnspan=2, padx=10, pady=10)
    self.frmLB.grid(row=2, column=0, columnspan=2, padx=10, pady=10)

    # 固定大小
    self.frmLT.grid_propagate(0)
    self.frmLC.grid_propagate(0)
    self.frmLB.grid_propagate(0)

    self.labSend.grid(row=0, column=0)
    self.btnSend.grid(row=0, column=1)
    self.txtMsgList.grid()
    self.txtMsg.grid()

    # WM_DELETE_WINDOW 不能改变,这是捕获命令
    self.root.protocol('WM_DELETE_WINDOW', self.on_closing)

三、聊天室最终效果

  • 登录页面

Python语言基于Socket实现聊天室(包含登录、注册功能)_第1张图片

  • 注册页面

Python语言基于Socket实现聊天室(包含登录、注册功能)_第2张图片

  • 聊天页面

Python语言基于Socket实现聊天室(包含登录、注册功能)_第3张图片

Python语言基于Socket实现聊天室(包含登录、注册功能)_第4张图片

你可能感兴趣的:(pytho项目,python,tkinter,mysql,socket)