Python中基于TCP网络通信协议的多人聊天室

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数据库中设置。

 

  • 0
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值