一.目的
以实现小项目的方式,来巩固之前学过的Python基本语法以及相关的知识。
二.相关技术:
1.wxpython GUI编程
2.网络编程
3.多线程编程
4.数据库编程
5.简单的将数据导出到Excel表
三.存在的漏洞以及不足
1.由于数据库编码的问题,无法使用中文。
2.在客户端关闭后,其相关的线程仍然存在于服务器的用户线程队列中,所以服务器会错误地往已关闭的客户端传送信息。
3.客户端初始登录并加载历史记录时,会出现每条历史消息后面的回车键丢失的现象,解决的方法是:在加载相邻两条消息之间加个时间间隔,但效果不佳。
四.源码
服务器Server:
1 # -*- coding: UTF-8 -*- 2 3 from socket import * 4 import time 5 import threading 6 import wx 7 import MySQLdb 8 import xlwt 9 from clientthread import ClientThread 10 11 class Server(wx.Frame): 12 def __init__(self,parent=None,id=-1,title='服务器',pos=wx.DefaultPosition,size=(500,300)): 13 14 '''窗口''' 15 wx.Frame.__init__(self,parent,id,title,pos,size=(400,470)) 16 pl = wx.Panel(self) 17 con = wx.BoxSizer(wx.VERTICAL) 18 subcon = wx.FlexGridSizer(wx.HORIZONTAL) 19 sta = wx.Button(pl , size=(133, 40),label='启动服务器') 20 end = wx.Button(pl, size=(133, 40), label='关闭服务器') 21 hist = wx.Button(pl,size=(133,40),label='导出聊天记录') 22 subcon.Add(sta, 1, wx.BOTTOM) 23 subcon.Add(hist, 1, wx.BOTTOM) 24 subcon.Add(end, 1, wx.BOTTOM) 25 con.Add(subcon,1,wx.ALIGN_CENTRE|wx.BOTTOM) 26 self.Text = wx.TextCtrl(pl, size=(400,250),style = wx.TE_MULTILINE|wx.TE_READONLY) 27 con.Add(self.Text, 1, wx.ALIGN_CENTRE) 28 self.ttex = wx.TextCtrl(pl, size=(400,100),style=wx.TE_MULTILINE) 29 con.Add(self.ttex, 1, wx.ALIGN_CENTRE) 30 sub2 = wx.FlexGridSizer(wx.HORIZONTAL) 31 clear = wx.Button(pl, size=(200, 40), label='清空') 32 send = wx.Button(pl, size=(200, 40), label='发送') 33 sub2.Add(clear, 1, wx.TOP | wx.LEFT) 34 sub2.Add(send, 1, wx.TOP | wx.RIGHT) 35 con.Add(sub2, 1, wx.ALIGN_CENTRE) 36 pl.SetSizer(con) 37 '''窗口''' 38 39 '''绑定''' 40 self.Bind(wx.EVT_BUTTON, self.EditClear, clear) 41 self.Bind(wx.EVT_BUTTON, self.SendMessage, send) 42 self.Bind(wx.EVT_BUTTON, self.Start, sta) 43 self.Bind(wx.EVT_BUTTON, self.Break, end) 44 self.Bind(wx.EVT_BUTTON, self.WriteToExcel, hist) 45 '''绑定''' 46 47 '''服务器准备工作''' 48 self.UserThreadList = [] 49 self.onServe = False 50 addr = ('', 21567) 51 self.ServeSock = socket(AF_INET, SOCK_STREAM) 52 self.ServeSock.bind(addr) 53 self.ServeSock.listen(10) 54 '''服务器准备工作''' 55 56 '''数据库准备工作,用于存储聊天记录''' 57 self.db = MySQLdb.connect('localhost', 'root', '123456', 'user_info') 58 self.cursor = self.db.cursor() 59 self.cursor.execute("select * from history order by time") 60 self.Text.SetValue('') 61 for data in self.cursor.fetchall(): #加载历史聊天记录 62 self.Text.AppendText('%s said:\n%s\nwhen %s\n\n' % (data[0], data[2], data[1])) 63 '''数据库准备工作,用于存储聊天记录''' 64 65 66 #将聊天记录导出到EXCEl表中 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 def main(): 132 app = wx.App(False) 133 Server().Show() 134 app.MainLoop() 135 136 if __name__ == '__main__': 137 main()