一、聊天室的整体架构
1.twisted:实现服务端,以及事件驱动
- Twisted是用Python实现的基于事件驱动的网络引擎框架。
2.socket:实现客户端,以及事件驱动
3.tkinter:实现客户端界面
4.MySQL:用户信息存储,以及登录、注册逻辑处理
二、聊天室的具体实现
1.服务端实现事件驱动
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):
if self in self.users.keys():
del self.users[self]
def dataReceived(self, data):
data = data.decode('utf-8')
data_dict = json.loads(data)
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 = {
}
def buildProtocol(self, addr):
print(type(addr),addr)
return Chat(self.users)
if __name__ == '__main__':
reactor.listenTCP(1200, ChatFactory())
print ("开始进入监听状态...")
reactor.run()
2.服务端的逻辑处理
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
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
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"))
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.客户端实现事件驱动、以及页面跳转逻辑
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.客户端的界面布局
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='账 号')
self.label_password = tkinter.Label(self.root, text='密 码')
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='昵 称')
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')
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)
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()
self.root.protocol('WM_DELETE_WINDOW', self.on_closing)
三、聊天室最终效果