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聊天功能汇总
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。