zoukankan      html  css  js  c++  java
  • 43.QQ聊天软件GUI窗口编写

    QQ聊天软件代码功能编写


    一,Tkinter聊天界面编写

     1,聊天软件客户端界面开发-1

    • Tkinter的模块(“TK接口”)是标准的Python接口从Tk的GUI工具包
    • https://i.cnblogs.com/EditPosts.aspx?opt=1   ###Tkinter官方文档:关于相关函数具体看文档介绍
    • Tkinter模块:自带的,跨平台
      • 做GUI:界面软件
      • 不难,但是复杂,东西多,窗口控件多,设计,美感,抠图
    • 界面开发设计
      • 设置窗口:像素大小
      • 拜访控件:按钮,复选框,文本框
    • root控件流程:
      • 1:导入模块   import tkinter
      • 2:创建主窗口root = tkinter.Tk()
      • 3:创建其他窗口以及控件(按钮,文本框,等等)
      • 4:摆放到root上
      • 5:开启root的事件循环:root.mainloop()
      • 6:如果不需要该窗口,可以使用root.destory()

    • 组件:
      • 文本框
        • tkinter.Text()
          • insert():插入数据 
            • tkinter.END,从末尾插入
          • delete(‘0.0’,tkinter.END):删除全部文本框数据
          • tag_config('标签名',foreground='颜色',):标签配置,设置字体颜色, 第一个字符串参数是标签名,第二个是颜色
          • get('0.0',thinter.END):阅读拿出文本框的内容
      • 按钮
        • tkinter.Button()
          • text:按钮名字
          • command:回调函数,按钮点击时的工作任务列表框
      • 列表框
        • tkinter.Listbox()
          • insert(tkinter.END, 追加的数据)
    • 事件绑定
      • 双击事件:<Double-Button-1>
      • 事件绑定的函数必须有一个参数,这个参数就是事件
      • self.right_listbox.curselection()
        • 返回当前选择的列表框索引位置值,返回结果为单个数据的元组
        • index = self.listbox_user.curselection()[0]
      • self.talk_user_name = self.listbox_user.get(index)
        • index   #获取的当前列表框中的索引位置的值

    • root.title('**')
      • 设置上标字符
    • grid()
      • x,y轴的摆放方式
      • row:行
      • column:列
      • rowspan:行宽,跨行数
      • pady:表格间距,x轴
      • padx:y轴的表格间距
    • sticky
      • 对其方向,上北下南左西右东N,S,W,E
    • grid_propagate(0)
      • 0代表禁止容器窗口缩放
    • root.resizable(width=False,height=Flase)
      • 禁止窗口缩放
    • root.winfo_screenwidth()
      • 自动获取用户的屏幕宽度
    • root.winfo_screenheight()
      • 自动获取用户的屏幕高度
    • root.geometry("%dx%d%+d%+d" % (width, height, xoffset, yoffset))
      • 设置控件所处的屏幕位置
    • tkinter.Label(所属的主窗口,text='名字',font=("黑体",9, "bold"))
      • 添加标签lable
    • tkinter.Entry()
      • 单行文本框
      • 获取数据直接使用get() 函数即可,不需要传参。
    • from tkinter import messagebox   ##展示报错信息
      • 'showerror', 'showinfo', 'showwarning'
        • title=None, message=None
      • messagebox.showerror(title='登录失败',message='登录失败')

      

      


    • 客户端登录及聊天窗口代码编写整合如下:
      from tkinter import Tk
      import tkinter
      import time
      from multiprocessing.pool import ThreadPool
      import socket
      from tkinter import messagebox
      import pickle
      import _pickle
      #scroolbar
      class TalkRoot:
      	'''
      		实现用户聊天的主要界面
      	'''
      	def __init__(self,user_name,client,user_id):
      		self.root = Tk()
      		self.root.title('欢迎你:%s' % user_name)
      		self.client = client
      		self.user_id = user_id
      		#-----------------创建进程池----------------------
      		self.thread_pool = ThreadPool(5) #5个线程的线程池
      		#-------功能变量----------------------
      		self.talk_user_name = '' #你要聊天的人
      		self.client = client	#套接字
              #------------------设置窗口所处的用户界面位置及窗口大小在屏幕中间------------
      		self.root_width=550
      		self.root_height=420
      		self.user_screen_width = self.root.winfo_screenwidth()#自动获取用户的屏幕宽度
      		self.user_screen_height = self.root.winfo_screenheight()#用户的屏幕高
      		self.root.geometry("%dx%d%+d%+d" %
      			(self.root_width,
      			self.root_height,
      			(self.user_screen_width - self.root_width) / 2,
      			(self.user_screen_height - self.root_height) / 2,)
      			) 		
      		#-------容器--------
      		#上: 输出
      		self.frame_top = tkinter.Frame(width=380,height=270)
      		#中: 输入
      		self.frame_center = tkinter.Frame(width=380,height=100)
      		#下: 按钮
      		self.frame_bottom = tkinter.Frame(width=380,height=30)
      		#右: 列表
      		self.frame_right = tkinter.Frame(width=170,height=400)
      
      		#--------控件--------
      		#上: 输出
      		self.text_output = tkinter.Text(self.frame_top,height=260)
      		self.text_output.tag_config('time_stamp',foreground='green')  #标签配置颜色
      		self.text_output.tag_config('self_msg',foreground='blue')#标签配置颜色
      		self.text_output.tag_config('other_msg',foreground='red')#标签配置颜色
      		#中: 输入
      		self.text_input = tkinter.Text(self.frame_center)
      		self.text_input.bind('<Return>',self.button_send_msg)
      		#下: 按钮
      		self.button_send = tkinter.Button(self.frame_bottom,text='发送',command=self.button_send_msg) #发送按钮
      		self.button_cancle = tkinter.Button(self.frame_bottom,text='取消',command=self.fc_button_cancle) #取消按钮
      		#右: 列表
      		self.listbox_user = tkinter.Listbox(self.frame_right,width=150,height=25)
      		self.listbox_user.bind('<Double-Button-1>',self.get_talk_user)#绑定双击事件
      		#--------摆放生效容器---------
      		self.frame_top.grid(row=0,column=0,padx=2,pady=5)
      		self.frame_center.grid(row=1,column=0,padx=2,pady=5)
      		self.frame_bottom.grid(row=2,column=0,sticky=tkinter.E)
      		self.frame_right.grid(row=0,column=1,rowspan=3,padx=5,pady=5)
      
      		#--------摆放生效控件---------
      		self.text_output.grid()
      		self.text_input.grid()
      		self.button_send.grid(row=0,column=1,padx=300)
      		self.button_cancle.grid(row=0,column=0)
      		self.listbox_user.grid()
      
      		#--------禁止容器窗口缩放grid_--------------------
      		self.frame_top.grid_propagate(0)
      		self.frame_center.grid_propagate(0)
      		self.frame_bottom.grid_propagate(0)
      		self.frame_right.grid_propagate(0)
      		#------------专门开一个接受消息聊天的线程------------
      		self.thread_pool.apply_async(func=self.check_recv_msg) #非阻塞线程
      		#------------窗口循环---------------------------------------
      		self.root.mainloop()
      
      	def check_recv_msg(self):
      		while True:	#循环窗口聊天
      			try:
      				data = pickle.loads(self.client.recv(1024))
      			except _pickle.UnpicklingError:
      				messagebox.showinfo(title='服务端消息错误',message='服务端发来无法解析的错误')
      				self.root.destroy()
      				exit()
      			except Exception as e:
      				messagebox.showinfo(title='服务端断开',message='服务端断开')
      				self.root.destroy()
      				exit()
      			if data.get('flag') == 'flush':
      				#服务端返回最新在线用户
      				self.thread_pool.apply_async(func=self.flush_onlien_user,args=(data,))
      			elif data.get('flag') == 'forward':
      				#服务端返回别人发来的消息
      				self.thread_pool.apply_async(func=self.show_other_msg,args=(data,))
      			elif not data:
      				#服务端发来空消息,断开连接
      				exit()
      
      	def show_other_msg(self,data):
      		'''
      			展示别人发来的消息
      		'''
      		send_name = data.get('data')['send_name']
      		other_msg = data.get('data')['message']
      		print('别人发来了消息:',other_msg)
      		time_stamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime() )  + '
      '
      		self.text_output.insert(tkinter.END,time_stamp,'green')
      		self.text_output.insert(tkinter.END,'%s:%s' % (send_name,other_msg),'red')
      
      	def flush_onlien_user(self,data):
      		self.listbox_user.delete(0,tkinter.END) #清空在线用户列表
      		for user in data.get('data'):
      			#user : {id:name}
      			id_ = list(user.keys())[0]
      			name = list(user.values())[0]
      			self.listbox_user.insert(tkinter.END, '%s:%s' % (name,id_))
      
      	def button_send_msg(self,events=None):
      		self_msg = self.text_input.get('0.0',tkinter.END)
      		self.text_input.delete('0.0',tkinter.END)
      		if not self_msg.isspace():
      			time_stamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime() )  + '
      '
      			self.text_output.insert(tkinter.END,time_stamp,'time_stamp')
      			self.text_output.insert(tkinter.END,' ' + self_msg,'self_msg')
      		self.thread_pool.apply_async(func=self.clear_input_text)
      		#id:name
      		if self.talk_user_name:
      			print('捕捉到在线用户,准备发送消息')
      			recv_id = self.talk_user_name.split(':')[0] 
      			print(recv_id)
      			send_data = {
      				'flag': 'send',
      				'data': {
      					'send_id': self.user_id,
      					'recv_id': recv_id,
      					'message': self_msg,
      				}
      			}
      			self.client.send(pickle.dumps(send_data))
      		else:
      			'''
      				群发功能
      			'''
      			send_data = {
      				'flag': 'part',
      				'data': {
      					'send_id': self.user_id,
      					'message': self_msg,
      				}
      			}
      
      	def clear_input_text(self):
      		time.sleep(0.1)
      		self.text_input.delete('0.0',tkinter.END)
      		
      	def get_talk_user(self,events):
      		'''
      			获取当前聊天用户
      		'''
      		if self.listbox_user.size() != 0:
      			index = self.listbox_user.curselection()[0]
      			self.talk_user_name = self.listbox_user.get(index)
      			print(self.talk_user_name)
      
      	def fc_button_cancle(self):
      		'''
      			聊天界面的销毁
      				销毁窗口
      				客户端套接字释放
      		'''
      		exit()
      
      	def __del__(self):
      		self.client.close() #套接字释放
      		self.root.destroy() #销毁窗口
      
      class LoginRoot():
      	def __init__(self):
      		self.root = tkinter.Tk()
      		self.root.title('登陆')
      		self.root.resizable(width=False,height=False) #禁止窗口缩放调整
      
      		self.root_width = 197
      		self.root_height = 75
      
      		#自适应,首先要获取到用户的屏幕分辨率
      		self.user_screen_width = self.root.winfo_screenwidth() #用户的屏幕也宽度
      		self.user_screen_height = self.root.winfo_screenheight() #用户的屏幕高度
      
      		self.root.geometry("%dx%d%+d%+d" % #设置窗口所处的用户界面位置及窗口大小
      			(self.root_width,
      			self.root_height,
      			(self.user_screen_width - self.root_width) / 2,
      			(self.user_screen_height - self.root_height) / 2,)
      			)
      
      		#创建提示label
      		self.label_id = tkinter.Label(self.root,text='I D:',font=("黑体",10, "bold")) # font设置字体样式
      		self.label_name = tkinter.Label(self.root,text='昵称:',font=("黑体",10, "bold"))
      		#摆放提示label
      		self.label_id.grid(row=0,column=0,sticky=tkinter.W)
      		self.label_name.grid(row=1,column=0,)
      		#创建输入单行文本框
      		self.entry_id = tkinter.Entry(self.root)
      		self.entry_name = tkinter.Entry(self.root)
      		#摆放文本框
      		self.entry_id.grid(row=0,column=1)
      		self.entry_name.grid(row=1,column=1)
      		#创建功能按钮
      		self.button_login = tkinter.Button(self.root,text='登陆',command=self.user_login)
      		self.button_cancle = tkinter.Button(self.root,text='取消',command=self.user_cancle)
      		#摆放功能按钮
      		self.button_login.grid(row=2,column=1,sticky=tkinter.E)
      		self.button_cancle.grid(row=2,column=0,sticky=tkinter.W)
      		self.root.mainloop()
      
      	def user_login(self):
      		'''
      			要确定用户提供的ID是唯一的
      			把昵称传递给TalkRoot
      		'''
      		user_id = self.entry_id.get()
      		user_name = self.entry_name.get()
      
      		send_login_msg = {
      			'flag': 'login',
      			'data':{
      				'id': user_id,
      				'name': user_name
      			}
      		}
      		ip = '192.168.1.101'
      		port = 8000
      		try:
      			self.client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
      			self.client.connect( (ip,port) )
      		except Exception as e:
      			print('登陆连接时的错误:',e)
      			messagebox.showerror(title='登陆失败',message='登陆失败')
      		else:
      			self.client.send(pickle.dumps(send_login_msg))
      			data = pickle.loads(self.client.recv(1024))
      			if data.get('flag') == 'login':
      				if data.get('data')['state']:
      					#登陆成功
      					self.root.destroy()
      					tkr = TalkRoot(user_name,self.client,user_id)
      				else:
      					self.client.close()
      					messagebox.showwarning(title='登陆失败',message='%s' % (data.get('data')['message']))
      
      	def user_cancle(self):
      		self.root.destroy()
      
      def main():
      	lgr = LoginRoot()
      
      
      if __name__ == '__main__':
      	main()

    二,聊天类软件后台架构及请求数据设计 

    一,服务端功能

    • 1:接收用户登陆
      • 主线程,消息收集
      • 服务端套接字,唯一ID的校验
    • 2:告知在线用户
      • 另开线程,遍历访问服务端保存的客户端序列数据
    • 3:处理在线用户的消息发送
      • 主线程,消息收集 一旦捕获到了一个消息是要发送给别人的
      • 另开线程,处理消息转发,发给另外一个在线的套接字

    二,客户端功能

    • 1:登陆
      • 套接字,发送ID和昵称即可
    • 2:接收处理服务端发来的在线用户列表
    • 3:发送消息,展示消息

    三,请求数据结构体 / 客户端

    • 1:pickle.dumps() #打包二进制数据体
      •   
      • { #客户端登陆发送的数据体
            'flag': 'login', #数据标志位
            'data': {
                'id': '123456', #用户ID
                'name: 'xxxxx', #用户昵称
            }
        }
        
        { #客户端发送数据体
            'flag': 'send' #数据标志位
            'data': {
            'send_id': '123455', #发送者的ID
                'recv_id': '123456', #接收人的ID号
                'message: 'xxxxx', #发给别人的数据
            }
        }
        
        { #客户端注册数据体
            'flag': 'register', #数据标志位
            'data': {
                'id': '123456', #用户ID
                'name: 'xxxxx', #用户昵称
                'password': 'xxxxx', #
            }
        }

    四,服务端保存在线用户数据体

    • 1:直接连进来的,不进入这个数据体,不算有效用户
    • 2:某个线程,捕捉这个套接字发来的第一个login的数据体
    • 3:判断ID:
      • ID不重复:登陆保存
      • ID重复:当用户发送重复id的时,返回的数据
        •   
          {
              'flag':'login',
              'data': {
                  'state':True,/False
                  'message':'登陆成功'
              }
          }  
    • 4:成功登陆的用户保存在字典中的数据结构体
      • #字典:
        dict_onlien_user = {
            xxxx:{
                'name':'666',
                'socket':client,
            },
            xxxx:{,
                name:'666',
                socket:client,
            },
        }
      • onlien_user.get(recv_id)['socket'].send(message)  #如果再转发消息的时候,准确快速的找到接收者 
    • 5:成功的用户发来的数据,要给别人发消息,服务端怎么保存这个数据?
      •   
        { #客户端发送数据体
            'flag': 'send' #数据标志位
            'data': {
                'send_id': '123455', #发送者的ID
                'recv_id': '123456', #接收人的ID号
                'message: 'xxxxx', #发给别人的数据
            }
        }
    • 6:客户端接收服务端发来的在线用户数据结构体
      • list_onlien_user = [
            {'id1':'name1'},
            {'id2':'name2'},
        }
    • 7: 刷新的用户列表数据是一个列表,现在要更改
      • { #服务端发给客户端的数据体
            'flag': 'flush' #数据标志位
            'data': [在线用户列表]
        }
        
    • 8: 发给其他用户的数据,怎么发过去?
      • { #服务端发给客户端的数据体
            'flag': 'forward' #数据标志位
            'data': {
                'send_name': '123455', #发送者的ID
                'message': '123456', #接收人的ID号
            }
        }

    五,代码编写如下:

    #服务端
    import socket
    from multiprocessing.pool import ThreadPool
    import pickle
    import copy
    import time
    
    class TalkServer:
        def __init__(self,ip,port):
            self.ip = ip
            self.port = port
            self.socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.socket_server.setblocking(0) #服务端套接字设置为非阻塞
            self.socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #设置端口复用
            self.socket_server.bind( (self.ip, self.port) )
    
            self.dict_online_user = {} #保存未来的在线用户字典
            self.list_online_user = [] #保存即将发送给别人在现在用户列表
            self.thread_pool = ThreadPool(10)
    
            self.is_flush = False #用来判断是否需要刷新在线用户的
    
        def run(self, num_=5):
            self.socket_server.listen(num_)
            #2:用户发送给别人的消息处理
            self.thread_pool.apply_async(func=self.inform_online_user_list)
            self.thread_pool.apply_async(func=self.check_send_data)
            while True:
                '''
                    接收用户来访
                '''
                try:
                    client,client_addr = self.socket_server.accept()
                except BlockingIOError:
                    pass
                else:
                    print('有人来了:[%s|%s]' % client_addr)
                    #没报错,那么就是用户真正的连接到了,accept捕捉到了返回值
                    #1:用户登陆发来的消息处理,开一个线程去等待他发来对应的数据,或者说判断这个ID是否是重复的
                    self.thread_pool.apply_async(func=self.check_login_data,args=(client,))
                    #login数据
    
        def inform_online_user_list(self):
            '''
                告知客户端现在的在线用户,就是直接发送一个在线用户的列表
            '''
            while True:
                if self.is_flush:
                    time.sleep(1)
                    print('刷新在线用户')
                    send_online_user_msg = {
                        'flag': 'flush',
                        'data': self.list_online_user,
                    }
    
                    for id in self.dict_online_user:
                        client = self.dict_online_user[id]['socket']
                        client.send(pickle.dumps(send_online_user_msg))
                    self.is_flush = False
    
        def check_send_data(self):
            '''
                处理用户的发送数据,要发给别人拉
                还会碰到用户发来的断开连接的数据
            '''
            while True:
                list_onlien_user_bak = copy.copy(self.list_online_user)
                for data in list_onlien_user_bak: #[{'id1':'name1'},{'id1':'name1'}...]
                    id = list(data.keys())[0]
                    client = self.dict_online_user[id]['socket'] #套接字拿到
                    name = self.dict_online_user[id]['name']
                    #print('当前遍历到的在线用户,%s:%s' % (id,name))
                    try:
                        recv_msg = client.recv(1024) #非阻塞形式获取客户端发来的数据
                    except BlockingIOError as e:
                        pass #当前该在线用户并没有发送任何消息
                    except ConnectionResetError as e:
                        '''
                            客户端断开连接
                        '''
                        pass
                    else:
                        #1:判断是否是断开的数据
                        if not recv_msg:
                            print('离开了:%s,%s' % (id,name))
                            client.close()
                            del self.dict_online_user[id]
                            self.list_online_user.remove(data)
                            self.is_flush = True
                        else:
                            '''
                                { #发送数据体
                                    'flag': 'send' #数据标志位
                                    'data': {
                                    	'send_id': '123455', #发送者的ID
                                        'recv_id': '123456', #接收人的ID号
                                        'message: 'xxxxx', #发给别人的数据
                                    }
                                }
                            '''
                            clear_recv_msg = pickle.loads(recv_msg)
                            if clear_recv_msg.get('flag') == 'send': #确实是要给别人发送消息
                                clear_recv_data = clear_recv_msg.get('data')
                                if clear_recv_data: #发来的数据中确实有data
                                    send_id = clear_recv_data.get('send_id')
                                    recv_id = clear_recv_data.get('recv_id')
                                    message = clear_recv_data.get('message')
    
                                    if self.dict_online_user.get(recv_id):
                                        recv_socket = self.dict_online_user.get(recv_id)['socket'] #接收者的套接字
                                    else:
                                        continue
                                    send_name = self.dict_online_user[send_id]['name']#发送者的名字
                                    '''
                                    { #服务端发给客户端的数据体
                                        'flag': 'forward' #数据标志位
                                        'data': {
                                        	'send_name': '123455', #发送者的ID
                                            'message': '123456', #接收人的ID号
                                        }
                                    }
                                    '''
                                    forward_data = {
                                        'flag': 'forward',
                                        'data': {
                                            'send_name': send_name,
                                            'message': message,
                                        }
                                    }
                                    recv_socket.send(pickle.dumps(forward_data))
                                    print('消息转发完毕:
    ',forward_data)
    
        def check_login_data(self,client):
            '''
                { #登陆数据体
                    'flag': 'login', #数据标志位
                    'data': {
                        'id': '123456', #用户ID
                        'name: 'xxxxx', #用户昵称
                    }
                }
            '''
            recv_msg = client.recv(1024) #接收用户发来的login数据体
            if recv_msg: #发来的数据是有效的
                clear_recv_msg = pickle.loads(recv_msg)
                if clear_recv_msg.get('flag') == 'login': #发来的确实是login的数据
                    clear_recv_data = clear_recv_msg.get('data')
                    if clear_recv_data: #发来的数据中确实有data
                        id = clear_recv_data.get('id')
                        name = clear_recv_data.get('name')
                        #判断ID是否重复
                        if id in self.dict_online_user:
                            print('[%s:%s]该用户出现重复ID' % (id, name))
                            send_login_msg = {
                                'flag': 'login',
                                'data': {
                                    'state': False,
                                    'message': 'ID使用重复',
                                }
                            }
                            client.send(pickle.dumps(send_login_msg)) #给客户端返回错误信息
                        elif not id or not name:
                            print('[%s:%s]该用户所需数据为空' % (id, name))
                            send_login_msg = {
                                'flag': 'login',
                                'data': {
                                    'state': False,
                                    'message': 'ID或名字为空',
                                }
                            }
                            client.send(pickle.dumps(send_login_msg)) #给客户端返回错误信息
                        else:
                            '''
                            dict_onlien_user = {
                                xxxx:{
                                    'name':'666',
                                    'socket':client,
                                ...
                                },
                            '''
                            client.setblocking(0) #真正存储在在线字典里的用户套接字是个非阻塞的
                            self.dict_online_user[id] = {
                                'name': name,
                                'socket': client,
                            }
                            self.list_online_user.append( {id:name} )
                            #刷新在线用户
                            print('在线用户已添加')
                            self.is_flush = True
                            send_login_msg = {
                                'flag': 'login',
                                'data': {
                                    'state': True,
                                    'message': '登陆成功',
                                }
                            }
                            client.send(pickle.dumps(send_login_msg))
                            return
            client.close() #用户数据无效,关闭连接
    
    def main():
        ip = ''
        port = 8000
        tks = TalkServer(ip, port)
        tks.run()
    
    if __name__ == '__main__':
        main() 

      运行结果:

  • 相关阅读:
    Next Permutation
    SpringMVC配置信息
    Servlet详解(转载)
    Length of Last Word
    Maximum Subarray**
    Divide Two Integers
    Generate Parentheses***
    http解码-2
    编码-1
    扫描工具对比
  • 原文地址:https://www.cnblogs.com/zhangan/p/10295167.html
Copyright © 2011-2022 走看看