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 性能优化技巧总结
Nov 01 Python
浅谈pyhton学习中出现的各种问题(新手必看)
May 17 Python
Python中标准库OS的常用方法总结大全
Jul 19 Python
django模板语法学习之include示例详解
Dec 17 Python
python Opencv将图片转为字符画
Feb 19 Python
Pyinstaller打包.py生成.exe的方法和报错总结
Apr 02 Python
Pandas之ReIndex重新索引的实现
Jun 25 Python
Python如何读写二进制数组数据
Aug 01 Python
Python利用myqr库创建自己的二维码
Nov 24 Python
python 解决函数返回return的问题
Dec 05 Python
python使用pywinauto驱动微信客户端实现公众号爬虫
May 19 Python
一篇文章带你了解Python和Java的正则表达式对比
Sep 15 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
php 图片加水印与上传图片加水印php类
2010/05/12 PHP
PHP反射类ReflectionClass和ReflectionObject的使用方法
2013/11/13 PHP
PHP中你应该知道的require()文件包含的正确用法
2015/06/12 PHP
js 学习笔记(三)
2009/12/29 Javascript
jQuery 滑动方法slideDown向下滑动元素
2014/01/16 Javascript
动态的绑定事件addEventListener方法的使用
2014/01/24 Javascript
jQuery unbind()方法实例详解
2016/01/19 Javascript
javascript html5移动端轻松实现文件上传
2020/03/27 Javascript
详细分析Javascript中创建对象的四种方式
2016/08/17 Javascript
初识简单却不失优雅的Vue.js
2016/09/12 Javascript
原生js仿浏览器滚动条效果
2017/03/02 Javascript
基于nodejs res.end和res.send的区别
2018/05/14 NodeJs
微信小程序图表插件wx-charts用法实例详解
2019/05/20 Javascript
基于JavaScript判断两个对象内容是否相等
2020/01/10 Javascript
[02:51]DOTA2英雄基础教程 艾欧
2014/01/13 DOTA
python中的函数用法入门教程
2014/09/02 Python
常见的python正则用法实例讲解
2016/06/21 Python
Python 专题三 字符串的基础知识
2017/03/19 Python
详解python 拆包可迭代数据如tuple, list
2017/12/29 Python
Tensorflow实现卷积神经网络的详细代码
2018/05/24 Python
Python读写文件模式和文件对象方法实例详解
2019/09/17 Python
python 数据库查询返回list或tuple实例
2020/05/15 Python
python和php哪个容易学
2020/06/19 Python
python exit出错原因整理
2020/08/31 Python
JackJones官方旗舰店:杰克琼斯男装
2018/03/27 全球购物
什么是.net的Remoting技术
2016/07/08 面试题
大学生村官任职感言
2014/01/09 职场文书
《维生素c的故事》教学反思
2014/02/18 职场文书
小学校本培训方案
2014/06/06 职场文书
绿色校园广播稿
2014/10/13 职场文书
优秀教师推荐材料
2014/12/16 职场文书
个人股份转让协议书范本
2015/01/28 职场文书
办公室主任岗位职责范本
2015/03/31 职场文书
MySQL复制问题的三个参数分析
2021/04/07 MySQL
SpringBoot系列之MongoDB Aggregations用法详解
2022/02/12 MongoDB
Python批量解压&压缩文件夹的示例代码
2022/04/04 Python