python实现多人聊天室


Posted in Python onMarch 31, 2020

本文实例为大家分享了python实现多人聊天室的具体代码,供大家参考,具体内容如下

一、目的

以实现小项目的方式,来巩固之前学过的Python基本语法以及相关的知识。 

二、相关技术

1.wxpython GUI编程

2.网络编程

3.多线程编程

4.数据库编程

5.简单的将数据导出到Excel表 

三、存在的漏洞以及不足

1.由于数据库编码的问题,无法使用中文。

2.在客户端关闭后,其相关的线程仍然存在于服务器的用户线程队列中,所以服务器会错误地往已关闭的客户端传送信息。

3.客户端初始登录并加载历史记录时,会出现每条历史消息后面的回车键丢失的现象,解决的方法是:在加载相邻两条消息之间加个时间间隔,但效果不佳。

四、源码

服务器Server:

# -*- coding: UTF-8 -*-

from socket import *
import time
import threading
import wx
import MySQLdb
import xlwt
from clientthread import ClientThread

class Server(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 * from history order by time")
 self.Text.SetValue('')
 for data in self.cursor.fetchall(): #加载历史聊天记录
 self.Text.AppendText('%s said:\n%s\nwhen %s\n\n' % (data[0], data[2], data[1]))
 '''数据库准备工作,用于存储聊天记录'''


 #将聊天记录导出到EXCEl表中
 def WriteToExcel(self,event):
 wbk = xlwt.Workbook()
 sheet = wbk.add_sheet('sheet 1')
 self.cursor.execute("select * from history order by time")
 sheet.write(0, 0, "User")
 sheet.write(0, 1, "Datetime")
 sheet.write(0, 5, "Message")
 index = 0
 for data in self.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')


 #启动服务器的服务线程
 def Start(self,event):
 if not self.onServe:
 '''启动服务线程'''
 self.onServe = True
 mainThread = threading.Thread(target=self.on_serving, args=())
 mainThread.setDaemon(True) # 解决父线程结束,子线程还继续运行的问题
 mainThread.start()
 '''启动服务线程'''

 #关闭服务器
 def Break(self,event):
 self.onServe = False

 #服务器主循环
 def on_serving(self):
 print '...On serving...'
 while self.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()

 #绑定发送按钮
 def SendMessage(self,event):
 if self.onServe and cmp(self.ttex.GetValue(),''):
 data = self.ttex.GetValue()
 self.AddText('Server',data,time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
 self.ttex.SetValue('')


 # 向所有客户端(包括自己)发送信息,同时更新到数据库
 def AddText(self, source, data,Time):
 self.cursor.execute("insert into history values(\"%s\",\"%s\",\"%s\")" % (source,Time,data)) #双引号里面有双引号,bug:句子不能有双引号、以及中文
 self.db.commit()
 sendData = '%s said:\n%s\nwhen %s\n' % (source,data,Time)
 self.Text.AppendText('%s\n'%sendData)
 for user in self.UserThreadList: #bug:客户端关闭了仍然在队列中。如果客户端关闭了,那怎么在服务器判断是否已经关闭了?客户端在关闭之前发一条信息给服务器?
 user.UserSocket.send(sendData.encode(encoding='utf-8'))

 #绑定清空按钮
 def EditClear(self,event):
 self.ttex.Clear()


def main():
 app = wx.App(False)
 Server().Show()
 app.MainLoop()

if __name__ == '__main__':
 main()

服务器的客户线程Clientthread:

# -*- coding: UTF-8 -*-

import threading
import time

class ClientThread(threading.Thread):

 def __init__(self,UserSocket, Username,server):
 threading.Thread.__init__(self)
 self.UserSocket = UserSocket
 self.Username = Username
 self.server = server
 self.Loadhist()

 # 加载历史聊天记录
 def Loadhist(self):
 self.server.cursor.execute("select * from history order by time")
 for data in self.server.cursor.fetchall():
 time.sleep(0.6)  #几条信息同时发,会造成末尾回车键的丢失,所以要有时间间隔
 sendData = '%s said:\n%s\nwhen %s\n'%(data[0], data[2], data[1])
 self.UserSocket.send(sendData.encode(encoding='utf-8'))


 #方法重写,线程的入口
 def run(self):
 size = 1024
 while True:
 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 -*-

from socket import *
import wx
import MySQLdb
from client import Client

class LogFrame(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

 #连接到服务器
 def ConnectToServer(self):
 if not self.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
 return True
 except Exception:
 return False
 else:
 return True

 #登录
 def Login(self,event):
 if not self.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 * from user_list where username='%s' and password='%s'"%(username,password))
 if not cursor.fetchone():
 err = wx.MessageDialog(None,'用户不存在或密码错误','ERROR!',wx.OK)
 err.ShowModal()
 else:
 self.Close()
 Client(opSock=self.userSocket, username=username).Show()
 db.commit()
 db.close()

 #注册
 def Register(self,event):
 if not self.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 * from user_list where username='%s'"%username)
 if not cursor.fetchone():
 cursor.execute("insert into user_list(username,password) values('%s','%s')"%(username,password))
 else:
 err = wx.MessageDialog(None, '用户已存在', 'ERROR!', wx.OK)
 err.ShowModal()
 db.commit()
 db.close()


def main():
 app = wx.App(False)
 LogFrame().Show()
 app.MainLoop()

if __name__ == '__main__':
 main()

客户端Client:

#/usr/bin/env python
# -*- coding: UTF-8 -*-

import wx
import threading
from time import ctime

class Client(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

 #登录
 def Login(self,event):
 '''客户端准备工作'''
 self.isConnected = True
 t = threading.Thread(target=self.Receive, args=())
 t.setDaemon(True)
 t.start()
 '''客户端准备工作'''

 #退出
 def Logout(self,event):
 self.isConnected = False

 #绑定发送按钮
 def Send(self,event):
 if self.isConnected and cmp(self.ttex.GetValue(),''):
 self.opSock.send(self.ttex.GetValue().encode(encoding='utf-8'))
 self.ttex.SetValue('')

 #绑定清空按钮
 def EditClear(self,event):
 self.ttex.Clear()

 #接收客户端的信息(独立一个线程)
 def Receive(self):
 while self.isConnected:
 data = self.opSock.recv(1024).decode(encoding='utf-8')
 self.Text.AppendText('%s\n'%data)

更多关于python聊天功能的精彩文章请点击专题: python聊天功能汇总

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python下载文件时显示下载进度的方法
Apr 02 Python
Python中用Decorator来简化元编程的教程
Apr 13 Python
python使用arp欺骗伪造网关的方法
Apr 24 Python
Django实现的自定义访问日志模块示例
Jun 23 Python
Python制作豆瓣图片的爬虫
Dec 28 Python
Django数据库连接丢失问题的解决方法
Dec 29 Python
Python使用folium excel绘制point
Jan 03 Python
python命令行工具Click快速掌握
Jul 04 Python
python接口自动化如何封装获取常量的类
Dec 24 Python
Python利用pip安装tar.gz格式的离线资源包
Sep 14 Python
python实现企业微信定时发送文本消息的实例代码
Nov 25 Python
Python竟然能剪辑视频
May 25 Python
Python实现将数据写入netCDF4中的方法示例
Aug 30 #Python
Python使用爬虫抓取美女图片并保存到本地的方法【测试可用】
Aug 30 #Python
Python使用一行代码获取上个月是几月
Aug 30 #Python
Python实现的读取/更改/写入xml文件操作示例
Aug 30 #Python
python实现录音小程序
Oct 26 #Python
Python图像处理之简单画板实现方法示例
Aug 30 #Python
浅析python中numpy包中的argsort函数的使用
Aug 30 #Python
You might like
mysql建立外键
2006/11/25 PHP
ioncube_loader_win_5.2.dll的错误解决方法
2015/01/04 PHP
php版微信公众平台实现预约提交后发送email的方法
2016/09/26 PHP
微信封装的调用微信签名包的类库
2017/06/08 PHP
php单元测试phpunit入门实例教程
2017/11/17 PHP
Django中通过定时任务触发页面静态化的处理方式
2018/08/29 PHP
php常用字符串查找函数strstr()与strpos()实例分析
2019/06/21 PHP
JavaScript中访问节点对象的方法有哪些如何使用
2013/09/24 Javascript
优化Jquery,提升网页加载速度
2013/11/14 Javascript
JS实现div居中示例
2014/04/17 Javascript
js拆分字符串并将分割的数据放到数组中的方法
2015/05/06 Javascript
ECMAScript6函数默认参数
2015/06/12 Javascript
jQuery实现带滑动条的菜单效果代码
2015/08/26 Javascript
创建自己的jquery表格插件
2015/11/25 Javascript
js实现对table的增加行和删除行的操作方法
2016/10/13 Javascript
微信小程序链接传参并跳转新页面
2016/11/29 Javascript
微信小程序 setData的使用方法详解
2017/04/20 Javascript
jQuery+HTML5实现WebGL高性能烟花绽放动画效果【附demo源码下载】
2017/08/18 jQuery
基于vue中css预加载使用sass的配置方式详解
2018/03/13 Javascript
微信小程序之分享页面如何返回首页的示例
2018/03/28 Javascript
微信小程序textarea层级过高(盖住其他元素)问题的解决办法
2019/03/04 Javascript
Vue注册组件命名时不能用大写的原因浅析
2019/04/25 Javascript
javascript实现计算器功能
2020/03/30 Javascript
[44:50]DOTA2上海特级锦标赛B组小组赛#2 VG VS Fnatic第二局
2016/02/26 DOTA
python 时间信息“2018-02-04 18:23:35“ 解析成字典形式的结果代码详解
2018/04/19 Python
css3的transition属性详解
2014/12/15 HTML / CSS
CSS3制作苹果风格键盘特效
2015/02/26 HTML / CSS
玛蒂尔达简服装:Matilda Jane Clothing
2019/02/13 全球购物
荷兰音乐会和音乐剧门票订购网站:Topticketshop
2019/08/27 全球购物
《苏珊的帽子》教学反思
2014/04/07 职场文书
工伤赔偿协议书
2014/04/15 职场文书
2015银行年终工作总结范文
2015/05/26 职场文书
职业规划从高考志愿专业选择开始
2019/08/08 职场文书
Python使用random模块实现掷骰子游戏的示例代码
2021/04/29 Python
MySQL sql_mode的使用详解
2021/05/08 MySQL
Python使用Beautiful Soup(BS4)库解析HTML和XML
2022/06/05 Python