I followed this solution and worked fine at the beginning.
Connect QWebEngine to proxy
But i cant change proxy again after i change it once or QWebEngineView doesnt cares it.
The original code contains more than that, so i purified it to a working example to demonstrate my problem
Assume our ip is "x.x.x.x", proxy1's ip is "y.y.y.y", and proxy2's ip is "z.z.z.z"
When you run the sample code you have to see
x.x.x.x
y.y.y.y
z.z.z.z
but i got
x.x.x.x
y.y.y.y
y.y.y.y
So, any ideas how to solve that?
Sample running code: (just change proxy infos in test function. You may try any working proxies from here: http://free-proxy.cz/en/).
""" NODOC """
__author__ = 'ozgur'
__creation_date__ = '7.10.2020 14:04'
import sys
import time
from PyQt5 import QtNetwork
from PyQt5.QtCore import QMetaObject, QTimer, QUrl
from PyQt5.QtTest import QTest
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout
class RosaRafined(QMainWindow):
# noinspection PyUnusedLocal
def __init__(self, qapp: QApplication):
super(self.__class__, self).__init__()
self.qapp = qapp
self._setup_ui()
self.counter = 0
def _setup_ui(self):
self.setObjectName("Form")
self.resize(1024, 768)
self.verticalLayout = QVBoxLayout(self)
self.verticalLayout.setObjectName("verticalLayout")
self.webengine = QWebEngineView(self)
self.webengine.setObjectName("webengine")
self.webengine.resize(1024, 768)
# self.webengine.page().profile().setPersistentStoragePath(self.temppath)
# self.webengine.page().profile().setCachePath(self.temppath)
self.verticalLayout.addWidget(self.webengine)
QMetaObject.connectSlotsByName(self)
QTimer().singleShot(1000, self.test)
#staticmethod
def _change_proxy(ip, port, user, passwd):
proxy = QtNetwork.QNetworkProxy()
proxy.setType(QtNetwork.QNetworkProxy.HttpProxy)
proxy.setHostName(ip)
proxy.setPort(port)
proxy.setUser(user)
proxy.setPassword(passwd)
QtNetwork.QNetworkProxy.setApplicationProxy(proxy)
def test(self):
testurl = QUrl("https://whatismyipaddress.com")
if self.counter == 0:
print("Will show your real ip, visiting url")
self.webengine.setUrl(testurl)
# your ip x.x.x.x shown, normally
elif self.counter == 1:
self._change_proxy("y.y.y.y", 80, "user", "pass")
QTest.qWait(2)
print("Will show your proxy1 ip")
self.webengine.setUrl(testurl)
# your ip y.y.y.y shown, normally
elif self.counter == 2:
self._change_proxy("x.x.x.x", 80, "user", "pass")
QTest.qWait(2)
print("Will show your proxy2 ip, unfortunately not...")
self.webengine.setUrl(testurl)
# still seeing y.y.y.y , it is not normal
else:
self.qapp.quit()
self.counter += 1
QTimer().singleShot(5000, self.test)
if __name__ == '__main__':
app = QApplication(sys.argv)
form = RosaRafined(app)
form.show()
app.exec_()
sys.exit()
Related
I want to stream mp3 file from web using python PyQt5.I have researched a lot and only found code for streaming wav file.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtMultimedia import *
import urllib.request
import threading
import time
class Streamer:
def __init__(self,url):
self.url = url
self.fancy = urllib.request.URLopener()
self.web = self.fancy.open(self.url)
self.content_len = int(self.web.headers["Content-Length"])
self.data = self.web.read(1024*1024)
self.buffer = QBuffer()
self.buffer.writeData(self.data[250:])
self.buffer.open(QBuffer.ReadWrite)
threading.Thread(target=self.stream).start()
self.format = QAudioFormat()
self.format.setSampleRate(48000)
self.format.setChannelCount(2)
self.format.setSampleSize(16)
self.format.setCodec("audio/pcm")
self.format.setByteOrder(QAudioFormat.LittleEndian)
self.format.setSampleType(QAudioFormat.SignedInt)
self.audio = QAudioOutput(self.format)
self.audio.start(self.buffer)
def stream(self):
# while True:
# self.sound_data = self.web.read(1024*1024)
# if not self.sound_data:
# break
# self.buffer.buffer().append(self.sound_data)
# time.sleep(2)
while len(self.data) < self.content_len:
self.sound_data = self.web.read(1024*1024)
self.buffer.buffer().append(self.sound_data)
self.data+=self.sound_data
time.sleep(2)
self.buffer.buffer().clear()
del self.data
if __name__ == "__main__":
app = QApplication([])
streamer = Streamer("https://raw.githubusercontent.com/PremKumarMishra/Stream-Songs/main/Audio.wav")
app.exec_()
I checked but cant add MPEG-3(mp3 codec) codec in QAudioFormat.So this current code does not work for mp3.
The basic behavior of QMediaPlayer should be enough to manage buffering of simple audio streams, as the backend will consider the 100% of buffer size as enough to guarantee playing.
In case you want more control over the buffer state, you need to implement a custom QIODevice subclass to act as a middle layer between QMediaPlayer and the download process.
In the following example I'm using QNetworkAccessManager to download the stream, the readyRead signal of the QNetworkReply is then connected to a function that reads the raw bytes, and emits a buffer status considering the current available read data and the minimum size set for the buffer.
The first time the received data has reached the minimum size, it begins to emit the readyRead signal, and if the player has not been started yet (no media set), it sets the media using the Buffer instance, and is then ready to play.
from PyQt5 import QtCore, QtWidgets, QtNetwork, QtMultimedia
url = 'https://url.to/stream'
Errors = {}
for k, v in QtMultimedia.QMediaPlayer.__dict__.items():
if isinstance(v, QtMultimedia.QMediaPlayer.Error):
Errors[v] = k
class Buffer(QtCore.QIODevice):
buffering = QtCore.pyqtSignal(object, object)
fullBufferEmitted = False
def __init__(self, reply, minBufferSize=250000):
super().__init__()
self.minBufferSize = max(200000, minBufferSize)
self.reply = reply
self.data = bytes()
# the network reply is on another thread, use a mutex to ensure that
# no simultaneous access is done in the meantime
self.mutex = QtCore.QMutex()
# this is important!
self.setOpenMode(self.ReadOnly|self.Unbuffered)
self.reply.readyRead.connect(self.dataReceived)
def dataReceived(self):
self.mutex.lock()
self.data += self.reply.readAll().data()
dataLen = len(self.data)
self.mutex.unlock()
self.buffering.emit(dataLen, self.minBufferSize)
if not self.fullBufferEmitted:
if dataLen < self.minBufferSize:
return
self.fullBufferEmitted = True
self.readyRead.emit()
def isSequential(self):
return True
def readData(self, size):
self.mutex.lock()
data = self.data[:size]
self.data = self.data[size:]
self.mutex.unlock()
return data
def bytesAvailable(self):
return len(self.data) + super().bytesAvailable()
class Player(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
self.playButton = QtWidgets.QPushButton('Play', enabled=False)
layout.addWidget(self.playButton)
self.volumeSlider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
layout.addWidget(self.volumeSlider)
self.statusLabel = QtWidgets.QLabel('Waiting')
self.statusLabel.setFrameShape(
self.statusLabel.StyledPanel|self.statusLabel.Sunken)
layout.addWidget(self.statusLabel)
self.player = QtMultimedia.QMediaPlayer(volume=16)
self.volumeSlider.setValue(self.player.volume())
self.networkManager = QtNetwork.QNetworkAccessManager()
self.url = QtCore.QUrl(url)
self.media = QtMultimedia.QMediaContent(self.url)
reply = self.networkManager.get(QtNetwork.QNetworkRequest(self.url))
self.buffer = Buffer(reply)
self.playButton.clicked.connect(self.play)
self.volumeSlider.valueChanged.connect(self.player.setVolume)
self.player.error.connect(self.error)
self.buffer.buffering.connect(self.buffering)
def error(self, error):
errorStr = 'Error: {} ({})'.format(
Errors.get(error, 'Unknown error'), int(error))
self.statusLabel.setText(errorStr)
print(errorStr)
def buffering(self, loaded, minBufferSize):
self.statusLabel.setText('Buffer: {}%'.format(int(loaded / minBufferSize * 100)))
if self.player.media().isNull() and loaded >= minBufferSize:
self.player.setMedia(self.media, self.buffer)
self.playButton.setEnabled(True)
self.playButton.setFocus()
self.statusLabel.setText('Ready to play')
def play(self):
if self.player.state() == self.player.PlayingState:
self.player.pause()
self.playButton.setText('Play')
else:
self.player.play()
self.playButton.setText('Pause')
app = QtWidgets.QApplication([])
w = Player()
w.show()
app.exec_()
Note that:
as soon as QMediaPlayer begins to read the stream, the buffer length will obviously become smaller, as there's no way to know or control how the backend access the stream: when the player is reading (which doesn't mean it's playing), it will read the stream anyway;
due to the reason above, the shown buffer size is only "guessed" as soon as the media is set, based on the data read and the data received from the network reply;
you might want to control the media player status in case the buffer goes too low (but you must consider what explained above), and eventually pause it;
I am converting my simple Udp Client application which is working on the Qt C++ but when I try to achieve the same behaviour on the Pyside6 readyRead is not triggered even though the socket seems to be in the ConnectedState.
I am using Windows machine.
Pyside6 Code - Not triggering readyRead
import sys
from PySide6.QtCore import QDataStream, QTimer, Qt, QObject, Signal
from PySide6.QtGui import QIntValidator
from PySide6.QtNetwork import QAbstractSocket, QUdpSocket, QHostAddress
from PySide6.QtWidgets import (QApplication, QDialog, QDialogButtonBox, QGridLayout,
QLabel, QLineEdit, QMessageBox, QPushButton,
QVBoxLayout, QWidget)
# from UdpRx import UdpConectionManager
class UdpClient(QDialog):
def __init__(self, parent= None):
super().__init__(parent)
self._block_size = 0
self._current_fortune = ''
host_label = QLabel("&Server name:")
port_label = QLabel("S&erver port:")
self._host_line_edit = QLineEdit('Localhost')
self._port_line_edit = QLineEdit()
self._port_line_edit.setValidator(QIntValidator(1, 65535, self))
host_label.setBuddy(self._host_line_edit)
port_label.setBuddy(self._port_line_edit)
self._status_label = QLabel("Data: ")
self.hostname = "127.0.0.1"
self.portnum = 12000
self._udp_socket_manager = UdpConectionManager(self.hostname, self.portnum)
self._udp_socket_manager.ProcessTheDataGram.connect(self.process_Data)
main_layout = QGridLayout(self)
main_layout.addWidget(self._status_label, 2, 0, 1, 2)
self.setWindowTitle("Fortune Client")
self._port_line_edit.setFocus()
def process_Data(self, data):
self._status_label.setText(data)
class UdpConectionManager(QObject):
ProcessTheDataGram = Signal(str)
def __init__(self, hostname, portnum):
super().__init__()
self.udp_socket_ptr = QUdpSocket(self)
print("Making socket connection to : ", portnum)
self.udp_socket_ptr.bind(QHostAddress(hostname), int(portnum))
self.udp_socket_ptr.readyRead.connect(self.readPendingDataGrams)
def readPendingDataGrams(self):
while self.udp_socket_ptr.hasPendingDatagrams():
datagram = self.udp_socket_ptr.receiveDatagram()
self.ProcessTheDataGram.emit(str(datagram.data()))
if __name__ == '__main__':
app = QApplication(sys.argv)
client = UdpClient()
client.show()
sys.exit(client.exec())
I get the results when I execute this routine as Qt C++ Application
#include <QtNetwork/QUdpSocket>
#include <QtNetwork/QNetworkDatagram>
#include <QObject>
#include <qdebug.h>
class UdpConnectionManager : public QObject
{
Q_OBJECT
public:
UdpConnectionManager(QString host_name, quint16 port_number) : udp_sockter_ptr(new QUdpSocket(this))
{
udp_sockter_ptr->bind(QHostAddress(host_name), port_number);
connect(udp_sockter_ptr,&QUdpSocket::readyRead,this, &UdpConnectionManager::ReadPendingDataGrams);
}
void ReadPendingDataGrams()
{
while (udp_sockter_ptr->hasPendingDatagrams()) {
QNetworkDatagram datagram = udp_sockter_ptr->receiveDatagram();
QString data_gram = datagram.data();
emit ProcessTheDataGram(data_gram);
}
}
Q_SIGNALS:
void ProcessTheDataGram(QString& data);
private:
QString host_name;
quint16 port_number;
QUdpSocket* udp_sockter_ptr;
};
I was also surprised not to find example codes for the QUdpSocket in the Pyside6.
Thanks in advance.
Is it possible to perform in asynchrone(like with asyncio) web requests under Pyqt4 (QwebPage)?
For example, how can I call multiple urls in parallel with this code:
#!/usr/bin/env python3.4
import sys
import signal
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import QWebPage
class Crawler( QWebPage ):
def __init__(self, url):
QWebPage.__init__( self )
self._url = url
self.content = ''
def crawl( self ):
signal.signal( signal.SIGINT, signal.SIG_DFL )
self.connect( self, SIGNAL( 'loadFinished(bool)' ), self._finished_loading )
self.mainFrame().load( QUrl( self._url ) )
def _finished_loading( self, result ):
self.content = self.mainFrame().toHtml()
print(self.content)
sys.exit( 0 )
def main():
app = QApplication( sys.argv )
crawler = Crawler( self._url, self._file )
crawler.crawl()
sys.exit( app.exec_() )
if __name__ == '__main__':
crawl = Crawler( 'http://www.example.com')
crawl.main()
Thanks
You cannot make self.mainFrame().load(QUrl(self._url)) working through asyncio, sorry -- the method implemented in Qt itself.
But you can install quamash event loop and asynchronously call aiohttp.request coroutine to get web pages.
The way doesn't work with QWebPage though.
Requests are already done asynchronously, so you all you need to do is create multiple instances of QWebPage.
Here's a simple demo based on your example script:
import sys, signal
from PyQt4 import QtCore, QtGui, QtWebKit
urls = [
'http://qt-project.org/doc/qt-4.8/qwebelement.html',
'http://qt-project.org/doc/qt-4.8/qwebframe.html',
'http://qt-project.org/doc/qt-4.8/qwebinspector.html',
'http://qt-project.org/doc/qt-4.8/qwebpage.html',
'http://qt-project.org/doc/qt-4.8/qwebsettings.html',
'http://qt-project.org/doc/qt-4.8/qwebview.html',
]
class Crawler(QtWebKit.QWebPage):
def __init__(self, url, identifier):
super(Crawler, self).__init__()
self.loadFinished.connect(self._finished_loading)
self._id = identifier
self._url = url
self.content = ''
def crawl(self):
self.mainFrame().load(QtCore.QUrl(self._url))
def _finished_loading(self, result):
self.content = self.mainFrame().toHtml()
print('[%d] %s' % (self._id, self._url))
print(self.content[:250].rstrip(), '...')
print()
self.deleteLater()
if __name__ == '__main__':
app = QtGui.QApplication( sys.argv )
signal.signal( signal.SIGINT, signal.SIG_DFL)
crawlers = []
for index, url in enumerate(urls):
crawlers.append(Crawler(url, index))
crawlers[-1].crawl()
sys.exit( app.exec_() )
I am looking to create a QTcpServer using PyQt that can simultaneously return data to 2 or more clients. I assume that this will require threading.
Using the threadedfortuneserver.py example as a test case (included with PyQt4, on my system it is found in /usr/share/doc/python-qt4-doc/examples/network), I want to connect multiple clients and each time one of the clients asks for a fortune, the other clients also get updated with a message like "Client X just received the fortune 'blah blah blah'".
I understand how the fortuneserver/client program works, but it seems that the client connections are immediately terminated after the fortune is sent back to the client. My specific questions are:
Is it possible to keep all of the connections open so that every
time one of the clients requests a fortune, the other clients can be
updated?
If so, what is the best way to keep track of and loop over the connected clients?
This is a serious stumbling block for me because I want to develop an app where several clients can interact, and each client can be updated about the actions of the other clients.
Thanks in advance for your help, let me know if there is any other information I can provide.
I found this thread but there wasn't enough specific information to make use of. Other discussions have been for the Python socket package, but it is my understanding that when using PyQt, the server should be a QTcpServer so everything plays nice.
*** EDIT ***
Here are the beginning stages of my solution. I have created a basic server and client. The server just sends back what the client entered into a Line Edit box.
I am basing this on the "buildingservices" example from Chapter 18 of Rapid GUI Programming with Python and Qt.
The major change I made is that now the threads keep running indefinitely and their sockets stay open, listening for data being sent by the client.
It handles multiple clients fine. It is certainly ugly, but I think it is a good starting point.
What I would like is to be able to notify each client whenever one client enters text (like a typical chat program, say).
Also, to give you an idea of who you are dealing with, I am NOT a professional programmer. I am a physicist with many years of undisciplined scripting and fiddling around under my belt. But I would like to try to develop basic server/client programs that can pass data around.
Thanks for any help or suggestions!
SERVER:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtNetwork import *
PORT = 9999
SIZEOF_UINT16 = 2
class Thread(QThread):
#lock = QReadWriteLock()
def __init__(self, socketId, parent):
super(Thread, self).__init__(parent)
self.socketId = socketId
def run(self):
self.socket = QTcpSocket()
if not self.socket.setSocketDescriptor(self.socketId):
self.emit(SIGNAL("error(int)"), socket.error())
return
while self.socket.state() == QAbstractSocket.ConnectedState:
nextBlockSize = 0
stream = QDataStream(self.socket)
stream.setVersion(QDataStream.Qt_4_2)
if (self.socket.waitForReadyRead(-1) and
self.socket.bytesAvailable() >= SIZEOF_UINT16):
nextBlockSize = stream.readUInt16()
else:
self.sendError("Cannot read client request")
return
if self.socket.bytesAvailable() < nextBlockSize:
if (not self.socket.waitForReadyRead(-1) or
self.socket.bytesAvailable() < nextBlockSize):
self.sendError("Cannot read client data")
return
textFromClient = stream.readQString()
textToClient = "You wrote: \"{}\"".format(textFromClient)
self.sendReply(textToClient)
def sendError(self, msg):
reply = QByteArray()
stream = QDataStream(reply, QIODevice.WriteOnly)
stream.setVersion(QDataStream.Qt_4_2)
stream.writeUInt16(0)
stream.writeQString("ERROR")
stream.writeQString(msg)
stream.device().seek(0)
stream.writeUInt16(reply.size() - SIZEOF_UINT16)
self.socket.write(reply)
def sendReply(self, text):
reply = QByteArray()
stream = QDataStream(reply, QIODevice.WriteOnly)
stream.setVersion(QDataStream.Qt_4_2)
stream.writeUInt16(0)
stream.writeQString(text)
stream.device().seek(0)
stream.writeUInt16(reply.size() - SIZEOF_UINT16)
self.socket.write(reply)
class TcpServer(QTcpServer):
def __init__(self, parent=None):
super(TcpServer, self).__init__(parent)
def incomingConnection(self, socketId):
self.thread = Thread(socketId, self)
self.thread.start()
class ServerDlg(QPushButton):
def __init__(self, parent=None):
super(ServerDlg, self).__init__(
"&Close Server", parent)
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.tcpServer = TcpServer(self)
if not self.tcpServer.listen(QHostAddress("0.0.0.0"), PORT):
QMessageBox.critical(self, "Threaded Server",
"Failed to start server: {}".format(
self.tcpServer.errorString()))
self.close()
return
self.connect(self, SIGNAL("clicked()"), self.close)
font = self.font()
font.setPointSize(24)
self.setFont(font)
self.setWindowTitle("Threaded Server")
app = QApplication(sys.argv)
form = ServerDlg()
form.show()
form.move(0, 0)
app.exec_()
CLIENT:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtNetwork import *
PORT = 9999
SIZEOF_UINT16 = 2
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Ititialize socket
self.socket = QTcpSocket()
# Initialize data IO variables
self.nextBlockSize = 0
self.request = None
# Create widgets/layout
self.browser = QTextBrowser()
self.lineedit = QLineEdit("Texty bits")
self.lineedit.selectAll()
self.connectButton = QPushButton("Connect")
self.connectButton.setDefault(False)
self.connectButton.setEnabled(True)
layout = QVBoxLayout()
layout.addWidget(self.browser)
layout.addWidget(self.lineedit)
layout.addWidget(self.connectButton)
self.setLayout(layout)
self.lineedit.setFocus()
# Signals and slots for line edit and connect button
self.lineedit.returnPressed.connect(self.sendToServer)
self.connectButton.released.connect(self.connectToServer)
self.setWindowTitle("Client")
# Signals and slots for networking
self.socket.readyRead.connect(self.readFromServer)
self.socket.disconnected.connect(self.serverHasStopped)
self.connect(self.socket,
SIGNAL("error(QAbstractSocket::SocketError)"),
self.serverHasError)
# Update GUI
def updateUi(self, text):
self.browser.append(text)
# Create connection to server
def connectToServer(self):
self.connectButton.setEnabled(False)
print("Connecting to server")
self.socket.connectToHost("localhost", PORT)
# Send data to server
def sendToServer(self):
self.request = QByteArray()
stream = QDataStream(self.request, QIODevice.WriteOnly)
stream.setVersion(QDataStream.Qt_4_2)
stream.writeUInt16(0)
stream.writeQString(self.lineedit.text())
stream.device().seek(0)
stream.writeUInt16(self.request.size() - SIZEOF_UINT16)
self.socket.write(self.request)
self.nextBlockSize = 0
self.request = None
self.lineedit.setText("")
# Read data from server and update Text Browser
def readFromServer(self):
stream = QDataStream(self.socket)
stream.setVersion(QDataStream.Qt_4_2)
while True:
if self.nextBlockSize == 0:
if self.socket.bytesAvailable() < SIZEOF_UINT16:
break
self.nextBlockSize = stream.readUInt16()
if self.socket.bytesAvailable() < self.nextBlockSize:
break
textFromServer = stream.readQString()
self.updateUi(textFromServer)
self.nextBlockSize = 0
def serverHasStopped(self):
self.socket.close()
def serverHasError(self):
self.updateUi("Error: {}".format(
self.socket.errorString()))
self.socket.close()
app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
As was probably exasperatingly obvious to most of you, I didn't fully understand how to deal with threads! Not to worry, I have discovered a way to design a server that can send data to multiple clients with nary a secondary thread to be found.
Quite simple, really, but I'm not the quickest of cats at the best of times.
SERVER:
#!/usr/bin/env python3
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtNetwork import *
PORT = 9999
SIZEOF_UINT32 = 4
class ServerDlg(QPushButton):
def __init__(self, parent=None):
super(ServerDlg, self).__init__(
"&Close Server", parent)
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.tcpServer = QTcpServer(self)
self.tcpServer.listen(QHostAddress("0.0.0.0"), PORT)
self.connect(self.tcpServer, SIGNAL("newConnection()"),
self.addConnection)
self.connections = []
self.connect(self, SIGNAL("clicked()"), self.close)
font = self.font()
font.setPointSize(24)
self.setFont(font)
self.setWindowTitle("Server")
def addConnection(self):
clientConnection = self.tcpServer.nextPendingConnection()
clientConnection.nextBlockSize = 0
self.connections.append(clientConnection)
self.connect(clientConnection, SIGNAL("readyRead()"),
self.receiveMessage)
self.connect(clientConnection, SIGNAL("disconnected()"),
self.removeConnection)
self.connect(clientConnection, SIGNAL("error()"),
self.socketError)
def receiveMessage(self):
for s in self.connections:
if s.bytesAvailable() > 0:
stream = QDataStream(s)
stream.setVersion(QDataStream.Qt_4_2)
if s.nextBlockSize == 0:
if s.bytesAvailable() < SIZEOF_UINT32:
return
s.nextBlockSize = stream.readUInt32()
if s.bytesAvailable() < s.nextBlockSize:
return
textFromClient = stream.readQString()
s.nextBlockSize = 0
self.sendMessage(textFromClient,
s.socketDescriptor())
s.nextBlockSize = 0
def sendMessage(self, text, socketId):
for s in self.connections:
if s.socketDescriptor() == socketId:
message = "You> {}".format(text)
else:
message = "{}> {}".format(socketId, text)
reply = QByteArray()
stream = QDataStream(reply, QIODevice.WriteOnly)
stream.setVersion(QDataStream.Qt_4_2)
stream.writeUInt32(0)
stream.writeQString(message)
stream.device().seek(0)
stream.writeUInt32(reply.size() - SIZEOF_UINT32)
s.write(reply)
def removeConnection(self):
pass
def socketError(self):
pass
app = QApplication(sys.argv)
form = ServerDlg()
form.show()
form.move(0, 0)
app.exec_()
CLIENT
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtNetwork import *
PORTS = (9998, 9999)
PORT = 9999
SIZEOF_UINT32 = 4
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Ititialize socket
self.socket = QTcpSocket()
# Initialize data IO variables
self.nextBlockSize = 0
self.request = None
# Create widgets/layout
self.browser = QTextBrowser()
self.lineedit = QLineEdit("Enter text here, dummy")
self.lineedit.selectAll()
self.connectButton = QPushButton("Connect")
self.connectButton.setEnabled(True)
layout = QVBoxLayout()
layout.addWidget(self.browser)
layout.addWidget(self.lineedit)
layout.addWidget(self.connectButton)
self.setLayout(layout)
self.lineedit.setFocus()
# Signals and slots for line edit and connect button
self.lineedit.returnPressed.connect(self.issueRequest)
self.connectButton.clicked.connect(self.connectToServer)
self.setWindowTitle("Client")
# Signals and slots for networking
self.socket.readyRead.connect(self.readFromServer)
self.socket.disconnected.connect(self.serverHasStopped)
self.connect(self.socket,
SIGNAL("error(QAbstractSocket::SocketError)"),
self.serverHasError)
# Update GUI
def updateUi(self, text):
self.browser.append(text)
# Create connection to server
def connectToServer(self):
self.connectButton.setEnabled(False)
self.socket.connectToHost("localhost", PORT)
def issueRequest(self):
self.request = QByteArray()
stream = QDataStream(self.request, QIODevice.WriteOnly)
stream.setVersion(QDataStream.Qt_4_2)
stream.writeUInt32(0)
stream.writeQString(self.lineedit.text())
stream.device().seek(0)
stream.writeUInt32(self.request.size() - SIZEOF_UINT32)
self.socket.write(self.request)
self.nextBlockSize = 0
self.request = None
self.lineedit.setText("")
def readFromServer(self):
stream = QDataStream(self.socket)
stream.setVersion(QDataStream.Qt_4_2)
while True:
if self.nextBlockSize == 0:
if self.socket.bytesAvailable() < SIZEOF_UINT32:
break
self.nextBlockSize = stream.readUInt32()
if self.socket.bytesAvailable() < self.nextBlockSize:
break
textFromServer = stream.readQString()
self.updateUi(textFromServer)
self.nextBlockSize = 0
def serverHasStopped(self):
self.socket.close()
self.connectButton.setEnabled(True)
def serverHasError(self):
self.updateUi("Error: {}".format(
self.socket.errorString()))
self.socket.close()
self.connectButton.setEnabled(True)
app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
To summarize, each client connection opens a socket, and the socket is appended to a list of all client sockets. Then, when one of the clients sends text, the server loops over the client sockets, finds the one that has bytesAvailable, reads it in, and then sends the message to the other clients.
I would be curious to hear what other people may think of this approach. Pitfalls, issues, etc.
Thanks!
Here is the shareable code for PyQt5!
QTcpServer ==================
import sys
from PyQt5.QtCore import Qt, QDataStream, QByteArray, QIODevice, pyqtSignal
from PyQt5.QtWidgets import QApplication, QPushButton
from PyQt5.QtNetwork import QTcpServer, QHostAddress
PORT = 9999
SIZEOF_UINT32 = 4
class ServerDlg(QPushButton):
def __init__(self, parent=None):
super(ServerDlg, self).__init__(
"&Close Server", parent)
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.tcpServer = QTcpServer(self)
self.tcpServer.listen(QHostAddress("127.0.0.1"), PORT)
self.tcpServer.newConnection.connect(self.addConnection)
self.connections = []
self.clicked.connect(self.close)
font = self.font()
font.setPointSize(24)
self.setFont(font)
self.setWindowTitle("Server")
def addConnection(self):
clientConnection = self.tcpServer.nextPendingConnection()
clientConnection.nextBlockSize = 0
self.connections.append(clientConnection)
clientConnection.readyRead.connect(self.receiveMessage)
clientConnection.disconnected.connect(self.removeConnection)
clientConnection.errorOccurred.connect(self.socketError)
def receiveMessage(self):
for s in self.connections:
if s.bytesAvailable() > 0:
stream = QDataStream(s)
stream.setVersion(QDataStream.Qt_4_2)
if s.nextBlockSize == 0:
if s.bytesAvailable() < SIZEOF_UINT32:
return
s.nextBlockSize = stream.readUInt32()
if s.bytesAvailable() < s.nextBlockSize:
return
textFromClient = stream.readQString()
s.nextBlockSize = 0
self.sendMessage(textFromClient,
s.socketDescriptor())
s.nextBlockSize = 0
print('Connections ', self.connections)
def sendMessage(self, text, socketId):
print('Text ', text)
for s in self.connections:
if s.socketDescriptor() == socketId:
message = "You> {}".format(text)
else:
message = "{}> {}".format(socketId, text)
reply = QByteArray()
stream = QDataStream(reply, QIODevice.WriteOnly)
stream.setVersion(QDataStream.Qt_4_2)
stream.writeUInt32(0)
stream.writeQString(message)
stream.device().seek(0)
stream.writeUInt32(reply.size() - SIZEOF_UINT32)
s.write(reply)
def removeConnection(self):
pass
def socketError(self):
pass
app = QApplication(sys.argv)
form = ServerDlg()
form.show()
form.move(0, 0)
app.exec_()
I've got a subclass of QTcpSocket. And problem is : when i firt time connect to server - everything ok, but after socket connected i restart server (python socketServer,just close and start script again) socket disconnecting and tryin to reconnect while server is down, but when i start server again - nothing happened, socket.state() always in ConnectingState.. what is wrong ?
Here example code:
# -*- coding: utf-8 -*-
from PyQt4.QtCore import QVariant, QTimer, pyqtSignal, QCoreApplication
import sys
from PyQt4.QtNetwork import QTcpSocket
from re import match
import json
MAX_WAIT_LEN = 8
class UpQSocket(QTcpSocket):
data_ready = pyqtSignal(unicode)
def __init__(self):
QTcpSocket.__init__(self)
self.wait_len = ''
self.temp = ''
self.setSocketOption(QTcpSocket.KeepAliveOption, QVariant(1))
self.readyRead.connect(self.on_ready_read)
self.connected.connect(self.on_connected)
self.disconnected.connect(self.on_disconnect)
self.error.connect(self.on_error)
self.data_ready.connect(self.print_command)
def connectToHost(self, host, port):
print 'connectToHost'
self.temp = ''
self.wait_len = ''
QTcpSocket.abort(self)
QTcpSocket.connectToHost(self, host, port)
def close(self):
print 'close!'
self.disconnectFromHost()
def send(self, data):
self.writeData('%s|%s' % (len(data), data))
def on_ready_read(self):
if self.bytesAvailable():
data = str(self.readAll())
while data:
if not self.wait_len and '|' in data:#new data and new message
self.wait_len , data = data.split('|',1)
if match('[0-9]+', self.wait_len) and (len(self.wait_len) <= MAX_WAIT_LEN) and data.startswith('{'):#okay, this is normal length
self.wait_len = int(self.wait_len)
self.temp = data[:self.wait_len]
data = data[self.wait_len:]
else:#oh, it was crap
self.wait_len , self.temp = '',''
return
elif self.wait_len:#okay, not new message, appending
tl= int(self.wait_len)-len(self.temp)
self.temp+=data[:tl]
data=data[tl:]
elif not self.wait_len and not '|' in data:#crap
return
if self.wait_len and self.wait_len == len(self.temp):#okay, full message
self.data_ready.emit(self.temp)
self.wait_len , self.temp = '',''
if not data:
return
def print_command(self,data):
print 'data!'
def get_sstate(self):
print self.state()
def on_error(self):
print 'error', self.errorString()
self.close()
self.connectToHost('dev.ulab.ru', 10000)
def on_disconnect(self):
print 'disconnected!'
def on_connected(self):
print 'connected!'
self.send(json.dumps(
{'command' : "operator_insite",
'password' : "376c43878878ac04e05946ec1dd7a55f",
'login' : "nsandr",
'version':unicode("1.2.9")}))
if __name__ == "__main__":
app = QCoreApplication(sys.argv)
main_socket = UpQSocket()
state_timer = QTimer()
state_timer.setInterval(1000)
state_timer.timeout.connect(main_socket.get_sstate)
state_timer.start()
main_socket.connectToHost('dev.ulab.ru', 10000)
sys.exit(app.exec_())
Here is output:
connectToHost
1
1
connected!
data!
data!
3
3
3
3
3
error The remote host closed the connection
close!
disconnected!
connectToHost
2
2
Workaround:
import functools
def on_error(self):
print 'error', self.errorString()
QTimer.singleShot(2000, functools.partial(self.connectToHost, 'localhost', 9999))
# 2000 - your prefered reconnect timeout in ms
Update
There is more correct solution in comments for Qt bugreport QTBUG-18082. Here is Python implementation:
#QtCore.pyqtSlot()
def do_reconnect(self):
print 'Trying to reconnect'
self.connectToHost('localhost', 9999)
def on_error(self):
print 'error', self.errorString()
QtCore.QMetaObject.invokeMethod(self, 'do_reconnect', QtCore.Qt.QueuedConnection)
or just:
QTimer.singleShot(0, self.do_reconnect) # or any callable, slot is unnecessary
which anyway will call QtCore.QMetaObject.invokeMethod with QueuedConnection conection type (source)