服务器Server:
1 #/usr/bin/env python 2 # -*- coding: UTF-8 -*- 3 4 from socket import * 5 import time 6 import threading 7 import wx 8 import MySQLdb 9 import xlwt 10 from clientthread import ClientThread 11 12 class Server(wx.Frame): 13 def __init__(self,parent=None,id=-1,title='服务器',pos=wx.DefaultPosition,size=(500,300)): 14 15 '''窗口''' 16 wx.Frame.__init__(self,parent,id,title,pos,size=(400,470)) 17 pl = wx.Panel(self) 18 con = wx.BoxSizer(wx.VERTICAL) 19 subcon = wx.FlexGridSizer(wx.HORIZONTAL) 20 sta = wx.Button(pl , size=(133, 40),label='启动服务器') 21 end = wx.Button(pl, size=(133, 40), label='关闭服务器') 22 hist = wx.Button(pl,size=(133,40),label='导出聊天记录') 23 subcon.Add(sta, 1, wx.BOTTOM) 24 subcon.Add(hist, 1, wx.BOTTOM) 25 subcon.Add(end, 1, wx.BOTTOM) 26 con.Add(subcon,1,wx.ALIGN_CENTRE|wx.BOTTOM) 27 self.Text = wx.TextCtrl(pl, size=(400,250),style = wx.TE_MULTILINE|wx.TE_READONLY) 28 con.Add(self.Text, 1, wx.ALIGN_CENTRE) 29 self.ttex = wx.TextCtrl(pl, size=(400,100),style=wx.TE_MULTILINE) 30 con.Add(self.ttex, 1, wx.ALIGN_CENTRE) 31 sub2 = wx.FlexGridSizer(wx.HORIZONTAL) 32 clear = wx.Button(pl, size=(200, 40), label='清空') 33 send = wx.Button(pl, size=(200, 40), label='发送') 34 sub2.Add(clear, 1, wx.TOP | wx.LEFT) 35 sub2.Add(send, 1, wx.TOP | wx.RIGHT) 36 con.Add(sub2, 1, wx.ALIGN_CENTRE) 37 pl.SetSizer(con) 38 '''窗口''' 39 40 '''绑定''' 41 self.Bind(wx.EVT_BUTTON, self.EditClear, clear) 42 self.Bind(wx.EVT_BUTTON, self.SendMessage, send) 43 self.Bind(wx.EVT_BUTTON, self.Start, sta) 44 self.Bind(wx.EVT_BUTTON, self.Break, end) 45 self.Bind(wx.EVT_BUTTON, self.WriteToExcel, hist) 46 '''绑定''' 47 48 '''服务器准备工作''' 49 self.UserThreadList = [] 50 self.onServe = False 51 addr = ('', 21567) 52 self.ServeSock = socket(AF_INET, SOCK_STREAM) 53 self.ServeSock.bind(addr) 54 self.ServeSock.listen(10) 55 '''服务器准备工作''' 56 57 '''数据库准备工作,用于存储聊天记录''' 58 self.db = MySQLdb.connect('localhost', 'root', '123456', 'user_info') 59 self.cursor = self.db.cursor() 60 self.cursor.execute("select * from history order by time") 61 self.Text.SetValue('') 62 for data in self.cursor.fetchall(): 63 self.Text.AppendText('%s said:\n%s\nwhen %s\n\n' % (data[0], data[2], data[1])) 64 '''数据库准备工作,用于存储聊天记录''' 65 66 67 def WriteToExcel(self,event): 68 wbk = xlwt.Workbook() 69 sheet = wbk.add_sheet('sheet 1') 70 self.cursor.execute("select * from history order by time") 71 sheet.write(0, 0, "User") 72 sheet.write(0, 1, "Datetime") 73 sheet.write(0, 5, "Message") 74 index = 0 75 for data in self.cursor.fetchall(): 76 index = index + 1 77 Time = '%s'%data[1] #将datetime转成字符形式,否则直接写入Excel会变成时间戳 78 sheet.write(index,0,data[0]) 79 sheet.write(index,1,Time) #写进EXCEL会变成时间戳 80 sheet.write(index,5,data[2]) 81 wbk.save(r'D:\History_Dialog.xls') 82 83 84 #启动服务器 85 def Start(self,event): 86 if not self.onServe: 87 '''启动服务线程''' 88 self.onServe = True 89 mainThread = threading.Thread(target=self.on_serving, args=()) 90 mainThread.setDaemon(True) # 解决父线程结束,子线程还继续运行的问题 91 mainThread.start() 92 '''启动服务线程''' 93 94 #关闭服务器 95 def Break(self,event): 96 self.onServe = False 97 98 #服务器主循环 99 def on_serving(self): 100 print '...On serving...' 101 while self.onServe: 102 UserSocket, UserAddr = self.ServeSock.accept() 103 username = UserSocket.recv(1024).decode(encoding='utf-8') 104 userthread = ClientThread(UserSocket, username,self) 105 self.UserThreadList.append(userthread) 106 userthread.start() 107 self.ServeSock.close() 108 109 #绑定发送按钮 110 def SendMessage(self,event): 111 if self.onServe and cmp(self.ttex.GetValue(),''): 112 data = self.ttex.GetValue() 113 self.AddText('Server',data,time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) 114 self.ttex.SetValue('') 115 116 117 # 向所有客户端(包括自己)发送信息,同时更新到数据库 118 def AddText(self, source, data,Time): 119 self.cursor.execute("insert into history values(\"%s\",\"%s\",\"%s\")" % (source,Time,data)) #双引号里面有双引号,bug:句子不能有双引号、以及中文 120 self.db.commit() 121 sendData = '%s said:\n%s\nwhen %s\n' % (source,data,Time) 122 self.Text.AppendText('%s\n'%sendData) 123 for user in self.UserThreadList: #bug:客户端关闭了仍然在队列中。如果客户端关闭了,那怎么在服务器判断是否已经关闭了?客户端在关闭之前发一条信息给服务器? 124 user.UserSocket.send(sendData.encode(encoding='utf-8')) 125 126 #绑定清空按钮 127 def EditClear(self,event): 128 self.ttex.Clear() 129 130 131 132 def main(): 133 app = wx.App(False) 134 Server().Show() 135 app.MainLoop() 136 137 if __name__ == '__main__': 138 main()
服务器的客户线程Clientthread:
1 #/usr/bin/env python 2 # -*- coding: UTF-8 -*- 3 4 import threading 5 import time 6 7 class ClientThread(threading.Thread): 8 9 def __init__(self,UserSocket, Username,server): 10 threading.Thread.__init__(self) 11 self.UserSocket = UserSocket 12 self.Username = Username 13 self.server = server 14 self.Loadhist() #加载历史聊天记录 15 16 def Loadhist(self): 17 self.server.cursor.execute("select * from history order by time") 18 for data in self.server.cursor.fetchall(): 19 time.sleep(0.6) #几条信息同时发,会造成末尾回车键的丢失,所以要有时间间隔 20 sendData = '%s said:\n%s\nwhen %s\n'%(data[0], data[2], data[1]) 21 self.UserSocket.send(sendData.encode(encoding='utf-8')) 22 23 24 def run(self): 25 size = 1024 26 while True: 27 data = self.UserSocket.recv(size) #未解决:客户端断开连接后这里会报错 28 self.server.AddText(self.Username,data.decode(encoding='utf-8'),time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) 29 self.UserSocket.close() #这里都执行不到
客户登录界面Logframe:
1 #/usr/bin/env python 2 # -*- coding: UTF-8 -*- 3 4 from socket import * 5 import wx 6 import MySQLdb 7 from client import Client 8 9 class LogFrame(wx.Frame): 10 def __init__(self,parent=None,id=-1,title='登录窗口',pos=wx.DefaultPosition,size=(500,300)): 11 12 '''窗口''' 13 wx.Frame.__init__(self,parent,id,title,pos,size=(400,280)) 14 self.pl = wx.Panel(self) 15 con = wx.BoxSizer(wx.VERTICAL) 16 subcon = wx.FlexGridSizer(2,2,10,10) 17 username = wx.StaticText(self.pl, label="Username:",style=wx.ALIGN_LEFT) 18 password = wx.StaticText(self.pl, label="Password:",style=wx.ALIGN_LEFT) 19 self.tc1 = wx.TextCtrl(self.pl,size=(180,20)) 20 self.tc2 = wx.TextCtrl(self.pl,size=(180,20),style=wx.TE_PASSWORD) 21 subcon.Add(username,wx.TE_LEFT) 22 subcon.Add(self.tc1,1,wx.EXPAND) 23 subcon.Add(password) 24 subcon.Add(self.tc2,1,wx.EXPAND) 25 con.Add(subcon,1,wx.ALIGN_CENTER) 26 subcon2 = wx.FlexGridSizer(1,2,10,10) 27 register = wx.Button(self.pl,label='Register') 28 login = wx.Button(self.pl,label='Login') 29 subcon2.Add(register,1, wx.TOP) 30 subcon2.Add(login,1, wx.TOP) 31 con.Add(subcon2,1,wx.ALIGN_CENTRE) 32 self.pl.SetSizer(con) 33 self.Bind(wx.EVT_BUTTON,self.Register,register) 34 self.Bind(wx.EVT_BUTTON,self.Login,login) 35 '''窗口''' 36 self.isConnected = False 37 self.userSocket = None 38 39 def ConnectToServer(self): 40 if not self.isConnected: 41 ADDR = ('localhost', 21567) 42 self.userSocket = socket(AF_INET, SOCK_STREAM) 43 try: 44 self.userSocket.connect(ADDR) 45 self.userSocket.send(self.tc1.GetValue().encode(encoding='utf-8')) 46 self.isConnected = True 47 return True 48 except Exception: 49 return False 50 else: 51 return True 52 53 54 def Login(self,event): 55 if not self.ConnectToServer(): 56 err = wx.MessageDialog(None, '服务器未启动', 'ERROR!', wx.OK) 57 err.ShowModal() 58 err.Destroy() 59 else: 60 username = self.tc1.GetValue() 61 password = self.tc2.GetValue() 62 db = MySQLdb.connect('localhost', 'root', '123456', 'user_info') 63 cursor = db.cursor() 64 cursor.execute("select * from user_list where username='%s' and password='%s'"%(username,password)) 65 if not cursor.fetchone(): 66 err = wx.MessageDialog(None,'用户不存在或密码错误','ERROR!',wx.OK) 67 err.ShowModal() 68 else: 69 self.Close() 70 Client(opSock=self.userSocket, username=username).Show() 71 db.commit() 72 db.close() 73 74 def Register(self,event): 75 if not self.ConnectToServer(): 76 err = wx.MessageDialog(None, '服务器未启动', 'ERROR!', wx.OK) 77 err.ShowModal() 78 err.Destroy() 79 else: 80 username = self.tc1.GetValue() 81 password = self.tc2.GetValue() 82 db = MySQLdb.connect('localhost', 'root', 'triumphvictory', 'user_info') 83 cursor = db.cursor() 84 cursor.execute("select * from user_list where username='%s'"%username) 85 if not cursor.fetchone(): 86 cursor.execute("insert into user_list(username,password) values('%s','%s')"%(username,password)) 87 else: 88 err = wx.MessageDialog(None, '用户已存在', 'ERROR!', wx.OK) 89 err.ShowModal() 90 db.commit() 91 db.close() 92 93 94 def main(): 95 app = wx.App(False) 96 LogFrame().Show() 97 app.MainLoop() 98 99 if __name__ == '__main__': 100 main()
客户端Client:
1 #/usr/bin/env python 2 # -*- coding: UTF-8 -*- 3 4 import wx 5 import threading 6 from time import ctime 7 8 class Client(wx.Frame): 9 def __init__(self,opSock,username,parent=None,id=-1,title='客户端',pos=wx.DefaultPosition,size=(500,300)): 10 11 '''窗口''' 12 wx.Frame.__init__(self,parent,id,title,pos,size=(400,470)) 13 self.opSock = opSock 14 self.username = username 15 pl = wx.Panel(self) 16 con = wx.BoxSizer(wx.VERTICAL) 17 subcon = wx.FlexGridSizer(wx.HORIZONTAL) 18 sta = wx.Button(pl, size=(200, 40),label='连接') 19 end = wx.Button(pl, size=(200, 40),label='断开') 20 subcon.Add(sta, 1, wx.TOP|wx.LEFT) 21 subcon.Add(end, 1, wx.TOP|wx.RIGHT) 22 con.Add(subcon,1,wx.ALIGN_CENTRE) 23 self.Text = wx.TextCtrl(pl, size=(400,250),style = wx.TE_MULTILINE|wx.TE_READONLY) 24 con.Add(self.Text, 1, wx.ALIGN_CENTRE) 25 self.ttex = wx.TextCtrl(pl, size=(400,100),style=wx.TE_MULTILINE) 26 con.Add(self.ttex, 1, wx.ALIGN_CENTRE) 27 sub2 = wx.FlexGridSizer(wx.HORIZONTAL) 28 clear = wx.Button(pl, size=(200, 40), label='清空') 29 send = wx.Button(pl, size=(200, 40), label='发送') 30 sub2.Add(clear, 1, wx.TOP | wx.LEFT) 31 sub2.Add(send, 1, wx.TOP | wx.RIGHT) 32 con.Add(sub2, 1, wx.ALIGN_CENTRE) 33 pl.SetSizer(con) 34 '''窗口''' 35 36 '''绑定''' 37 self.Bind(wx.EVT_BUTTON, self.EditClear, clear) 38 self.Bind(wx.EVT_BUTTON, self.Send, send) 39 self.Bind(wx.EVT_BUTTON, self.Login, sta) 40 self.Bind(wx.EVT_BUTTON, self.Logout, end) 41 '''绑定''' 42 self.flag = False 43 44 #登录 45 def Login(self,event): 46 '''客户端准备工作''' 47 self.flag = True 48 t = threading.Thread(target=self.Receive, args=()) 49 t.setDaemon(True) 50 t.start() 51 '''客户端准备工作''' 52 53 #退出 54 def Logout(self,event): 55 self.flag = False 56 57 #绑定发送按钮 58 def Send(self,event): 59 if self.flag and cmp(self.ttex.GetValue(),''): 60 self.opSock.send(self.ttex.GetValue().encode(encoding='utf-8')) 61 self.ttex.SetValue('') 62 63 #绑定清空按钮 64 def EditClear(self,event): 65 self.ttex.Clear() 66 67 #接收客户端的信息(独立一个线程) 68 def Receive(self): 69 while self.flag: 70 data = self.opSock.recv(1024).decode(encoding='utf-8') 71 self.Text.AppendText('%s\n'%data)