本篇内容讲述图形化界面的聊天室的搭建过程。

用到的知识点:

1,tkinter模块

2,udp协议构建聊天室

3,多进程接收消息和发送消息

4,进程间的通信

难点:

1,用tkinter编写的图形化界面作为客户端,客户端需要接收和发送消息,都是阻塞函数,需要解决状态不一致的问题

2,tkinter的模块在启动了mainloop之后,就只能根据界面上的事件进行响应,而且不支持在程序内容增加线程/进程进行处理接收服务器转发的消息,这样就导致接收消息成为重要的难点。

3,基于第二个问题的情况,只能考虑在主程序中进行接收消息,然后启动一个线程/进程处理图形化界面中的界面更新,这样就把这个同时接收和发送的问题解决了。

4,但是主程序收到的消息要传递给子进程/线程,此时考虑的是用进程间通信,方式有管道,消息队列,共享内存,信号,信号量,套接字等方式,在此程序中采用的是消息队列。

5,消息队列,在接受到消息之后,将处理后的消息put()放入到消息队列中,然后在接收的地方将消息队列中的消息get()循环全部接收,将这些得到的消息进行插入到需要显示的地方

6,需要注意的地方有:

tkinter用于显示消息的内容的控件scrolltext,在没有消息时是不能编辑的,因此需要设置不可编辑属性,在程序中,需要插入消息的时候需要修改不可编辑属性,然后插入数据,插入完全之后,又设置为不可编辑状态

tkinter用于显示消息的主窗口,最好是能够容纳完整的消息,不出现消息只显示一半的情况。

以下是代码主体内容

服务器端:

import socket
import select
import sys
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind(("127.0.0.1",9527))enrc = "^^^^"
addrlist = []
namelist = []
# data = "welcome %s to join us"
while True:# print(addrlist)# print(namelist)#recvfrom接收UDP消息,参数是每次接收消息的大小,返回接收到的内容#因为服务器在收到消息之后,会直接转发消息到客户端,所以服务器不存在阻塞,#所以也就没有必要开启多线程/进程print('waiting for recv')data,address = s.recvfrom(1024)# print(data.decode())# s.sendto(data,address)#这里测试发送消息,测试完了之后关闭# s.sendto(b'server recv your msg',address)if enrc in data.decode():#udp是无连接的协议,所以需要写应用层协议来确保消息有效传输name = data.decode().split(enrc)[0]# print("name:",name)if data.decode().endswith("quit"): #代表退出,将用户从通讯列表删除,后续消息不转发给taaddrlist.remove(address)namelist.remove(name)  for i in range(len(addrlist)):#将消息发送给所有在通讯列表中的客户端s.sendto((name+" quit").encode(),addrlist[i])       else:if address in addrlist:#避免用户发来一条消息就添加一次,造成用户接受消息出现了多次的情况passelse:#地址不在列表中,代表是新的客户端连接,则添加该地址和用户addrlist.append(address)namelist.append(name)# print("received string :",data.decode("utf-8"))#sendto 发送UDP消息,,参数分别为消息和发送方的地址,返回发送的字节数# print(data.decode())# print("addrlist:",addrlist)for x in addrlist:#将data转发给地址列表中的所以用户s.sendto(data,x)
s.close()

客户端:

#coding=utf-8
# import sys
# sys.setdefaultencoding("utf-8")
import tkinter,time,threading,random
from multiprocessing import Process,Queue
from tkinter import Frame,Text,END,Scrollbar
import socket
import os
import time
from tkinter import *
import tkinter.messagebox
import time
import random
import threading
from multiprocessing import Process,Queue
from tkinter import scrolledtext
q = Queue()
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
class GuiPart():def __init__(self,master,queue,endCommand,name='alice',enrc='^^^^',HOST = "127.0.0.1",PORT = 9527):self.queue=queue# self.t = Tk()# self.t.title('超级聊天窗口')     # 窗口名称# self.t.resizable(0, 0)           # 禁止调整窗口大小# self.s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)# self.s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)self.enrc = enrcself.HOST = HOSTself.PORT = PORTself.recvdmsg = 'testmsg'# data = self.name + self.enrc# self.s.sendto(data.encode("utf-8"),(self.HOST,self.PORT))#第一列self.frmA1 = Frame(width=180, height=30)self.frmA2 = Frame(width=180, height=280)self.frmA3 = Frame(width=180, height=160) self.frmA4 = Frame(width=180, height=30) #第二列self.frmB1 = Frame(width=350, height=326)self.frmB2 = Frame(width=350, height=144)self.frmB3 = Frame(width=350, height=30)#第三列self.frmC1 = Frame(width=200, height=30)self.frmC11= Frame(width=200, height=350)self.frmC2 = Frame(width=200, height=90)self.frmC3 = Frame(width=200, height=30)self.frmA1.grid(row=0, column=0, padx=10, pady=3)self.frmA2.grid(row=1, column=0, padx=10)self.frmA3.grid(row=2, column=0, rowspan=1)self.frmA4.grid(row=3, column=0, rowspan=1)self.frmB1.grid(row=0, column=1, columnspan=1, rowspan=2, padx=1, pady=3)self.frmB2.grid(row=2, column=1, columnspan=1, padx=1, pady=1)self.frmB3.grid(row=3, column=1, columnspan=1, padx=1)self.frmC1.grid(row=0, column=2, rowspan=1, padx=1, pady=1)self.frmC11.grid(row=1, column=2, rowspan=1, padx=1, pady=1)  self.frmC2.grid(row=2, column=2, rowspan=1, padx=1, pady=1)self.frmC3.grid(row=3, column=2, padx=1)# #固定大小self.frmA1.grid_propagate(0)self.frmA2.grid_propagate(0)self.frmA3.grid_propagate(0)#frmA4.grid_propagate(0)self.frmB1.grid_propagate(0)self.frmB2.grid_propagate(0)self.frmB3.grid_propagate(0)self.frmC1.grid_propagate(0)self.frmC11.grid_propagate(0)  self.frmC2.grid_propagate(0)self.frmC3.grid_propagate(0)#1.Text控件self.txtMsgList = scrolledtext.ScrolledText(self.frmB1,wrap=tkinter.WORD)                          #frmB1表示父窗口self.txtMsgList.config(state=DISABLED)#创建并配置标签tag属性# self.txtMsgList.tag_config('greencolor',foreground='#008C00')self.txtMsg = Text(self.frmB2);self.txtMsg.bind("<KeyPress-Return>", self.sendMsgEvent)    #事件绑定,定义快捷键self.timeText=Text(self.frmC2,font=("Times", "28", "bold italic"),height=1,bg="PowderBlue")self.timeText2=Text(self.frmC2,fg="blue",font=("Times", "12","bold italic"))self.txtText=Text(self.frmC11,font=("Times", "11",'bold'),  #字体控制width=24,height=15,                  #文本框的宽(in characters )和高(in lines) (not pixels!)spacing2=5,                          #文本的行间距bd=2,                                #边框宽度padx=5,pady=5,                       #距离文本框四边的距离selectbackground='blue',             #选中文本的颜色state=NORMAL)                        #文本框是否启用 NORMAL/DISABLEDself.txtText.insert(END,'1.一个平平常常的日子,细蒙蒙的雨丝夹着一星半点的雪花,正纷纷淋淋的向大地飘洒着,时令已快到惊蛰,雪当然再不会存留,往往还没等落地,就消失的无踪无影了,黄土高原严寒而漫长的冬天看来就要过去,但那真正温暖的春天还远远没有到来。\n2、这时候,他也体验到了类似孙少平的那种感觉:只有繁重的体力劳动才使精神上的痛苦变为某种麻木,以致思维局限在机械性活动中。\n3、哭,笑,都是因为欢乐,哭的人知道而笑的人不知道,这欢乐是多少痛苦所换来的。')self.txtText.config(state=DISABLED)# insert(插入位置,插入内容)#2.Button控件self.btnSend = Button(self.frmB3, text='发 送', width = 8,cursor='heart', command=self.sendMsg)self.btnCancel = Button(self.frmB3, text='取消', width = 8,cursor='shuttle', command=self.cancelMsg)self.btnSerch=Button(self.frmA1, text='搜索联系人',         #button的显示内容width = 9,height=1,               #宽和高cursor='man',                     #光标样式     command =self.message)                 #回调函数#3.Entry控件self.entrySerch=Entry(self.frmA1, bd =3,width=14) # entrySerch=Entry(frmA1, bd =3,width=14,show='*')   #输入值以掩码显示    #4.Scrollbar控件self.scroLianxi = Scrollbar(self.frmA2,width=22,cursor='pirate',troughcolor="blue") #5.Listbox控件self.listLianxi = Listbox(self.frmA2, width=22,height=16,yscrollcommand = self.scroLianxi.set )  #连接listbox 到 vertical scrollbarself.Linkman=['曹操','刘备','孙权','关羽','张飞','赵云','马超','黄忠','张郃','姜维','夏侯惇','魏延','张辽','周瑜','贾诩','典韦','吕布','袁绍','袁术','貂蝉','董卓','华佗','诸葛亮','郭嘉','孙策','孙坚','太史慈','鲁肃','黄盖','程普','程昱','司马懿','曹丕','曹植','曹睿']for line in self.Linkman:self.listLianxi.insert(END, "  联系人   ------   " + str(line))self.scroLianxi.config( command = self.listLianxi.yview )   #scrollbar滚动时listbox同时滚动#6.Canvas控件self.imgCanvas=Canvas(self.frmA3,bg='ivory')#7.Radiobutton控件var = IntVar()                                    #设置variable和value可以保证只有一个按钮被按下self.R1 = Radiobutton(self.frmA4, text="多边形", variable=var, value=1,command=self.Polygon)self.R2 = Radiobutton(self.frmA4, text="椭圆", variable=var, value=2,command=self.Oval)#8.Menubutton控件self.colorMenubt =  Menubutton (self.frmA4, text="画板颜色", relief=RAISED )#9.Menu控件self.colorMenubt.menu  =  Menu(self.colorMenubt,tearoff = 0 )self.colorMenubt["menu"]  = self.colorMenubt.menuself.colorMenubt.menu.add_checkbutton ( label="红色",command=self.red)self.colorMenubt.menu.add_checkbutton ( label="绿色",command=self.green)self.colorMenubt.menu.add_checkbutton ( label="蓝色",command=self.blue)self.colorMenubt.menu.add_separator()       #添加菜单分隔符self.colorMenubt.menu.add_checkbutton ( label="连续色",command=self.color1)#10.Scale控件self.sizeScale = Scale(self.frmC3,length=135,width=18,from_=10, to=35,orient=HORIZONTAL,command=self.fontSize,cursor='star',showvalue=0,         #不显示数值sliderlength=30,     #滑块的长度             troughcolor='ivory') #滑动条底色self.sizeScale.set(20)                      #设置滑块的初始值#11.Label控件self.sizeLabel = Label(self.frmC3,width=8,height=1,bd=1, relief=RIDGE)self.nameLabel = Label(self.frmC1, text='Favorite Book List',font="Times 16 bold italic")#12.Spinbox控件self.txtSpinbox = Spinbox(self.frmC11,width=24,command=self.txtlist)self.txtSpinbox.config(values=['NO.1 《平凡的世界》  路遥','NO.2 《看见》  柴静','NO.3 《亲爱的安德烈》龙应台'])# self.txtSpinbox.config(state=DISABLED)self.btnSend.grid(row=0, column=0)self.btnCancel.grid(row=0, column=1)self.btnSerch.grid(row=0,column=1)self.nameLabel.grid()self.sizeLabel.grid(row=0,column=0)self.txtMsgList.grid()self.txtMsg.grid()self.entrySerch.grid(row=0,column=0)self.scroLianxi.grid(row=0,column=1,ipady=120)self.listLianxi.grid(row=0,column=0)self.imgCanvas.grid(row=0,column=0,sticky=N)self.colorMenubt.grid(row=0,column=0)self.R1.grid(row=0,column=1) self.R2.grid(row=0,column=2)self.timeText.grid(row=0,column=0)self.timeText2.grid(row=1,column=0,sticky=E+W)self.txtText.grid(row=1,column=0,pady=5)self.sizeScale.grid(row=0,column=1)self.txtSpinbox.grid(row=0,column=0)name = input("input your name\t")self.name = nameself.strMsg = self.name + time.ctime() + '\n 'def sendMsg2Server(self):self.strMsg = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime()) + '\n 'info = self.txtMsg.get('0.0', END)info = info.strip()print('send msg: ',info)if info =='quit':s.sendto((self.name+ self.enrc+"quit").encode("utf-8"),(self.HOST,self.PORT))# breakelse:data = self.name + self.enrc + info s.sendto(data.encode("utf-8"),(self.HOST,self.PORT))self.txtMsg.delete('0.0', END)def sendMsg(self):#发送消息self.sendMsg2Server()def cancelMsg(self):                #取消消息self.txtMsg.delete('0.0', END)def sendMsgEvent(self,event):        #发送消息事件if event.keysym == "Return":  #按回车键可发送self.sendMsg()def red(self): #Canvas背景色控制self.imgCanvas.config(bg='red')def green(self):self.imgCanvas.config(bg='green')def blue(self):self.imgCanvas.config(bg='blue')def color1(self):names = ['red', 'green', 'blue','yellow','white','SlateGray','SpringGreen','LightSteelBlue','Cyan','BlueViolet','GhostWhite']for i in names:self.imgCanvas.config(bg=i)time.sleep(0.4)self.imgCanvas.update()def Oval(self): #这里要换成按钮self.imgCanvas.create_oval(110, 20, 170, 120)def Polygon(self): #这里要换成按钮self.imgCanvas.create_polygon(20, 20, 110, 50,100,110,40,120)def fontSize(self,ev=None):          #字体缩放self.timeText2.config(font='Helvetica -%d bold' % self.sizeScale.get())self.sizeLabel.config(text='字号:%d'% self.sizeScale.get())def txtlist(self):                  #字典实现--书名与内容的对应book={'NO.1 《平凡的世界》  路遥':'1.一个平平常常的日子,细蒙蒙的雨丝夹着一星半点的雪花,正纷纷淋淋的向大地飘洒着,时令已快到惊蛰,雪当然再不会存留,往往还没等落地,就消失的无踪无影了,黄土高原严寒而漫长的冬天看来就要过去,但那真正温暖的春天还远远没有到来。\n2、这时候,他也体验到了类似孙少平的那种感觉:只有繁重的体力劳动才使精神上的痛苦变为某种麻木,以致思维局限在机械性活动中。\n3、哭,笑,都是因为欢乐,哭的人知道而笑的人不知道,这欢乐是多少痛苦所换来的。','NO.2 《看见》  柴静':'1.写本身也是一种发现自己的过程,你不写永远都知道自己身上发生了什么。\n2.保持对不同论述的警惕,才能保持自己的独立性。\n3.灵魂变得沉重,是因为爱,因为所爱的东西,眼睁睁在失去,却无能为力。\n4.我可能做不到更好了,但还是要像朱光潜说的那样,此时,此地,此身,此时我能做的事情绝不推诿到下一时刻,此地我能做的事情绝不换另一种境地再去做,此身我能做的事绝不妄想与他人来替代。\n5.如果带着强烈的预设和反感,你就没有办法真的认识这个人。','NO.3 《亲爱的安德烈》龙应台':'1.我也要求你读书用功,不是因为我要你跟别人比较成就,而是因为,我希望你将来会拥有会选择的权利,选择有意义、有时间的工作,而不是被迫谋生。 当你的工作在你的心中有意义,你就有成就感。当你的工作给你时间,不剥夺你的生活,你就有尊严。成就感和尊严,给你快乐。\n2.农村中长大的孩子,会接触更真实的社会,接触更丰富的生活,会感受到人间的各种悲欢离合。所以更能形成那种原始的,正面的价值观—那“愚昧无知”的渔村,确实没有给我知识,但是给了我一种能力,悲悯同情的能力,使得我在日后面对权利的傲慢、欲望的嚣张和种种时代的虚假时,仍旧得以穿透,看见文明的核心关怀所在。'}self.txtText.config(state=NORMAL)self.txtText.delete(0.0,END)self.txtText.insert(END,book[self.txtSpinbox.get()])self.txtText.config(state=DISABLED)def message(self):                  #弹出窗口tkinter.messagebox.showinfo("联系人搜索","这是一个假的功能!!")def mainevent(self): if self.recvdmsg :self.txtMsgList.insert(END, data, 'greencolor')def processIncoming(self):self.strMsg = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime()) + '\n 'while self.queue.qsize():try:msg=self.queue.get(0)print(msg)self.txtMsgList.see('end')self.txtMsgList.config(state=NORMAL)# self.txtMsgList(state=NORMAL)self.txtMsgList.insert(END, self.strMsg, 'greencolor')self.txtMsgList.insert(END, str(msg)+'\n', 'greencolor')self.txtMsgList.see('end')self.txtMsgList.config(state=DISABLED)except :passclass ThreadedClient():def __init__(self,master):self.master=masterself.queue=Queue()self.gui=GuiPart(master,self.queue,self.endApplication)self.running=Trueself.thread1=Process(target=self.workerThread1)self.thread1.start()self.periodicCall()def periodicCall(self):self.master.after(200,self.periodicCall)self.gui.processIncoming()if not self.running:self.master.destroy()def workerThread1(self):#self.ott=Tkinter.Tk()#self.ott.mainloop()# self.s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)# self.s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)while self.running:# s.bind(("127.0.0.1",9527))# print('wait for recv')data,addr = s.recvfrom(1024)# print(data)# time.sleep(rand.random()*1.5)# msg=rand.random() self.queue.put(data.decode())def endApplication(self):self.running=Falsedef __del__(self):self.thread1.join()
rand=random.Random()
root=tkinter.Tk()
client=ThreadedClient(root)root.mainloop()

python3 项目开发-中级篇(二)相关推荐

  1. CriminalIntent项目开发--后篇

    CriminalIntent项目开发--后篇 为Criminal Intent应用添加对话框 创建DiaologFragment,使用FragmentManager管理对话框,可以灵活的显示对话框. ...

  2. WPF 项目开发入门(二) WPF 页面布局

    WPF 项目开发入门(一) 安装运行 WPF 项目开发入门(二) WPF 页面布局 WPF 项目开发入门(三)WPF 窗体与页面 WPF 项目开发入门(四) MVVM 模式 与 TreeView树组件 ...

  3. 【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发

    一.前言 本博客将讲解如何用Qt+OpenCV开发一款图片查看器的Windows应用程序,其实不用OpenCV也能开发出这类软件,作者目的是为了学习Qt+OpenCV开发项目,所以会使用OpenCV, ...

  4. 《“透视”个人大数据》项目开发小记 --(二)网络服务端,邮箱验证和手机验证(C#,Java)

    现在网络的应用越来越普及,网络的构建也越来越简便,对于某些研究性项目自建网络服务端 也是可行的方案.本项目的网络服务,是用C#,基于Socket构建的,核心的工作是通过自定的BS60传输协议,实现与手 ...

  5. 【Lolttery】项目开发日志 (二) 数据库的二三事

    基本的框架定好了之后,就是数据库的问题咯.在框架上我们选用了现在比较流行的mybatis框架. mybatis与spring的整合十分简单: <!-- 配置sqlSessionFactory - ...

  6. iOS开发——高级篇——二维码的生产和读取

    一.二维码的生成 从iOS7开始集成了二维码的生成和读取功能 此前被广泛使用的zbarsdk目前不支持64位处理器 生成二维码的步骤: 导入CoreImage框架 通过滤镜CIFilter生成二维码 ...

  7. 智能手机之硬件开发知识篇二

    手机软件的常见故障及维修 软件故障的认识 1.什么是软件故障 我们已经大概了解了软件及单片机的系统,现在让我们来了解一下什么是软件的故障.在手机中,会引发控制系统不正常有两种情况:一方面是软件故障(如 ...

  8. 硬件项目开发 datasheet篇

    Datasheet(数据手册):电子元器件或者芯片的数据手册规格书,一般由厂家编写,格式一般为PDF,内容包括性能介绍,如电气参数.物理参数等(主要讲芯片可以做什么用).引脚功能及管脚定义(要画原理图 ...

  9. java控制并发数量_Java并发编程中级篇(二):使用Semaphore信号量进行多个资源并发控制...

    上一节中我们使用了Semaphore信号量保护共享资源,但是它只能保护一个共享资源,当我们需要同时保护多个共享资源的时候,我们只需要在创建信号量的时候使用new Semaphore(int)构造方法, ...

最新文章

  1. 牛客练习赛3 E - 绝对半径2051
  2. 乐佰小迪智能机器人_云知声 AI 陪伴教育机器人方案亮相广州国际玩具展
  3. Mybatis源码之数据源模块分析
  4. (王道408考研数据结构)第一章绪论-第二节2:算法的时间复杂度和空间复杂度
  5. php在线预览文档,php如何实现文档在线预览
  6. dubbo配置文件xml校验报错
  7. java系列2:方法的重载
  8. mysql主从复制的流程_MYSQL主从复制部署流程
  9. VB.NET 父窗口内子窗口运用
  10. DialogFragment中通过dataBinding绑定View,设置点击事件无效,通过getWindow设置dialog位置和大小无效。
  11. 【数值分析】Doolittle分解和Cholesky分解的Python实现
  12. Hadoop之HDFS常见面试题
  13. GeekOS操作系统Project4
  14. 【资料整理】BGL中的BFS算法使用
  15. MSBuild 命令的简单使用
  16. WINVNC源码阅读(五)
  17. 使用WebMagic爬虫框架爬取暴走漫画
  18. 网络工程师常见面试问题
  19. python简单的三元一次方程求解
  20. python爬取数据分析淘宝商品_python爬取并分析淘宝商品信息

热门文章

  1. .NET类比学JAVA之访问SqlServer数据库
  2. java mock verify_java-缺少对verify(mock)的方法调用,但是有一个?
  3. 【历史上的今天】3 月 12 日:万维网概念被提出;Google Code 停运;仙童半导体公司被收购
  4. 【练习】面向对象系列(002)——双色球
  5. Weblogic启动报错weblogic.management.ManagementException: Unable to obtain lock on
  6. HTML怎么把图片颜色加深,怎么把Photoshop的图片整体颜色加深?
  7. python | 短句自动生成SEO文章
  8. php做宿舍门禁管理系统项目首选公司,宿舍人脸识别门禁系统,校园宿舍管理系统...
  9. 骨龄预测代码学习(一)
  10. 科普:DisplayPort与HDMI的比较