python实现多人聊天室
本文实例为大家分享了python实现多人聊天室的具体代码,供大家参考,具体内容如下
一、目的
以实现小项目的方式,来巩固之前学过的Python基本语法以及相关的知识。
二、相关技术
1.wxpythonGUI编程
2.网络编程
3.多线程编程
4.数据库编程
5.简单的将数据导出到Excel表
三、存在的漏洞以及不足
1.由于数据库编码的问题,无法使用中文。
2.在客户端关闭后,其相关的线程仍然存在于服务器的用户线程队列中,所以服务器会错误地往已关闭的客户端传送信息。
3.客户端初始登录并加载历史记录时,会出现每条历史消息后面的回车键丢失的现象,解决的方法是:在加载相邻两条消息之间加个时间间隔,但效果不佳。
四、源码
服务器Server:
#-*-coding:UTF-8-*- fromsocketimport* importtime importthreading importwx importMySQLdb importxlwt fromclientthreadimportClientThread classServer(wx.Frame): def__init__(self,parent=None,id=-1,title='服务器',pos=wx.DefaultPosition,size=(500,300)): '''窗口''' wx.Frame.__init__(self,parent,id,title,pos,size=(400,470)) pl=wx.Panel(self) con=wx.BoxSizer(wx.VERTICAL) subcon=wx.FlexGridSizer(wx.HORIZONTAL) sta=wx.Button(pl,size=(133,40),label='启动服务器') end=wx.Button(pl,size=(133,40),label='关闭服务器') hist=wx.Button(pl,size=(133,40),label='导出聊天记录') subcon.Add(sta,1,wx.BOTTOM) subcon.Add(hist,1,wx.BOTTOM) subcon.Add(end,1,wx.BOTTOM) con.Add(subcon,1,wx.ALIGN_CENTRE|wx.BOTTOM) self.Text=wx.TextCtrl(pl,size=(400,250),style=wx.TE_MULTILINE|wx.TE_READONLY) con.Add(self.Text,1,wx.ALIGN_CENTRE) self.ttex=wx.TextCtrl(pl,size=(400,100),style=wx.TE_MULTILINE) con.Add(self.ttex,1,wx.ALIGN_CENTRE) sub2=wx.FlexGridSizer(wx.HORIZONTAL) clear=wx.Button(pl,size=(200,40),label='清空') send=wx.Button(pl,size=(200,40),label='发送') sub2.Add(clear,1,wx.TOP|wx.LEFT) sub2.Add(send,1,wx.TOP|wx.RIGHT) con.Add(sub2,1,wx.ALIGN_CENTRE) pl.SetSizer(con) '''窗口''' '''绑定''' self.Bind(wx.EVT_BUTTON,self.EditClear,clear) self.Bind(wx.EVT_BUTTON,self.SendMessage,send) self.Bind(wx.EVT_BUTTON,self.Start,sta) self.Bind(wx.EVT_BUTTON,self.Break,end) self.Bind(wx.EVT_BUTTON,self.WriteToExcel,hist) '''绑定''' '''服务器准备工作''' self.UserThreadList=[] self.onServe=False addr=('',21567) self.ServeSock=socket(AF_INET,SOCK_STREAM) self.ServeSock.bind(addr) self.ServeSock.listen(10) '''服务器准备工作''' '''数据库准备工作,用于存储聊天记录''' self.db=MySQLdb.connect('localhost','root','123456','user_info') self.cursor=self.db.cursor() self.cursor.execute("select*fromhistoryorderbytime") self.Text.SetValue('') fordatainself.cursor.fetchall():#加载历史聊天记录 self.Text.AppendText('%ssaid:\n%s\nwhen%s\n\n'%(data[0],data[2],data[1])) '''数据库准备工作,用于存储聊天记录''' #将聊天记录导出到EXCEl表中 defWriteToExcel(self,event): wbk=xlwt.Workbook() sheet=wbk.add_sheet('sheet1') self.cursor.execute("select*fromhistoryorderbytime") sheet.write(0,0,"User") sheet.write(0,1,"Datetime") sheet.write(0,5,"Message") index=0 fordatainself.cursor.fetchall(): index=index+1 Time='%s'%data[1]#将datetime转成字符形式,否则直接写入Excel会变成时间戳 sheet.write(index,0,data[0]) sheet.write(index,1,Time)#写进EXCEL会变成时间戳 sheet.write(index,5,data[2]) wbk.save(r'D:\History_Dialog.xls') #启动服务器的服务线程 defStart(self,event): ifnotself.onServe: '''启动服务线程''' self.onServe=True mainThread=threading.Thread(target=self.on_serving,args=()) mainThread.setDaemon(True)#解决父线程结束,子线程还继续运行的问题 mainThread.start() '''启动服务线程''' #关闭服务器 defBreak(self,event): self.onServe=False #服务器主循环 defon_serving(self): print'...Onserving...' whileself.onServe: UserSocket,UserAddr=self.ServeSock.accept() username=UserSocket.recv(1024).decode(encoding='utf-8')#接收用户名 userthread=ClientThread(UserSocket,username,self) self.UserThreadList.append(userthread)#将用户线程加到队列中 userthread.start() self.ServeSock.close() #绑定发送按钮 defSendMessage(self,event): ifself.onServeandcmp(self.ttex.GetValue(),''): data=self.ttex.GetValue() self.AddText('Server',data,time.strftime("%Y-%m-%d%H:%M:%S",time.localtime())) self.ttex.SetValue('') #向所有客户端(包括自己)发送信息,同时更新到数据库 defAddText(self,source,data,Time): self.cursor.execute("insertintohistoryvalues(\"%s\",\"%s\",\"%s\")"%(source,Time,data))#双引号里面有双引号,bug:句子不能有双引号、以及中文 self.db.commit() sendData='%ssaid:\n%s\nwhen%s\n'%(source,data,Time) self.Text.AppendText('%s\n'%sendData) foruserinself.UserThreadList:#bug:客户端关闭了仍然在队列中。如果客户端关闭了,那怎么在服务器判断是否已经关闭了?客户端在关闭之前发一条信息给服务器? user.UserSocket.send(sendData.encode(encoding='utf-8')) #绑定清空按钮 defEditClear(self,event): self.ttex.Clear() defmain(): app=wx.App(False) Server().Show() app.MainLoop() if__name__=='__main__': main()
服务器的客户线程Clientthread:
#-*-coding:UTF-8-*- importthreading importtime classClientThread(threading.Thread): def__init__(self,UserSocket,Username,server): threading.Thread.__init__(self) self.UserSocket=UserSocket self.Username=Username self.server=server self.Loadhist() #加载历史聊天记录 defLoadhist(self): self.server.cursor.execute("select*fromhistoryorderbytime") fordatainself.server.cursor.fetchall(): time.sleep(0.6)#几条信息同时发,会造成末尾回车键的丢失,所以要有时间间隔 sendData='%ssaid:\n%s\nwhen%s\n'%(data[0],data[2],data[1]) self.UserSocket.send(sendData.encode(encoding='utf-8')) #方法重写,线程的入口 defrun(self): size=1024 whileTrue: data=self.UserSocket.recv(size)#未解决:客户端断开连接后这里会报错 self.server.AddText(self.Username,data.decode(encoding='utf-8'),time.strftime("%Y-%m-%d%H:%M:%S",time.localtime())) self.UserSocket.close()#这里都执行不到
客户登录界面Logframe:
#-*-coding:UTF-8-*- fromsocketimport* importwx importMySQLdb fromclientimportClient classLogFrame(wx.Frame): def__init__(self,parent=None,id=-1,title='登录窗口',pos=wx.DefaultPosition,size=(500,300)): '''窗口''' wx.Frame.__init__(self,parent,id,title,pos,size=(400,280)) self.pl=wx.Panel(self) con=wx.BoxSizer(wx.VERTICAL) subcon=wx.FlexGridSizer(2,2,10,10) username=wx.StaticText(self.pl,label="Username:",style=wx.ALIGN_LEFT) password=wx.StaticText(self.pl,label="Password:",style=wx.ALIGN_LEFT) self.tc1=wx.TextCtrl(self.pl,size=(180,20)) self.tc2=wx.TextCtrl(self.pl,size=(180,20),style=wx.TE_PASSWORD) subcon.Add(username,wx.TE_LEFT) subcon.Add(self.tc1,1,wx.EXPAND) subcon.Add(password) subcon.Add(self.tc2,1,wx.EXPAND) con.Add(subcon,1,wx.ALIGN_CENTER) subcon2=wx.FlexGridSizer(1,2,10,10) register=wx.Button(self.pl,label='Register') login=wx.Button(self.pl,label='Login') subcon2.Add(register,1,wx.TOP) subcon2.Add(login,1,wx.TOP) con.Add(subcon2,1,wx.ALIGN_CENTRE) self.pl.SetSizer(con) self.Bind(wx.EVT_BUTTON,self.Register,register) self.Bind(wx.EVT_BUTTON,self.Login,login) '''窗口''' self.isConnected=False self.userSocket=None #连接到服务器 defConnectToServer(self): ifnotself.isConnected: ADDR=('localhost',21567) self.userSocket=socket(AF_INET,SOCK_STREAM) try: self.userSocket.connect(ADDR) self.userSocket.send(self.tc1.GetValue().encode(encoding='utf-8')) self.isConnected=True returnTrue exceptException: returnFalse else: returnTrue #登录 defLogin(self,event): ifnotself.ConnectToServer(): err=wx.MessageDialog(None,'服务器未启动','ERROR!',wx.OK) err.ShowModal() err.Destroy() else: username=self.tc1.GetValue() password=self.tc2.GetValue() db=MySQLdb.connect('localhost','root','123456','user_info') cursor=db.cursor() cursor.execute("select*fromuser_listwhereusername='%s'andpassword='%s'"%(username,password)) ifnotcursor.fetchone(): err=wx.MessageDialog(None,'用户不存在或密码错误','ERROR!',wx.OK) err.ShowModal() else: self.Close() Client(opSock=self.userSocket,username=username).Show() db.commit() db.close() #注册 defRegister(self,event): ifnotself.ConnectToServer(): err=wx.MessageDialog(None,'服务器未启动','ERROR!',wx.OK) err.ShowModal() err.Destroy() else: username=self.tc1.GetValue() password=self.tc2.GetValue() db=MySQLdb.connect('localhost','root','123456','user_info') cursor=db.cursor() cursor.execute("select*fromuser_listwhereusername='%s'"%username) ifnotcursor.fetchone(): cursor.execute("insertintouser_list(username,password)values('%s','%s')"%(username,password)) else: err=wx.MessageDialog(None,'用户已存在','ERROR!',wx.OK) err.ShowModal() db.commit() db.close() defmain(): app=wx.App(False) LogFrame().Show() app.MainLoop() if__name__=='__main__': main()
客户端Client:
#/usr/bin/envpython #-*-coding:UTF-8-*- importwx importthreading fromtimeimportctime classClient(wx.Frame): def__init__(self,opSock,username,parent=None,id=-1,title='客户端',pos=wx.DefaultPosition,size=(500,300)): '''窗口''' wx.Frame.__init__(self,parent,id,title,pos,size=(400,470)) self.opSock=opSock self.username=username pl=wx.Panel(self) con=wx.BoxSizer(wx.VERTICAL) subcon=wx.FlexGridSizer(wx.HORIZONTAL) sta=wx.Button(pl,size=(200,40),label='连接') end=wx.Button(pl,size=(200,40),label='断开') subcon.Add(sta,1,wx.TOP|wx.LEFT) subcon.Add(end,1,wx.TOP|wx.RIGHT) con.Add(subcon,1,wx.ALIGN_CENTRE) self.Text=wx.TextCtrl(pl,size=(400,250),style=wx.TE_MULTILINE|wx.TE_READONLY) con.Add(self.Text,1,wx.ALIGN_CENTRE) self.ttex=wx.TextCtrl(pl,size=(400,100),style=wx.TE_MULTILINE) con.Add(self.ttex,1,wx.ALIGN_CENTRE) sub2=wx.FlexGridSizer(wx.HORIZONTAL) clear=wx.Button(pl,size=(200,40),label='清空') send=wx.Button(pl,size=(200,40),label='发送') sub2.Add(clear,1,wx.TOP|wx.LEFT) sub2.Add(send,1,wx.TOP|wx.RIGHT) con.Add(sub2,1,wx.ALIGN_CENTRE) pl.SetSizer(con) '''窗口''' '''绑定''' self.Bind(wx.EVT_BUTTON,self.EditClear,clear) self.Bind(wx.EVT_BUTTON,self.Send,send) self.Bind(wx.EVT_BUTTON,self.Login,sta) self.Bind(wx.EVT_BUTTON,self.Logout,end) '''绑定''' self.isConnected=False #登录 defLogin(self,event): '''客户端准备工作''' self.isConnected=True t=threading.Thread(target=self.Receive,args=()) t.setDaemon(True) t.start() '''客户端准备工作''' #退出 defLogout(self,event): self.isConnected=False #绑定发送按钮 defSend(self,event): ifself.isConnectedandcmp(self.ttex.GetValue(),''): self.opSock.send(self.ttex.GetValue().encode(encoding='utf-8')) self.ttex.SetValue('') #绑定清空按钮 defEditClear(self,event): self.ttex.Clear() #接收客户端的信息(独立一个线程) defReceive(self): whileself.isConnected: data=self.opSock.recv(1024).decode(encoding='utf-8') self.Text.AppendText('%s\n'%data)
更多关于python聊天功能的精彩文章请点击专题:python聊天功能汇总
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。