PyQt5+ Python3 模拟QQ群聊
尊重原创,原文链接
关于PyQt5+Python3开发环境安装可以参考上篇。这里实现的是一种群聊工具,socket类使用的是Qt的TcpSocket, TcpServer 类;线程类使用的QTread;收发数据使用的QDataStream(当然也可以用其他的方式,不唯一)
先看服务器端, 服务端界面设计,下面标注红色的是后面代码中要使用的控件,其它的可要可不要 # Ui_server.py
- # -*- coding: utf-8 -*-
- #
- # Created by: PyQt5 UI code generator 5.9.2
- #
- # WARNING! All changes made in this file will be lost!
- from PyQt5 import QtCore, QtGui, QtWidgets
- class Ui_Form(object):
- def setupUi(self, Form):
- Form.setObjectName( "Form")
- Form.resize( 541, 355)
- self.splitter_2 = QtWidgets.QSplitter(Form)
- self.splitter_2.setGeometry(QtCore.QRect( 10, 10, 516, 331))
- self.splitter_2.setOrientation(QtCore.Qt.Vertical)
- self.splitter_2.setObjectName( "splitter_2")
- self.layoutWidget = QtWidgets.QWidget(self.splitter_2)
- self.layoutWidget.setObjectName( "layoutWidget")
- self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget)
- self.horizontalLayout.setContentsMargins( 0, 0, 0, 0)
- self.horizontalLayout.setObjectName( "horizontalLayout")
- self.label = QtWidgets.QLabel(self.layoutWidget)
- self.label.setObjectName( "label")
- self.horizontalLayout.addWidget(self.label)
- self.txtPort = QtWidgets.QLineEdit(self.layoutWidget)
- self.txtPort.setObjectName( "txtPort")
- self.horizontalLayout.addWidget(self.txtPort)
- self.btnCreate = QtWidgets.QPushButton(self.layoutWidget)
- self.btnCreate.setObjectName( "btnCreate")
- self.horizontalLayout.addWidget(self.btnCreate)
- self.splitter = QtWidgets.QSplitter(self.splitter_2)
- self.splitter.setOrientation(QtCore.Qt.Horizontal)
- self.splitter.setObjectName( "splitter")
- self.layoutWidget1 = QtWidgets.QWidget(self.splitter)
- self.layoutWidget1.setObjectName( "layoutWidget1")
- self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget1)
- self.verticalLayout.setContentsMargins( 0, 0, 0, 0)
- self.verticalLayout.setObjectName( "verticalLayout")
- self.lbUserInfo = QtWidgets.QLabel(self.layoutWidget1)
- self.lbUserInfo.setObjectName( "lbUserInfo")
- self.verticalLayout.addWidget(self.lbUserInfo)
- self.listUser = QtWidgets.QListWidget(self.layoutWidget1)
- self.listUser.setObjectName( "listUser")
- self.verticalLayout.addWidget(self.listUser)
- self.layoutWidget2 = QtWidgets.QWidget(self.splitter)
- self.layoutWidget2.setObjectName( "layoutWidget2")
- self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.layoutWidget2)
- self.verticalLayout_2.setContentsMargins( 0, 0, 0, 0)
- self.verticalLayout_2.setObjectName( "verticalLayout_2")
- self.chatInfo = QtWidgets.QLabel(self.layoutWidget2)
- self.chatInfo.setObjectName( "chatInfo")
- self.verticalLayout_2.addWidget(self.chatInfo)
- self.browerChat = QtWidgets.QTextBrowser(self.layoutWidget2)
- self.browerChat.setObjectName("browerChat")
- self.verticalLayout_2.addWidget(self.browerChat)
- self.retranslateUi(Form)
- QtCore.QMetaObject.connectSlotsByName(Form)
- def retranslateUi(self, Form):
- _translate = QtCore.QCoreApplication.translate
- Form.setWindowTitle(_translate( "Form", "Form"))
- self.label.setText(_translate( "Form", "ADDRESS:PORT:"))
- self.btnCreate.setText(_translate( "Form", "create"))
- self.lbUserInfo.setText(_translate( "Form", "用户列表"))
- self.chatInfo.setText(_translate( "Form", "聊天信息"))
- if __name__ == "__main__":
- import sys
- app = QtWidgets.QApplication(sys.argv)
- Form = QtWidgets.QWidget()
- ui = Ui_Form()
- ui.setupUi(Form)
- Form.show()
- sys.exit(app.exec_())
服务器端实现:
在服务器端,使用QTcpServer类监听接入客户端,对于QTcpServer类,一旦有用户接入,就调用
其incomingConnection函数 。要使用多线程的话,就要在incomingConnection中调用线程类QThread,生成新的线程,
然后再新的线程run函数中调用QTcpSocket实例,进行数据的收发,在这里我也把QTcpSocket类重新实现了,也可以就在
重载的Thread实现QTcpSocket收发的代码,代码如下:
- # -*- coding: utf-8 -*-
- """
- Module implementing Server.
- """
- from PyQt5.QtCore import *
- from PyQt5.QtWidgets import *
- from PyQt5.QtNetwork import *
- from Ui_server import Ui_Form
- import sys
- PORT = 26711
- SIZEOF_UINT16 = 2
- class TcpSocket(QTcpSocket):
- '''
- 实现了客户端读取信息与发送信息到客户端的功能
- '''
- #信号用于Thread类中,信号在接受客户端信息后发送,一直发送到Server更新其界面
- #上级为Thead, 先将其交付给连接Thread中的slotRecv
- signRecv= pyqtSignal(str, str)
- def __init__(self, socketId, parent=None):
- super(TcpSocket, self).__init__(parent)
- self.socketId = socketId
- #客户端发送信息就接受
- self.readyRead.connect(self.slotRecv)
- #接受信息槽函数, client-> slotRecv -> Server
- def slotRecv(self):
- #使用QDataStream接受信息,也可以选择其他接受方式
- while self.state() == QAbstractSocket.ConnectedState:
- nextBlockSize = 0
- stream = QDataStream(self)
- if self.bytesAvailable() >= SIZEOF_UINT16:
- nextBlockSize = stream.readUInt16()
- else:
- #print('cannot read client')
- break
- if self.bytesAvailable() < nextBlockSize:
- break
- action = ''
- msg = ''
- action = stream.readQString()
- msg = stream.readQString()
- clientAddress = self.peerAddress().toString()
- clientPort = str(self.peerPort())
- msg= clientAddress+ ':'+clientPort + ' '+ msg+ '\n'
- #发射给上级Thread
- self.signRecv.emit(action, msg)
- #发送信息槽函数,其变量数据来源于上级 Server -> slotSend -> client
- def slotSend(self, action, msg, id):
- #print(id)
- #print(int(self.socketId))
- if id == int(self.socketId):
- reply = QByteArray()
- stream = QDataStream(reply, QIODevice.WriteOnly)
- stream.writeUInt16( 0)
- stream.writeQString(action)
- stream.writeQString(msg)
- stream.device().seek( 0)
- stream.writeUInt16(reply.size() - SIZEOF_UINT16)
- self.write(reply)
- class Thread(QThread):
- '''
- 线程类,在run中创建socket变量, 然后充当中介,向下交付slotSend中参数,
- 向上交付slotRecv中得到的参数
- '''
- #被TcpServer 使用,在类中发射
- signRecv = pyqtSignal(str, str)
- #在类中使用, 连接socket类中的slotSend
- signSend = pyqtSignal(str, str, int)
- def __init__(self, socketId, parent):
- super(Thread, self).__init__(parent)
- self.socketId = socketId
- def run(self):
- socket = TcpSocket(self.socketId)
- if not socket.setSocketDescriptor(self.socketId):
- return
- #socket类中的signRecv信号在此连接,把socket接受到的信息作为参数传递给Thread的slotSend
- socket.signRecv.connect(self.slotRecv)
- #Thread类中的signSend连接socket中slotSend, 将从上级得到的信息传递给socket,最后发送到客户端
- self.signSend.connect(socket.slotSend)
- #循环,不加这一句会出问题
- self.exec_()
- def slotSend(self, action, msg, id):
- if action == '':
- return
- #发射到socket中
- self.signSend.emit(action, msg, id)
- def slotRecv(self, action, msg):
- #发射到TcpServer
- self.signRecv.emit(action, msg)
- class TcpServer(QTcpServer):
- signRecv= pyqtSignal(str, str)
- signSend = pyqtSignal(str, str, int)
- #存放客户端socket的socketId
- socketList = []
- def __init__(self, parent=None):
- super(TcpServer, self).__init__(parent)
- def incomingConnection(self, socketId):
- #一有客户端进来就加入列表,当然在此要判断列表是否存在该客户端
- self.socketList.append(socketId)
- #创建线程
- thread = Thread(socketId, self)
- #thread发射后,调用tcpserver的slotRecv ,然后其又发射自身的signRecv信号,该信号在Server中连接Server的slotRecv
- thread.signRecv.connect(self.slotRecv)
- #连接thread的slotSend
- self.signSend.connect(thread.slotSend)
- thread.finished.connect(thread.deleteLater)
- thread.start()
- def slotRecv(self, action, msg):
- self.signRecv.emit(action, msg)
- class Server(QWidget, Ui_Form):
- """
- Class documentation goes here.
- """
- signSend = pyqtSignal(str, str, int)
- def __init__(self, parent=None):
- """
- Constructor
- @param parent reference to the parent widget
- @type QWidget
- """
- super(Server, self).__init__(parent)
- self.setupUi(self)
- self.server= TcpServer(self)
- self.server.listen(QHostAddress( "0.0.0.0"), PORT)
- #tcoServer中的signRecv信号,连接自身的slotRecv
- self.server.signRecv.connect(self.slotRecv)
- def slotRecv(self, action, msg):
- self.updateServer(action, msg)
- for id in self.server.socketList:
- self.server.signSend.emit(action, msg, id)
- def showConnection(self):
- print(self.server.socketList)
- #def slotSend(self, action, msg):
- def updateServer(self, action, msg):
- if action == 'USER':
- self.listUser.addItem( '*user*: '+ msg)
- if action == 'MSG':
- self.browerChat.append(msg)
- @pyqtSlot()
- def on_btnCreate_clicked(self):
- """
- Slot documentation goes here.
- """
- # TODO: not implemented yet
- if __name__ == '__main__':
- app = QApplication(sys.argv)
- serverDlg = Server()
- serverDlg.show()
- app.exec_()
客户端实现, 客户端类似服务器端,只不过多了一个发送消息的LineEdit, 取名为txtInput, 发送按钮btnSend
代码(Ui_client.py):
- # -*- coding: utf-8 -*-
- #
- # Created by: PyQt5 UI code generator 5.9.2
- #
- # WARNING! All changes made in this file will be lost!
- from PyQt5 import QtCore, QtGui, QtWidgets
- class Ui_Form(object):
- def setupUi(self, Form):
- Form.setObjectName( "Form")
- Form.resize( 455, 366)
- self.listUser = QtWidgets.QListWidget(Form)
- self.listUser.setGeometry(QtCore.QRect( 0, 10, 161, 341))
- self.listUser.setObjectName( "listUser")
- self.splitter_2 = QtWidgets.QSplitter(Form)
- self.splitter_2.setGeometry(QtCore.QRect( 180, 10, 256, 341))
- self.splitter_2.setOrientation(QtCore.Qt.Vertical)
- self.splitter_2.setObjectName( "splitter_2")
- self.setNameWidget = QtWidgets.QWidget(self.splitter_2)
- self.setNameWidget.setObjectName( "setNameWidget")
- self.horizontalLayout = QtWidgets.QHBoxLayout(self.setNameWidget)
- self.horizontalLayout.setContentsMargins( 0, 0, 0, 0)
- self.horizontalLayout.setObjectName( "horizontalLayout")
- self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_3.setObjectName( "horizontalLayout_3")
- self.lbName = QtWidgets.QLabel(self.setNameWidget)
- self.lbName.setObjectName( "lbName")
- self.horizontalLayout_3.addWidget(self.lbName)
- self.txtName = QtWidgets.QLineEdit(self.setNameWidget)
- self.txtName.setObjectName( "txtName")
- self.horizontalLayout_3.addWidget(self.txtName)
- self.btnSet = QtWidgets.QPushButton(self.setNameWidget)
- self.btnSet.setObjectName( "btnSet")
- self.horizontalLayout_3.addWidget(self.btnSet)
- self.horizontalLayout.addLayout(self.horizontalLayout_3)
- self.browerChat = QtWidgets.QTextBrowser(self.splitter_2)
- self.browerChat.setObjectName( "browerChat")
- self.layoutWidget = QtWidgets.QWidget(self.splitter_2)
- self.layoutWidget.setObjectName( "layoutWidget")
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.layoutWidget)
- self.horizontalLayout_2.setContentsMargins( 0, 0, 0, 0)
- self.horizontalLayout_2.setObjectName( "horizontalLayout_2")
- self.txtInput = QtWidgets.QLineEdit(self.layoutWidget)
- self.txtInput.setObjectName( "txtInput")
- self.horizontalLayout_2.addWidget(self.txtInput)
- self.btnSend = QtWidgets.QPushButton(self.layoutWidget)
- self.btnSend.setObjectName( "btnSend")
- self.horizontalLayout_2.addWidget(self.btnSend)
- self.retranslateUi(Form)
- QtCore.QMetaObject.connectSlotsByName(Form)
- def retranslateUi(self, Form):
- _translate = QtCore.QCoreApplication.translate
- Form.setWindowTitle(_translate( "Form", "Form"))
- self.lbName.setText(_translate( "Form", "name"))
- self.txtName.setText(_translate( "Form", "deafault"))
- self.btnSet.setText(_translate( "Form", "SetConnection"))
- self.btnSend.setText(_translate( "Form", "Send"))
- if __name__ == "__main__":
- import sys
- app = QtWidgets.QApplication(sys.argv)
- Form = QtWidgets.QWidget()
- ui = Ui_Form()
- ui.setupUi(Form)
- Form.show()
- sys.exit(app.exec_())
client.py 代码:
- # -*- coding: utf-8 -*-
- """
- Module implementing Client.
- """
- from PyQt5.QtCore import *
- from PyQt5.QtWidgets import *
- from PyQt5.QtNetwork import *
- from Ui_client import Ui_Form
- import sys
- import time
- PORT = 26711
- SIZEOF_UINT16 = 2
- class Client(QWidget, Ui_Form):
- """
- Class documentation goes here.
- """
- def __init__(self, parent=None):
- """
- Constructor
- @param parent reference to the parent widget
- @type QWidget
- """
- super(Client, self).__init__(parent)
- self.setupUi(self)
- self.socket = QTcpSocket()
- self.request = None
- #self.nextBlockSize = 0
- self.name = ''
- #信号
- #self.socket.connected.connected.connect(self.sendRequest)
- self.socket.readyRead.connect(self.readResponse)
- self.socket.disconnected.connect(self.serverHasStopped)
- self.socket.error.connect(self.serverHasError)
- #if self.socket.isOpen() == False:
- #self.socket.connectToHost("localhost", PORT)
- @pyqtSlot()
- def on_btnSend_clicked(self):
- """
- Slot documentation goes here.
- """
- # TODO: not implemented yet
- if self.socket.isOpen() :
- #self.socket.disconnected.emit
- self.socket.close()
- self.socket.connectToHost( "localhost", PORT)
- msg =self.name + ' '+\
- time.strftime( '%Y-%m-%d %H:%M:%S',time.localtime(time.time()))+ '\n'+\
- self.txtInput.text()
- self.issueRequest( 'MSG', msg)
- self.txtInput.setText( '')
- @pyqtSlot()
- def on_txtInput_returnPressed(self):
- """
- Slot documentation goes here.
- """
- # TODO: not implemented yet
- pass
- @pyqtSlot()
- def on_btnSet_clicked(self):
- """
- Slot documentation goes here.
- """
- if self.socket.isOpen() :
- self.socket.close()
- self.socket.connectToHost( "localhost", PORT)
- # TODO: not implemented yet
- self.name = self.txtName.text()
- self.issueRequest( 'USER', self.name)
- def issueRequest(self, action, msg):
- print( 'sendRequest')
- self.request= QByteArray()
- stream = QDataStream(self.request, QIODevice.WriteOnly)
- stream.writeUInt16( 0)
- stream.writeQString(action)
- stream.writeQString(msg)
- stream.device().seek( 0)
- stream.writeUInt16(self.request.size() - SIZEOF_UINT16) #overwrite seek(0)
- self.socket.write(self.request)
- self.nextBlockSize = 0
- self.requst = None
- def readResponse(self):
- nextBlockSize = 0
- stream = QDataStream(self.socket)
- while 1:
- if nextBlockSize == 0 :
- if self.socket.bytesAvailable() <SIZEOF_UINT16:
- break
- nextBlockSize = stream.readUInt16()
- if self.socket.bytesAvailable() < nextBlockSize:
- break
- action = ''
- msg = ''
- action = stream.readQString()
- msg = stream.readQString()
- print(msg)
- if action == 'MSG':
- self.browerChat.append(msg)
- if action == 'USER':
- self.listUser.addItem( '*user*: '+ msg)
- def serverHasStopped(self):
- #self.issueRequest('CLOSE', self.name)
- #self.socket.close()
- print( 'socket is close')
- def serverHasError(self, error):
- self.socket.close()
- if __name__ == '__main__':
- app = QApplication(sys.argv)
- dlg = Client()
- dlg.show()
- sys.exit(app.exec_())
运行结果如下:
之前的设计中经常出现Cannot create children for a parent that is in a different thread
Socket notifiers cannot be enabled or disabled from another thread等问题
但是以上代码是都解决了这些问题,当然还有很多不足之处,比如说用户列表那一块,懒得搞了,以后再说吧
PyQt5+ Python3 模拟QQ群聊相关推荐
- Java项目模拟QQ群聊和私聊(网络编程+多线程)
[文末获取资源] 前几天学习了多线程,最近在学习网络编程,了解了UDP之后又学习了TCP,听一下大佬说,要看看你这两个东西掌握的怎么样,最好的办法就是写一个模拟QQ群聊和私聊,经过这几天的学习,以及不 ...
- Java网络编程,模拟QQ群聊功能
Java网络编程,模拟QQ群聊功能 一.网络编程知识点简介: 1.C/S架构:Client客户端/Server服务器: 涉及到的应用:桌面的应用软件,QQ,王者荣耀 涉及到的技术:Socket网络编程 ...
- 基于QML模拟QQ群聊窗口
据说最近要开发简单的IM工具,于是兴起,研究了下QQ聊天窗口,大概模拟了一下群聊的聊天显示界面,遂与大家分享之 画面粗糙还望海涵 图片有点大,原理基本就是使用listview来显示每一条记录,别人的记 ...
- QQ 群聊美少女语音AI(ChatGPT版)
QQ 群聊美少女语音AI ✨ 基于 go-cqhttp 以及 VITS-fast-fine-tuning + revChatGPT 实现 ✨ Combination of ChatGPT and VI ...
- QQ 群聊美少女语音AI(ChatGLM 本地化版本)
QQ 群聊美少女语音AI(ChatGLM 本地化版本) ✨ 基于 go-cqhttp 以及 VITS-fast-fine-tuning + ChatGLM 实现 ✨ Combination of Ch ...
- C++可视化-----QQ群聊系统
目 录 第二章 概要设计 第三章 详细设计 第四章 测试报告 第五章 安装及使用 第六章 项目总结 需求分析 写这个是看着视频写出来的,项 ...
- 基于python实现类似qq群聊功能
这篇文章主要记录了自己学习python时学习到的使用python来实现类似qq群聊的功能,整个项目分为服务器端和客户端两个部分,具体的实现如下: 一.具体代码 1.服务器端(Server.py) im ...
- 多线程+SOCKET编程实现qq群聊的服务端和客户端
多线程+SOCKET编程实现qq群聊的服务端和客户端 标签(空格分隔): 多线程 网络编程 线程同步 一.设计思路 1.服务端 每来一个客户端连接,服务端起一个线程维护: 将收到的消息转发给所有的客户 ...
- java模仿微信QQ群聊头像拼接,根据群聊内的用户头像拼接群聊头像,九宫格
java模仿微信QQ群聊头像拼接,根据群聊内的用户头像拼接群聊头像,九宫格 效果图,这里只放了几张,1-9张图片都可以的,如果图片路径是从数据库查出来的相对路径,记得加上绝对路径否则会报找不到读取文件 ...
最新文章
- unity 2d 游戏优化之路 遇坑记录
- 今日 Paper | 多人线性模型;身体捕捉;会话问答;自然语言解析;神经语义
- jdbc批量调用oracle存储过程,oracle学习笔记(二十三)——JDBC调用存储过程以及批量操作...
- C# 与 LUA 的经验对比
- 264,avs重要的变量:
- 首次披露!阿里线下智能方案进化史
- pythoncsv文件的操作_python操作CSV文件
- 【OpenCV 例程200篇】62. 图像锐化——钝化掩蔽
- 企业上市上市央企大面积亏损折射出啥弊端?
- netty 校验_Netty SSL双向验证
- 云小课 |选定合适的证书,做“有证”的合规域名
- python能编游戏吗_python能做游戏吗
- QThread多线程编程分析
- Ghost XP SP2 64位 纯净珍藏版
- MuMu模拟器安装面具magisk24.1教程
- pycharm连接MySQL数据库
- 电阻何时取得最大功率
- FZU 1685 跑跑卡丁车
- 关于MAC下的SSH工具的推荐及SSH如何连接本地的小教程
- Swift MD5加密
热门文章
- max-width 无效的解决方法
- RTL8380M/RTL8382M管理型交换机系统软件操作指南二:转发表
- 百度 UE ueditor 实例化 Cannot set property 'innerHTML' of null 解决方案
- bullet 库的概述
- 我的 Oracle ACE 心路历程
- 头部咨询管理企业的数字化转型之路
- 2021鸿蒙开发者大会,全新鸿蒙 HarmonyOS 将至?余承东:华为开发者大会 2021(Cloud)来了...
- python数据分析:数据库基本操作(SQLite)
- 文本框——TextField 的使用
- 人工智能中研究计算机如何自动获取知识,西安科技大学人工智能题库8(含答案).doc...