1、服务端设置
1.1 相关配置信息
便于后期维护修改,将数据协议的相关配置和服务器配置,以及登录验证的数据库信息单独建立一个.py文件。需根据自己服务器ip修改。
#------数据协议相关配置------
REQUEST_LOGIN = '0001' #登录请求
REQUEST_CHAT = '0002' #聊天请求
RESPONSE_LOGIN_RESULT = '1001' #登录结果响应
RESPONSE_CHAT = '1002' # 聊天响应
DELIMITER = '|' #自定义协议数据分隔符
#服务器相关配置,便于修改
SERVER_IP = '172.22.11.207'
SERVER_PORT = 8090
# 数据库配置信息
DB_HOST = '172.22.11.207'
DB_PORT = 3306
DB_NAME = 'mini_chat'
DB_USER = 'root'
DB_PASS = '1162927864'
1.2 通信协议配置
利用提前约定好的消息协议字符串拼接,封装服务器响应的格式字符串处理。主要是登录结果字符串和聊天消息字符串。
from config import *
## 消息协议字符串的拼接,提前约定好的
class ResponseProtocol(object) :
"""服务器响应协议的格式字符串处理"""
@staticmethod #staticmethod用于修饰类中的方法,使其可以在不创建类实例的情况下调用方法,这样做的好处是执行效率比较高。其参数列表也不需要约定的默认参数self
def response_login_result(result, nickname, username):
"""
生成用户登录的结果字符串
:RESPONSE_LOGIN_RESULT 标识符 1001
:param result: 登录结果,0表示登录失败 1表示登录成功
:param nickname: 登录用户的昵称,如果登录失败为空
:param username: 登录用户的账号。如果登录失败则为空
:return: 返回给用户的登录结果协议字符串----拿去解析
"""
# 连接字符串数组。将字符串、元组、列表中的元素以指定的字符(分隔符)连接生成一个新的字符串 DELIMITER为分隔符
return DELIMITER.join([RESPONSE_LOGIN_RESULT, result, nickname, username])
@staticmethod
def response_chat(nickname, messages):
"""
生成返回给用户的消息字符串
:param nickname:发送消息的用户昵称
:param messages:消息正文
:return:返回给用户的消息字符串
"""
return DELIMITER.join([RESPONSE_CHAT, nickname, messages])
1.3 创建服务器套接字
封装一个服务器套接字,等待客服端响应。
import socket
from config import *
#封装了一个服务器套接字
class ServerSoket(socket.socket) : #新建一个类,继承与套接字,
"""自定义套接字,复杂初始化服务器套接字所需要的相关参数"""
def __init__(self) :
"""通常情况下,我们在子类中定义了和父类同名的方法,那么子类的方法就会覆盖父类的方法。
而super关键字实现了对父类方法的改写(增加了功能,增加的功能写在子类中,父类方法中原来的功能
得以保留)。
也可以说,super关键字帮助我们实现了在子类中调用父类的方法"""
super(ServerSoket,self).__init__(socket.AF_INET, socket.SOCK_STREAM) #设置TCP类型
#绑定服务器地址和服务器端口号,在config中定义,便于后期修改ip和端口号
self.bind((SERVER_IP, SERVER_PORT))
#设置监听模式,最大客户端数量128
self.listen(128)
封装套接字包装类,处理解码编码、字节数等重复性操作
class SocketWrapper(object):
"""套接字包装类,处理解码编码 字节数等重复性操作"""
def __init__(self,sock): #sock是一个套接字
self.sock = sock
def recv_data(self):
"""接收数据并解码为字符串"""
try: #异常处理
return self.sock.recv(512).decode('utf-8')
except:
return ""
def send_data(self,message):
"""编码之后再发送"""
return self.sock.send(message.encode('utf-8'))
def close(self):
"""关闭套接字"""
self.sock.close()
1.4 登录用户设置
利用MySQL数据库生成用户的登录信息和登录密码。
安装好MySQL,数据库中导入数据,代码如下:
# 创建数据库
create database mini_chat;
#切换数据库
use mini_chat;
#创建users表
CREATE TABLE `users` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(30) CHARACTER SET utf8 NOT NULL,
`user_password` varchar(30) CHARACTER SET utf8 NOT NULL,
`user_nickname` varchar(20) CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`user_id`)
)ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
#添加两个账号
insert into users values(0,'user1','111111','itcast1');
insert into users values(0,'user2','111111','itcast2');
insert into users values(0,'user3','111111','itcast3');
select * from users;
#删除数据库
# drop database mini_chat;
from pymysql import connect
from config import *
class DB(object):
"""数据库操作管理"""
def __init__(self):
"""初始化数据库连接"""
#连接到数据库
self.conn = connect(host=DB_HOST,
port= DB_PORT,
database= DB_NAME,
user= DB_USER,
password=DB_PASS,
)
#获取游标,创建了一个cursor实例
self.cursor = self.conn.cursor()
def close(self): #先关闭cursor,在关闭connect
"""释放数据库资源"""
self.cursor.close()
self.conn.close()
def get_one(self,sql): #返回用户信息的字典
"""使用sql语句查询用户信息"""
#执行sql语句
self.cursor.execute(sql)
#获取查询结果
query_result = self.cursor.fetchone() #获取单行数据,fetchall():获取所有行数据源,fetchmany(4):获取下4行数据
#判断是否有结果
if not query_result :
return None
#获取字段名称列表
fileds = [filed[0] for filed in self.cursor.description] #description 获取表的描述信息,
#使用字段和数据合成字典
return_data = {}
for filed, value in zip(fileds,query_result) : #序列解包
return_data[filed] = value
return return_data
if __name__ == '__main__':
db =DB()
data = db.get_one("select * from users WHERE user_name='user2'")
print(data)
db.close()
1.5 服务器主程序
from sever_socket import ServerSoket
from socket_wrapper import SocketWrapper
from threading import Thread
from config import *
from response_protocol import *
from datebase import DB
class Server(object):
"""服务器核心类"""
def __init__(self):
# 创建服务器套接字
self.server_socket = ServerSoket()
#创建请求的id和方法关联字典,可以增加不同的id类型
self.request_handle_function = {} #定义一个函数字典
#self.request_handle_function[REQUEST_LOGIN] = self.request_login_handle #不写括号,是把这个函数写进字典,不执行函数
#self.request_handle_function[REQUEST_CHAT] = self.request_chat_handle
#上面两行代码简化
self.register(REQUEST_LOGIN,self.request_login_handle)
self.register(REQUEST_CHAT,self.request_chat_handle)
#创建保存当前登陆用户的字典,键是当前用户的账号
self.clients = {}
#创建数据库管理项 初始化创建,因为不止一个用户
self.db = DB()
def register(self,request_id,handle_fuction):
"""注册消息类型和处理函数到字典里"""
self.request_handle_function[request_id] = handle_fuction
def startup(self):
while True : #接收不止一个客户端
"""获取客户端连接,并提供服务"""
#获取客户端连接
print('正在等待客户端连接')
soc,addr = self.server_socket.accept() #accept是服务器完成,不需要放到子任务中,accept获得的套接字给子任务
print('获取到客服端连接')
#使用套接字生成包装对象
client_soc = SocketWrapper(soc) #生成一个类对象,解码操作和字节数封装在包装对象中
# 收发消息,循环保持一个客户端多次收发数据,但这种死循环循环只能为一个客户端服务,为了服务多个客户端同时收发数据,选用多任务
#多线程:客服端连接上之后,立马把这个死循环交给子线程。服务端只处理连接任务,交给子线程的是accep上的套接字 服务器在一个死循环里不断获取新的子客户端连接就可以
t = Thread(target = self.requst_handle, args = (client_soc,)) #创建子线程对象,target=方法没有括号 一个数据的元组要加逗号
t.start() #开启子线程
#采用匿名函数开启子线程
#Thread(target=lambda: self.requst_handle(client_soc).start())
#关闭客户端套接字
#soc.close() 子线程中套接字再最后再关
def requst_handle(self,client_soc):
"""处理客户端请求"""
while True:
#接收客户端数据
recv_data = client_soc.recv_data() # 接收到的数据要解码.解码操作在包装类中进行
if not recv_data :
#没有接收到数据,客户端应该已经关闭,需要禁止用户发送空字符串,不会有空字符串,因为拼接必有0001
self.remove_offline_user(client_soc)
client_soc.close()
break
#解析数据,如果没有下线就要解析处理数据
""" 客户端发来的消息格式
用户发来的数据 0001|username|password
聊天内容数据 0002|username|messages
"""
parse_data = self.parse_request_text(recv_data)
#分析请求类型,并根据请求类型调用相应的处理函数
#字典方法容易扩展,有新的事件请求时只需要拓展字典就可以
#每次执行都需要初始化字典,太浪费,放到__init__里初始化
#request_handle_function = {} #定义一个函数字典
#request_handle_function[REQUEST_LOGIN] = self.request_login_handle #不写括号,是把这个函数写进字典,不执行函数
#request_handle_function[REQUEST_CHAT] = self.request_chat_handle
handle_function = self.request_handle_function.get(parse_data['request_id']) #get函数输入元组对象
if handle_function : #防止用户输入不存在的命令
handle_function(client_soc,parse_data) #执行函数
#if parse_data['request_id'] == REQUEST_LOGIN :
# self.request_login_handle()
#elif parse_data['request_id']==REQUEST_CHAT :
# self.request_chat_handle()
print('获取到解析后的内容:%s' % parse_data)
def remove_offline_user(self, client_soc):
"""客户端下线的处理"""
print("有客户端下线了")
#删除登录后下线的用户
for username,info in self.clients.items(): #Python 字典(Dictionary) items() 函数以列表返回可遍历的(键, 值) 元组数组。 usename 是client的键 info 是client的值,本身是字典
if info['sock'] == client_soc :
del self.clients['username']
break
def parse_request_text(self, text): #返回request data
"""
解析客户端发来的数据
登录信息:0001|username|password
聊天信息:0002|username|messages
"""
print('解析前客户数据'+text)
request_list = text.split('|') # 竖线分割,分割完是一个列表
#按照类型解析数据
request_data = {} #定义为一个字典类型
request_data['request_id'] = request_list[0]
if request_data['request_id'] == REQUEST_LOGIN :
#用户请求登录
request_data['username'] = request_list[1]
request_data['password'] = request_list[2]
elif request_data['request_id'] == REQUEST_CHAT :
#用户发来聊天信息
request_data['username'] = request_list[1]
request_data['messages'] = request_list[2]
return request_data
def request_chat_handle(self,client_soc,request_data):
"""处理聊天功能"""
print('收到聊天信息,准备处理')
#获取消息内容
username = request_data['username']
messages = request_data['messages']
nickname = self.clients['username']['nickname']
# 拼接发送给客户端的消息文本
msg = ResponseProtocol.response_chat(nickname,messages)
#转发消息给在线用户,不发给自己
for u_name,info in self.clients.items():
if username == u_name:
continue #continue 跳出此次循环,不需要向发送消息的账号转发数据
info['sock'].send_data(msg) # info['sock'] 获得在线用户的套接字
def request_login_handle(self,client_soc,request_data):
"""处理登录功能"""
print('收到登录请求,准备处理')
# 获取账号密码
username = request_data['username']
password = request_data['password']
# 检查是否能够登陆
ret,nickname,username = self.check_user_login(username,password)
# 登陆成功则需要保存当前用户,保存在字典里,用户名是唯一标识,登录之后才会有clien字典 所以不能直接0002|发送消息
if ret == '1' :
self.clients['username'] = {'sock':client_soc , 'nickname': nickname} #字典里包含一个字典
# 拼接返回给客户端的消息
response_text = ResponseProtocol.response_login_result(ret,nickname,username)
# 把消息发送给客户端
client_soc.send_data (response_text)
def check_user_login(self, username, password):
"""检查用户是否登陆成功,并返回结果(0代表失败,1代表成功),昵称,用户账号"""
#数据库查询用户信息
sql = "select * from users WHERE user_name='%s'" % username
result = self.db.get_one(sql)
#没有查询结果则说明,用户不存在,登录失败
if not result :
return '0','',username
#密码不匹配,说明密码错误,登录失败
if password != result['user_password'] :
return '0','',username
# 否则登录成功
return '1',result['user_nickname'],username
if __name__ == '__main__':
Server().startup()
2.客户端设置
客户端的配置与服务端大体类似,主要增加界面的设计内容。
2.1 相关配置信息
#------数据协议相关配置------
REQUEST_LOGIN = '0001' #登录请求
REQUEST_CHAT = '0002' #聊天请求
RESPONSE_LOGIN_RESULT = '1001' #登录结果响应
RESPONSE_CHAT = '1002' # 聊天响应
DELIMITER = '|' #自定义协议数据分隔符
#服务器相关配置,便于修改
SERVER_IP = '172.22.11.207'
SERVER_PORT = 8090
2.2 响应协议设置
from config import *
class RequestProtocol(object):
#定义好想发送给服务器的字符串
@staticmethod #staticmethod用于修饰类中的方法,使其可以在不创建类实例的情况下调用方法,这样做的好处是执行效率比较高。其参数列表也不需要约定的默认参数self
def response_login_result( username,password):
"""0001|user1|11111 类型|账号|密码 """
# 连接字符串数组。将字符串、元组、列表中的元素以指定的字符(分隔符)连接生成一个新的字符串 DELIMITER为分隔符
return DELIMITER.join([REQUEST_LOGIN, username, password])
@staticmethod
def response_chat(username, messages):
"""0002|user1|msg"""
return DELIMITER.join([REQUEST_CHAT, username, messages])
2.3 封装客服端套接字
import socket
from config import *
class ClientSocket (socket.socket):
"""客户端套接字自定义处理"""
def __init__(self):
#设置为tcp套接字
super(ClientSocket,self).__init__(socket.AF_INET,socket.SOCK_STREAM) #super 父类
def connect(self):
"""自动连接到服务器"""
super(ClientSocket,self).connect((SERVER_IP,SERVER_PORT))
def recv_data(self):
"""接收数据并自动转换为字符串返回"""
return self.recv(512).decode('utf-8')
def send_data(self,message):
"""接收一个字符串,并转换为字节数据发送"""
return self.send(message.encode('utf-8'))
2.4 登录界面设置
from tkinter import Tk
from tkinter import Label #文本
from tkinter import Entry #输入框
from tkinter import Frame #框架
from tkinter import Button #按钮
from tkinter import LEFT #按钮
from tkinter import END
class WindowLogin(Tk):
"""登录窗口"""
def __init__(self):
"""初始化登录窗口"""
super(WindowLogin,self).__init__() #与父类相同的初始化
#设置窗口属性
self.window_init_()
#填充控件
self.add_widgets()
#测试重置登录响应测试
#self.on_reset_button_click(lambda :self.clear_password())
def window_init_(self):
"""初始化窗口属性"""
#设置窗口标题
self.title('登录窗口')
# 设置窗口不能被拉伸
self.resizable(False,False)
#获取窗口的位置变量
window_width = 255
window_height = 95
# pos_x =屏幕宽度(高度)的一半 — 窗口宽度(高度)的一半
#获取屏幕宽度和高度
screen_width = self.winfo_screenwidth()
screen_heigth = self.winfo_screenheight()
pos_x = (screen_width-window_width)/2
pos_y =(screen_heigth-window_height)/2
#设置窗口大小和布置 geometry('宽x高+x坐标+y坐标')
self.geometry('%dx%d+%d+%d' %(window_width,window_height,pos_x,pos_y))
def add_widgets(self):
"""添加控件到窗口里"""
#用户名,包含文本(label)和输入框(entry)
username_label = Label(self) #label 生成一个字典对象
username_label['text'] ='用户名:'
username_label.grid(row=0,column=0,padx=10,pady=5) #padx 设置边距,设置一次就可以
username_entry = Entry(self,name='username_entry') #self 是显示窗口,name是输入框的标识符
username_entry['width'] = 25
username_entry.grid(row=0,column=1)
# 密码
password_label = Label(self)
password_label['text'] = '密 码:'
password_label.grid(row=1, column=0) # padx 设置边距
password_entry = Entry(self,name='password_entry') #self 是显示窗口,name是输入框的标识符
password_entry['width'] = 25
password_entry['show'] = '*'
password_entry.grid(row=1,column=1)
# 按钮区,创建一个框架,框架是在主面板里,按钮设置在框架里就可
button_frame = Frame(self,name='button_frame',pady=5)
#登录按钮
login_button = Button(button_frame, name='login_button')
login_button['text'] = ' 登录 '
login_button.pack(side=LEFT,padx=20) # pack水平布局
#重置按钮
reset_button = Button(button_frame,name='reset_button')
reset_button['text'] = ' 重置 '
reset_button.pack(side=LEFT) #pack水平布局
button_frame.grid(row=2, columnspan=2) # columnspan 第三行沾满
def on_reset_button_click(self,command):
"""登录按钮的响应注册"""
reset_button = self.children['button_frame'].children['reset_button']
reset_button['command'] = command
def on_login_button_click(self,command): #command 是一个函数,点击按钮后要做的事情
"""登录按钮的响应注册"""
login_button = self.children['button_frame'].children['login_button'] # button_frame 是框架的标识符,之后框架里有两个按钮分别是 login_button .reset_button
login_button['command'] = command # 点击获得响应处理
def get_username(self):
"""获取用户名"""
return self.children['username_entry'].get()
def get_password(self):
"""获取密码"""
return self.children['password_entry'].get()
def clear_username(self):
"""清空用户名输入框"""
self.children['username_entry'].delete(0,END)
def clear_password(self):
"""清空密码输入框"""
self.children['password_entry'].delete(0, END)
def on_window_close(self,command):
"""关闭窗口的响应注册"""
self.protocol('WM_DELETE_WINDOW',command)
if __name__ == '__main__':
window = WindowLogin()
window.mainloop()
2.5 聊天界面设置
from tkinter import Toplevel, UNITS # 整个程序里面只能有一个TK窗口,所以这里不能导入TK类,可以开启多个顶层窗口,tk是根窗口
from tkinter.scrolledtext import ScrolledText
from tkinter import Text
from tkinter import Button
from tkinter import END
from time import localtime,strftime,time
class WindowChat(Toplevel):
def __init__(self):
super(WindowChat,self).__init__()
#设置窗口大小,可以随意拖动,所以不需要设置xy坐标
self.geometry('%dx%d' %(795,505))
#设置窗口大小不可修改
self.resizable(False,False)
#添加组件
self.add_widget()
#测试
#self.on_send_button_click(lambda :print(self.append_message('1','1')))
def add_widget(self):
"""添加组件的方法"""
#聊天区 可滚动的文本框
chat_text_area = ScrolledText(self)
chat_text_area['width'] = 110
chat_text_area['height'] = 30
chat_text_area.grid(row=0,column=0,columnspan=2)
chat_text_area.tag_config('green',foreground='#008B00') #green 是名字
chat_text_area.tag_config('system',foreground='red')
self.children['chat_text_area'] = chat_text_area # 给组件起名
#输入区
chat_input_area = Text(self,name='chat_input')
chat_input_area['width'] = 100
chat_input_area['height'] = 7
chat_input_area.grid(row=1,column=0,pady=10)
# 发送按钮
send_button_area = Button(self,name='send_button')
send_button_area['width'] = 5
send_button_area['height'] = 2
send_button_area['text'] ='发送'
send_button_area.grid(row=1,column=1)
def set_title(self,title):
"""设置标题"""
self.title('欢迎%s进入聊天室!' % title)
def on_send_button_click(self,command):
"""注册事件,当发送按钮被点击时执行 command 方法"""
self.children['send_button']['command'] = command
def get_inputs(self):
"""获取文本框内容"""
return self.children['chat_input'].get(0.0,END) #浮点数
def clear_input(self):
"""清空文本框内容"""
self.children['chat_input'].delete(0.0,END)
def append_message(self,sender,message):
"""添加一条信息到聊天区"""
send_time = strftime('%Y-%m-%d %H:%M:%S',localtime(time()))
send_info = '%s:%s\n' %(sender,send_time)
self.children['chat_text_area'].insert(END,send_info,'green') # 从哪开始 信息内容 颜色
self.children['chat_text_area'].insert(END, ' ' +message+'\n')
#向下滚动屏幕
self.children['chat_text_area'].yview_scroll(3,UNITS) # 自动滚动三行
def on_window_closed(self,command):
"""注册关闭窗口时执行的指令"""
self.protocol('WM_DELETE_WINDOW',command)
if __name__ == '__main__':
WindowChat().mainloop()
2.6 客服端设置
import sys
from window_login import WindowLogin
from request_protocol import RequestProtocol
from client_socket import ClientSocket
from threading import Thread
from config import *
from tkinter.messagebox import showinfo #专门用来显示对话框
from window_chat import WindowChat
class Client(object):
def __init__(self):
"""初始化客户端内容"""
#初始化登录窗口
self.window = WindowLogin()
self.window.on_reset_button_click(self.clear_inputs)
self.window.on_login_button_click(self.send_login_data)
self.window.on_window_close(self.exit)
#初始化聊天窗口
self.window_chat = WindowChat()
self.window_chat.withdraw() #隐藏窗口
self.window_chat.on_send_button_click(self.send_chat_data) #点击按钮发送消息
self.window_chat.on_window_closed(self.exit)
#创建客户端套接字
self.conn = ClientSocket()
#初始化消息处理函数
#创建响应的id和方法关联字典,可以增加不同的id类型
self.response_handle_function = {} #定义一个函数字典
#调用方法把函数和键对应起来
self.register(RESPONSE_LOGIN_RESULT,self.response_login_handle)
self.register(RESPONSE_CHAT,self.response_chat_handle)
#在线用户名
self.username = None
#程序运行的标记
self.is_running = True
def register(self,response_id,handle_fuction):
"""注册消息类型和处理函数到字典里"""
self.response_handle_function[response_id] = handle_fuction
def startup(self):
"""开启窗口"""
self.conn.connect()
Thread(target=self.response_handle).start() #创建并开启一个子线程,专门来接收服务器发来的消息
self.window.mainloop()
def clear_inputs(self):
"""清空窗口内容"""
self.window.clear_password()
self.window.clear_username()
def send_login_data(self):
"""发送登录信息到服务器"""
#获取用户输入的账号密码
username = self.window.get_username()
password = self.window.get_password()
#生成协议文本
request_text = RequestProtocol.response_login_result(username,password)
#发送给服务器
print('发送给服务的文本是'+request_text)
self.conn.send_data(request_text)
#测试代码,需要时刻接收消息和发送消息,所以开启线程
#recv_data = self.conn.recv_data()
#print(recv_data)
def send_chat_data(self):
"""获取聊天框内容,发送到服务器"""
message = self.window_chat.get_inputs()
print('a %s a' % message)
self.window_chat.clear_input() #清空输入框
#拼接聊天信息
request_text = RequestProtocol.response_chat(self.username , message)
#发送消息内容
self.conn.send_data(request_text)
#把消息内容显示到聊天区
self.window_chat.append_message('我',message)
def response_handle(self):
"""不断接收服务器新消息"""
while self.is_running : #不用True是为了退出程序时可以跳出循环
#获取服务器内容
recv_data = self.conn.recv_data()
print('收到服务器的消息', recv_data)
#解析消息内容
response_data = self.parse_response_data(recv_data)
#根据消息内容分别进行处理,把函数封装在字典里调用
handle_function = self.response_handle_function.get(response_data['response_id']) # get函数输入元组对象
if handle_function : # 防止用户输入不存在的命令
handle_function(response_data) # 执行函数
#if response_data['response_id'] == RESPONSE_LOGIN_RESULT :
# self.response_login_handle(response_data)
#elif response_data['response_id'] == RESPONSE_CHAT :
# self.response_chat_handle(response_data)
@staticmethod
def parse_response_data(recv_data): #返回 response_data
"""解析消息内容
登录响应消息: 1001|成功or失败|昵称|账号
聊天响应信息: 10002|发送者昵称|消息内容
"""
#使用协议约定的符号来切割消息
response_data_list = recv_data.split(DELIMITER)
#机械消息的各个组成部分
response_data = dict() #dict 新的空字典
response_data['response_id'] = response_data_list[0]
if response_data['response_id'] == RESPONSE_LOGIN_RESULT :
#登录结果响应
response_data['result'] = response_data_list[1]
response_data['nickname'] = response_data_list[2]
response_data['username'] = response_data_list[3]
elif response_data['response_id'] == RESPONSE_CHAT :
# 聊天信息的响应
response_data['nickname'] = response_data_list[1]
response_data['message'] = response_data_list[2]
return response_data
def response_login_handle(self,response_data):
"""登录结果响应"""
print('接收到登录信息',response_data)
result = response_data['result']
if result == '0' :
showinfo('提示','登录失败,账号或密码错误!') #参数1是标题 参数2是提示内容
#print('登录失败')
return
#登录成功获取用户信息
showinfo('提示', '登录成功!')
nickname = response_data['nickname']
self.username = response_data['username'] #需要把username设置为属性,之后发消息用,加self之后其他就可以调用
#print('%s的昵称为%s,已经登录成功' % (username,nickname))
#显示聊天窗口
self.window_chat.set_title(nickname)
self.window_chat.update() #刷新界面
self.window_chat.deiconify()
#隐藏登录窗口
self.window.withdraw()
3 运行结果显示
3.1 所有文件显示
3.2 运行服务器
3.3 运行客户端
可同时登录多个账户,密码在MySQL数据库中设置。