尊重原创,原文链接

关于PyQt5+Python3开发环境安装可以参考上篇。这里实现的是一种群聊工具,socket类使用的是Qt的TcpSocket, TcpServer 类;线程类使用的QTread;收发数据使用的QDataStream(当然也可以用其他的方式,不唯一)

先看服务器端, 服务端界面设计,下面标注红色的是后面代码中要使用的控件,其它的可要可不要   # Ui_server.py

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Created by: PyQt5 UI code generator 5.9.2
  4. #
  5. # WARNING! All changes made in this file will be lost!
  6. from PyQt5 import QtCore, QtGui, QtWidgets
  7. class Ui_Form(object):
  8. def setupUi(self, Form):
  9. Form.setObjectName( "Form")
  10. Form.resize( 541, 355)
  11. self.splitter_2 = QtWidgets.QSplitter(Form)
  12. self.splitter_2.setGeometry(QtCore.QRect( 10, 10, 516, 331))
  13. self.splitter_2.setOrientation(QtCore.Qt.Vertical)
  14. self.splitter_2.setObjectName( "splitter_2")
  15. self.layoutWidget = QtWidgets.QWidget(self.splitter_2)
  16. self.layoutWidget.setObjectName( "layoutWidget")
  17. self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget)
  18. self.horizontalLayout.setContentsMargins( 0, 0, 0, 0)
  19. self.horizontalLayout.setObjectName( "horizontalLayout")
  20. self.label = QtWidgets.QLabel(self.layoutWidget)
  21. self.label.setObjectName( "label")
  22. self.horizontalLayout.addWidget(self.label)
  23. self.txtPort = QtWidgets.QLineEdit(self.layoutWidget)
  24. self.txtPort.setObjectName( "txtPort")
  25. self.horizontalLayout.addWidget(self.txtPort)
  26. self.btnCreate = QtWidgets.QPushButton(self.layoutWidget)
  27. self.btnCreate.setObjectName( "btnCreate")
  28. self.horizontalLayout.addWidget(self.btnCreate)
  29. self.splitter = QtWidgets.QSplitter(self.splitter_2)
  30. self.splitter.setOrientation(QtCore.Qt.Horizontal)
  31. self.splitter.setObjectName( "splitter")
  32. self.layoutWidget1 = QtWidgets.QWidget(self.splitter)
  33. self.layoutWidget1.setObjectName( "layoutWidget1")
  34. self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget1)
  35. self.verticalLayout.setContentsMargins( 0, 0, 0, 0)
  36. self.verticalLayout.setObjectName( "verticalLayout")
  37. self.lbUserInfo = QtWidgets.QLabel(self.layoutWidget1)
  38. self.lbUserInfo.setObjectName( "lbUserInfo")
  39. self.verticalLayout.addWidget(self.lbUserInfo)
  40. self.listUser = QtWidgets.QListWidget(self.layoutWidget1)
  41. self.listUser.setObjectName( "listUser")
  42. self.verticalLayout.addWidget(self.listUser)
  43. self.layoutWidget2 = QtWidgets.QWidget(self.splitter)
  44. self.layoutWidget2.setObjectName( "layoutWidget2")
  45. self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.layoutWidget2)
  46. self.verticalLayout_2.setContentsMargins( 0, 0, 0, 0)
  47. self.verticalLayout_2.setObjectName( "verticalLayout_2")
  48. self.chatInfo = QtWidgets.QLabel(self.layoutWidget2)
  49. self.chatInfo.setObjectName( "chatInfo")
  50. self.verticalLayout_2.addWidget(self.chatInfo)
  51. self.browerChat = QtWidgets.QTextBrowser(self.layoutWidget2)
  52. self.browerChat.setObjectName("browerChat")
  53. self.verticalLayout_2.addWidget(self.browerChat)
  54. self.retranslateUi(Form)
  55. QtCore.QMetaObject.connectSlotsByName(Form)
  56. def retranslateUi(self, Form):
  57. _translate = QtCore.QCoreApplication.translate
  58. Form.setWindowTitle(_translate( "Form", "Form"))
  59. self.label.setText(_translate( "Form", "ADDRESS:PORT:"))
  60. self.btnCreate.setText(_translate( "Form", "create"))
  61. self.lbUserInfo.setText(_translate( "Form", "用户列表"))
  62. self.chatInfo.setText(_translate( "Form", "聊天信息"))
  63. if __name__ == "__main__":
  64. import sys
  65. app = QtWidgets.QApplication(sys.argv)
  66. Form = QtWidgets.QWidget()
  67. ui = Ui_Form()
  68. ui.setupUi(Form)
  69. Form.show()
  70. sys.exit(app.exec_())

服务器端实现:

在服务器端,使用QTcpServer类监听接入客户端,对于QTcpServer类,一旦有用户接入,就调用

其incomingConnection函数 。要使用多线程的话,就要在incomingConnection中调用线程类QThread,生成新的线程,

然后再新的线程run函数中调用QTcpSocket实例,进行数据的收发,在这里我也把QTcpSocket类重新实现了,也可以就在

重载的Thread实现QTcpSocket收发的代码,代码如下:

  1. # -*- coding: utf-8 -*-
  2. """
  3. Module implementing Server.
  4. """
  5. from PyQt5.QtCore import *
  6. from PyQt5.QtWidgets import *
  7. from PyQt5.QtNetwork import *
  8. from Ui_server import Ui_Form
  9. import sys
  10. PORT = 26711
  11. SIZEOF_UINT16 = 2
  12. class TcpSocket(QTcpSocket):
  13. '''
  14. 实现了客户端读取信息与发送信息到客户端的功能
  15. '''
  16. #信号用于Thread类中,信号在接受客户端信息后发送,一直发送到Server更新其界面
  17. #上级为Thead, 先将其交付给连接Thread中的slotRecv
  18. signRecv= pyqtSignal(str, str)
  19. def __init__(self, socketId, parent=None):
  20. super(TcpSocket, self).__init__(parent)
  21. self.socketId = socketId
  22. #客户端发送信息就接受
  23. self.readyRead.connect(self.slotRecv)
  24. #接受信息槽函数, client-> slotRecv -> Server
  25. def slotRecv(self):
  26. #使用QDataStream接受信息,也可以选择其他接受方式
  27. while self.state() == QAbstractSocket.ConnectedState:
  28. nextBlockSize = 0
  29. stream = QDataStream(self)
  30. if self.bytesAvailable() >= SIZEOF_UINT16:
  31. nextBlockSize = stream.readUInt16()
  32. else:
  33. #print('cannot read client')
  34. break
  35. if self.bytesAvailable() < nextBlockSize:
  36. break
  37. action = ''
  38. msg = ''
  39. action = stream.readQString()
  40. msg = stream.readQString()
  41. clientAddress = self.peerAddress().toString()
  42. clientPort = str(self.peerPort())
  43. msg= clientAddress+ ':'+clientPort + ' '+ msg+ '\n'
  44. #发射给上级Thread
  45. self.signRecv.emit(action, msg)
  46. #发送信息槽函数,其变量数据来源于上级 Server -> slotSend -> client
  47. def slotSend(self, action, msg, id):
  48. #print(id)
  49. #print(int(self.socketId))
  50. if id == int(self.socketId):
  51. reply = QByteArray()
  52. stream = QDataStream(reply, QIODevice.WriteOnly)
  53. stream.writeUInt16( 0)
  54. stream.writeQString(action)
  55. stream.writeQString(msg)
  56. stream.device().seek( 0)
  57. stream.writeUInt16(reply.size() - SIZEOF_UINT16)
  58. self.write(reply)
  59. class Thread(QThread):
  60. '''
  61. 线程类,在run中创建socket变量, 然后充当中介,向下交付slotSend中参数,
  62. 向上交付slotRecv中得到的参数
  63. '''
  64. #被TcpServer 使用,在类中发射
  65. signRecv = pyqtSignal(str, str)
  66. #在类中使用, 连接socket类中的slotSend
  67. signSend = pyqtSignal(str, str, int)
  68. def __init__(self, socketId, parent):
  69. super(Thread, self).__init__(parent)
  70. self.socketId = socketId
  71. def run(self):
  72. socket = TcpSocket(self.socketId)
  73. if not socket.setSocketDescriptor(self.socketId):
  74. return
  75. #socket类中的signRecv信号在此连接,把socket接受到的信息作为参数传递给Thread的slotSend
  76. socket.signRecv.connect(self.slotRecv)
  77. #Thread类中的signSend连接socket中slotSend, 将从上级得到的信息传递给socket,最后发送到客户端
  78. self.signSend.connect(socket.slotSend)
  79. #循环,不加这一句会出问题
  80. self.exec_()
  81. def slotSend(self, action, msg, id):
  82. if action == '':
  83. return
  84. #发射到socket中
  85. self.signSend.emit(action, msg, id)
  86. def slotRecv(self, action, msg):
  87. #发射到TcpServer
  88. self.signRecv.emit(action, msg)
  89. class TcpServer(QTcpServer):
  90. signRecv= pyqtSignal(str, str)
  91. signSend = pyqtSignal(str, str, int)
  92. #存放客户端socket的socketId
  93. socketList = []
  94. def __init__(self, parent=None):
  95. super(TcpServer, self).__init__(parent)
  96. def incomingConnection(self, socketId):
  97. #一有客户端进来就加入列表,当然在此要判断列表是否存在该客户端
  98. self.socketList.append(socketId)
  99. #创建线程
  100. thread = Thread(socketId, self)
  101. #thread发射后,调用tcpserver的slotRecv ,然后其又发射自身的signRecv信号,该信号在Server中连接Server的slotRecv
  102. thread.signRecv.connect(self.slotRecv)
  103. #连接thread的slotSend
  104. self.signSend.connect(thread.slotSend)
  105. thread.finished.connect(thread.deleteLater)
  106. thread.start()
  107. def slotRecv(self, action, msg):
  108. self.signRecv.emit(action, msg)
  109. class Server(QWidget, Ui_Form):
  110. """
  111. Class documentation goes here.
  112. """
  113. signSend = pyqtSignal(str, str, int)
  114. def __init__(self, parent=None):
  115. """
  116. Constructor
  117. @param parent reference to the parent widget
  118. @type QWidget
  119. """
  120. super(Server, self).__init__(parent)
  121. self.setupUi(self)
  122. self.server= TcpServer(self)
  123. self.server.listen(QHostAddress( "0.0.0.0"), PORT)
  124. #tcoServer中的signRecv信号,连接自身的slotRecv
  125. self.server.signRecv.connect(self.slotRecv)
  126. def slotRecv(self, action, msg):
  127. self.updateServer(action, msg)
  128. for id in self.server.socketList:
  129. self.server.signSend.emit(action, msg, id)
  130. def showConnection(self):
  131. print(self.server.socketList)
  132. #def slotSend(self, action, msg):
  133. def updateServer(self, action, msg):
  134. if action == 'USER':
  135. self.listUser.addItem( '*user*: '+ msg)
  136. if action == 'MSG':
  137. self.browerChat.append(msg)
  138. @pyqtSlot()
  139. def on_btnCreate_clicked(self):
  140. """
  141. Slot documentation goes here.
  142. """
  143. # TODO: not implemented yet
  144. if __name__ == '__main__':
  145. app = QApplication(sys.argv)
  146. serverDlg = Server()
  147. serverDlg.show()
  148. app.exec_()

客户端实现, 客户端类似服务器端,只不过多了一个发送消息的LineEdit, 取名为txtInput, 发送按钮btnSend

代码(Ui_client.py):

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Created by: PyQt5 UI code generator 5.9.2
  4. #
  5. # WARNING! All changes made in this file will be lost!
  6. from PyQt5 import QtCore, QtGui, QtWidgets
  7. class Ui_Form(object):
  8. def setupUi(self, Form):
  9. Form.setObjectName( "Form")
  10. Form.resize( 455, 366)
  11. self.listUser = QtWidgets.QListWidget(Form)
  12. self.listUser.setGeometry(QtCore.QRect( 0, 10, 161, 341))
  13. self.listUser.setObjectName( "listUser")
  14. self.splitter_2 = QtWidgets.QSplitter(Form)
  15. self.splitter_2.setGeometry(QtCore.QRect( 180, 10, 256, 341))
  16. self.splitter_2.setOrientation(QtCore.Qt.Vertical)
  17. self.splitter_2.setObjectName( "splitter_2")
  18. self.setNameWidget = QtWidgets.QWidget(self.splitter_2)
  19. self.setNameWidget.setObjectName( "setNameWidget")
  20. self.horizontalLayout = QtWidgets.QHBoxLayout(self.setNameWidget)
  21. self.horizontalLayout.setContentsMargins( 0, 0, 0, 0)
  22. self.horizontalLayout.setObjectName( "horizontalLayout")
  23. self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
  24. self.horizontalLayout_3.setObjectName( "horizontalLayout_3")
  25. self.lbName = QtWidgets.QLabel(self.setNameWidget)
  26. self.lbName.setObjectName( "lbName")
  27. self.horizontalLayout_3.addWidget(self.lbName)
  28. self.txtName = QtWidgets.QLineEdit(self.setNameWidget)
  29. self.txtName.setObjectName( "txtName")
  30. self.horizontalLayout_3.addWidget(self.txtName)
  31. self.btnSet = QtWidgets.QPushButton(self.setNameWidget)
  32. self.btnSet.setObjectName( "btnSet")
  33. self.horizontalLayout_3.addWidget(self.btnSet)
  34. self.horizontalLayout.addLayout(self.horizontalLayout_3)
  35. self.browerChat = QtWidgets.QTextBrowser(self.splitter_2)
  36. self.browerChat.setObjectName( "browerChat")
  37. self.layoutWidget = QtWidgets.QWidget(self.splitter_2)
  38. self.layoutWidget.setObjectName( "layoutWidget")
  39. self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.layoutWidget)
  40. self.horizontalLayout_2.setContentsMargins( 0, 0, 0, 0)
  41. self.horizontalLayout_2.setObjectName( "horizontalLayout_2")
  42. self.txtInput = QtWidgets.QLineEdit(self.layoutWidget)
  43. self.txtInput.setObjectName( "txtInput")
  44. self.horizontalLayout_2.addWidget(self.txtInput)
  45. self.btnSend = QtWidgets.QPushButton(self.layoutWidget)
  46. self.btnSend.setObjectName( "btnSend")
  47. self.horizontalLayout_2.addWidget(self.btnSend)
  48. self.retranslateUi(Form)
  49. QtCore.QMetaObject.connectSlotsByName(Form)
  50. def retranslateUi(self, Form):
  51. _translate = QtCore.QCoreApplication.translate
  52. Form.setWindowTitle(_translate( "Form", "Form"))
  53. self.lbName.setText(_translate( "Form", "name"))
  54. self.txtName.setText(_translate( "Form", "deafault"))
  55. self.btnSet.setText(_translate( "Form", "SetConnection"))
  56. self.btnSend.setText(_translate( "Form", "Send"))
  57. if __name__ == "__main__":
  58. import sys
  59. app = QtWidgets.QApplication(sys.argv)
  60. Form = QtWidgets.QWidget()
  61. ui = Ui_Form()
  62. ui.setupUi(Form)
  63. Form.show()
  64. sys.exit(app.exec_())

client.py 代码:

  1. # -*- coding: utf-8 -*-
  2. """
  3. Module implementing Client.
  4. """
  5. from PyQt5.QtCore import *
  6. from PyQt5.QtWidgets import *
  7. from PyQt5.QtNetwork import *
  8. from Ui_client import Ui_Form
  9. import sys
  10. import time
  11. PORT = 26711
  12. SIZEOF_UINT16 = 2
  13. class Client(QWidget, Ui_Form):
  14. """
  15. Class documentation goes here.
  16. """
  17. def __init__(self, parent=None):
  18. """
  19. Constructor
  20. @param parent reference to the parent widget
  21. @type QWidget
  22. """
  23. super(Client, self).__init__(parent)
  24. self.setupUi(self)
  25. self.socket = QTcpSocket()
  26. self.request = None
  27. #self.nextBlockSize = 0
  28. self.name = ''
  29. #信号
  30. #self.socket.connected.connected.connect(self.sendRequest)
  31. self.socket.readyRead.connect(self.readResponse)
  32. self.socket.disconnected.connect(self.serverHasStopped)
  33. self.socket.error.connect(self.serverHasError)
  34. #if self.socket.isOpen() == False:
  35. #self.socket.connectToHost("localhost", PORT)
  36. @pyqtSlot()
  37. def on_btnSend_clicked(self):
  38. """
  39. Slot documentation goes here.
  40. """
  41. # TODO: not implemented yet
  42. if self.socket.isOpen() :
  43. #self.socket.disconnected.emit
  44. self.socket.close()
  45. self.socket.connectToHost( "localhost", PORT)
  46. msg =self.name + ' '+\
  47. time.strftime( '%Y-%m-%d %H:%M:%S',time.localtime(time.time()))+ '\n'+\
  48. self.txtInput.text()
  49. self.issueRequest( 'MSG', msg)
  50. self.txtInput.setText( '')
  51. @pyqtSlot()
  52. def on_txtInput_returnPressed(self):
  53. """
  54. Slot documentation goes here.
  55. """
  56. # TODO: not implemented yet
  57. pass
  58. @pyqtSlot()
  59. def on_btnSet_clicked(self):
  60. """
  61. Slot documentation goes here.
  62. """
  63. if self.socket.isOpen() :
  64. self.socket.close()
  65. self.socket.connectToHost( "localhost", PORT)
  66. # TODO: not implemented yet
  67. self.name = self.txtName.text()
  68. self.issueRequest( 'USER', self.name)
  69. def issueRequest(self, action, msg):
  70. print( 'sendRequest')
  71. self.request= QByteArray()
  72. stream = QDataStream(self.request, QIODevice.WriteOnly)
  73. stream.writeUInt16( 0)
  74. stream.writeQString(action)
  75. stream.writeQString(msg)
  76. stream.device().seek( 0)
  77. stream.writeUInt16(self.request.size() - SIZEOF_UINT16) #overwrite seek(0)
  78. self.socket.write(self.request)
  79. self.nextBlockSize = 0
  80. self.requst = None
  81. def readResponse(self):
  82. nextBlockSize = 0
  83. stream = QDataStream(self.socket)
  84. while 1:
  85. if nextBlockSize == 0 :
  86. if self.socket.bytesAvailable() <SIZEOF_UINT16:
  87. break
  88. nextBlockSize = stream.readUInt16()
  89. if self.socket.bytesAvailable() < nextBlockSize:
  90. break
  91. action = ''
  92. msg = ''
  93. action = stream.readQString()
  94. msg = stream.readQString()
  95. print(msg)
  96. if action == 'MSG':
  97. self.browerChat.append(msg)
  98. if action == 'USER':
  99. self.listUser.addItem( '*user*: '+ msg)
  100. def serverHasStopped(self):
  101. #self.issueRequest('CLOSE', self.name)
  102. #self.socket.close()
  103. print( 'socket is close')
  104. def serverHasError(self, error):
  105. self.socket.close()
  106. if __name__ == '__main__':
  107. app = QApplication(sys.argv)
  108. dlg = Client()
  109. dlg.show()
  110. 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群聊相关推荐

  1. Java项目模拟QQ群聊和私聊(网络编程+多线程)

    [文末获取资源] 前几天学习了多线程,最近在学习网络编程,了解了UDP之后又学习了TCP,听一下大佬说,要看看你这两个东西掌握的怎么样,最好的办法就是写一个模拟QQ群聊和私聊,经过这几天的学习,以及不 ...

  2. Java网络编程,模拟QQ群聊功能

    Java网络编程,模拟QQ群聊功能 一.网络编程知识点简介: 1.C/S架构:Client客户端/Server服务器: 涉及到的应用:桌面的应用软件,QQ,王者荣耀 涉及到的技术:Socket网络编程 ...

  3. 基于QML模拟QQ群聊窗口

    据说最近要开发简单的IM工具,于是兴起,研究了下QQ聊天窗口,大概模拟了一下群聊的聊天显示界面,遂与大家分享之 画面粗糙还望海涵 图片有点大,原理基本就是使用listview来显示每一条记录,别人的记 ...

  4. QQ 群聊美少女语音AI(ChatGPT版)

    QQ 群聊美少女语音AI ✨ 基于 go-cqhttp 以及 VITS-fast-fine-tuning + revChatGPT 实现 ✨ Combination of ChatGPT and VI ...

  5. QQ 群聊美少女语音AI(ChatGLM 本地化版本)

    QQ 群聊美少女语音AI(ChatGLM 本地化版本) ✨ 基于 go-cqhttp 以及 VITS-fast-fine-tuning + ChatGLM 实现 ✨ Combination of Ch ...

  6. C++可视化-----QQ群聊系统

    目  录 第二章       概要设计 第三章       详细设计 第四章       测试报告 第五章       安装及使用 第六章       项目总结 需求分析 写这个是看着视频写出来的,项 ...

  7. 基于python实现类似qq群聊功能

    这篇文章主要记录了自己学习python时学习到的使用python来实现类似qq群聊的功能,整个项目分为服务器端和客户端两个部分,具体的实现如下: 一.具体代码 1.服务器端(Server.py) im ...

  8. 多线程+SOCKET编程实现qq群聊的服务端和客户端

    多线程+SOCKET编程实现qq群聊的服务端和客户端 标签(空格分隔): 多线程 网络编程 线程同步 一.设计思路 1.服务端 每来一个客户端连接,服务端起一个线程维护: 将收到的消息转发给所有的客户 ...

  9. java模仿微信QQ群聊头像拼接,根据群聊内的用户头像拼接群聊头像,九宫格

    java模仿微信QQ群聊头像拼接,根据群聊内的用户头像拼接群聊头像,九宫格 效果图,这里只放了几张,1-9张图片都可以的,如果图片路径是从数据库查出来的相对路径,记得加上绝对路径否则会报找不到读取文件 ...

最新文章

  1. unity 2d 游戏优化之路 遇坑记录
  2. 今日 Paper | 多人线性模型;身体捕捉;会话问答;自然语言解析;神经语义
  3. jdbc批量调用oracle存储过程,oracle学习笔记(二十三)——JDBC调用存储过程以及批量操作...
  4. C# 与 LUA 的经验对比
  5. 264,avs重要的变量:
  6. 首次披露!阿里线下智能方案进化史
  7. pythoncsv文件的操作_python操作CSV文件
  8. 【OpenCV 例程200篇】62. 图像锐化——钝化掩蔽
  9. 企业上市上市央企大面积亏损折射出啥弊端?
  10. netty 校验_Netty SSL双向验证
  11. 云小课 |选定合适的证书,做“有证”的合规域名
  12. python能编游戏吗_python能做游戏吗
  13. QThread多线程编程分析
  14. Ghost XP SP2 64位 纯净珍藏版
  15. MuMu模拟器安装面具magisk24.1教程
  16. pycharm连接MySQL数据库
  17. 电阻何时取得最大功率
  18. FZU 1685 跑跑卡丁车
  19. 关于MAC下的SSH工具的推荐及SSH如何连接本地的小教程
  20. Swift MD5加密

热门文章

  1. max-width 无效的解决方法
  2. RTL8380M/RTL8382M管理型交换机系统软件操作指南二:转发表
  3. 百度 UE ueditor 实例化 Cannot set property 'innerHTML' of null 解决方案
  4. bullet 库的概述
  5. 我的 Oracle ACE 心路历程
  6. 头部咨询管理企业的数字化转型之路
  7. 2021鸿蒙开发者大会,全新鸿蒙 HarmonyOS 将至?余承东:华为开发者大会 2021(Cloud)来了...
  8. python数据分析:数据库基本操作(SQLite)
  9. 文本框——TextField 的使用
  10. 人工智能中研究计算机如何自动获取知识,西安科技大学人工智能题库8(含答案).doc...