I am new in c++ programming , so i need help with the logic (code would be awesome). I want to make QTextEdit to work as a terminal like widget.
I am trying to achieve that with QProcess like:
void QPConsole::command(QString cmd){
QProcess* process = new QProcess();
connect(process, &QProcess::readyReadStandardOutput, [=](){ print(process->readAllStandardOutput()); });
connect(process, &QProcess::readyReadStandardError, [=](){ print(process->readAllStandardError()); });
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),[=](int exitCode, QProcess::ExitStatus exitStatus){ prepareCommandLine(); return; });
process->setProgram("/bin/bash");
process->start();
process->write(cmd.toStdString().c_str());
// process->write("\n\r");
process->closeWriteChannel();
}
The problem is next:
If i don't close write channel (process->closeWriteChannel) than i am stuked in infinite loop. And if i do close it, than i cannot remember state (kinda makes new session) for example if i do "pwd" i get result, but if i do next "cd .." and then "pwd" the result is same as the first output of "pwd"
So, my question is is it possible to achieve some kind of real time output + having previous state remembered (like in real terminal) with QProcess or i have to do in some other way. In python i did it with subprocess calls with pipes, but i don't know what is the equilavent for c++.
An code example would be great. I have QTextEdit part (which is sending QString param to function), i need part with interaction with console.
The idea is simple: Keep an instance of QProcess and write commands to it followed by \n!
Here is a full example, with a QMainWindow and a Bash class, put it in main.cpp:
#include <QtCore>
#include <QtGui>
#include <QtWidgets>
class Bash : public QObject
{
Q_OBJECT
public:
explicit Bash(QObject *parent = nullptr) : QObject(parent)
{
}
~Bash()
{
closePrivate();
}
signals:
void readyRead(QString);
public slots:
bool open(int timeout = -1)
{
if (m_bash_process)
return false;
m_timeout = timeout;
return openPrivate();
}
void close()
{
closePrivate();
}
bool write(const QString &cmd)
{
return writePrivate(cmd);
}
void SIGINT()
{
SIGINTPrivate();
}
private:
//Functions
bool openPrivate()
{
if (m_bash_process)
return false;
m_bash_process = new QProcess();
m_bash_process->setProgram("/bin/bash");
//Merge stdout and stderr in stdout channel
m_bash_process->setProcessChannelMode(QProcess::MergedChannels);
connect(m_bash_process, &QProcess::readyRead, this, &Bash::readyReadPrivate);
connect(m_bash_process, QOverload<int>::of(&QProcess::finished), this, &Bash::closePrivate);
m_bash_process->start();
bool started = m_bash_process->waitForStarted(m_timeout);
if (!started)
m_bash_process->deleteLater();
return started;
}
void closePrivate()
{
if (!m_bash_process)
return;
m_bash_process->closeWriteChannel();
m_bash_process->waitForFinished(m_timeout);
m_bash_process->terminate();
m_bash_process->deleteLater();
}
void readyReadPrivate()
{
if (!m_bash_process)
return;
while (m_bash_process->bytesAvailable() > 0)
{
QString str = QLatin1String(m_bash_process->readAll());
emit readyRead(str);
}
}
bool writePrivate(const QString &cmd)
{
if (!m_bash_process)
return false;
if (runningPrivate())
return false;
m_bash_process->write(cmd.toLatin1());
m_bash_process->write("\n");
return m_bash_process->waitForBytesWritten(m_timeout);
}
void SIGINTPrivate()
{
if (!m_bash_process)
return;
QProcess::startDetached("pkill", QStringList() << "-SIGINT" << "-P" << QString::number(m_bash_process->processId()));
}
bool runningPrivate()
{
if (!m_bash_process)
return false;
QProcess process;
process.setProgram("pgrep");
process.setArguments(QStringList() << "-P" << QString::number(m_bash_process->processId()));
process.setProcessChannelMode(QProcess::MergedChannels);
process.start();
process.waitForFinished(m_timeout);
QString pids = QLatin1String(process.readAll());
QStringList pid_list = pids.split(QRegularExpression("\n"), QString::SkipEmptyParts);
return (pid_list.size() > 0);
}
//Variables
QPointer<QProcess> m_bash_process;
int m_timeout;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr) : QMainWindow(parent)
{
connect(&m_bash, &Bash::readyRead, this, &MainWindow::readyRead);
QWidget *central_widget = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(central_widget);
layout->addWidget(&m_message_board);
layout->addWidget(&m_cmd_edit);
layout->addWidget(&m_sigint_btn);
m_message_board.setReadOnly(true);
m_cmd_edit.setEnabled(false);
m_sigint_btn.setText("Send SIGINT(CTRL+C)");
connect(&m_cmd_edit, &QLineEdit::returnPressed, this, &MainWindow::writeToBash);
connect(&m_sigint_btn, &QPushButton::clicked, this, &MainWindow::SIGINT);
setCentralWidget(central_widget);
resize(640, 480);
QTimer::singleShot(0, this, &MainWindow::startBash);
}
~MainWindow()
{
m_bash.close(); //Not mandatory, called by destructor
}
private slots:
void startBash()
{
//Open and give to each operation a maximum of 1 second to complete, use -1 to unlimited
if (m_bash.open(1000))
{
m_cmd_edit.setEnabled(true);
m_cmd_edit.setFocus();
}
else
{
QMessageBox::critical(this, "Error", "Failed to open bash");
}
}
void writeToBash()
{
QString cmd = m_cmd_edit.text();
m_cmd_edit.clear();
if (!m_bash.write(cmd))
{
QMessageBox::critical(this, "Error", "Failed to write to bash");
}
}
void readyRead(const QString &str)
{
m_message_board.appendPlainText(str);
}
void SIGINT()
{
m_bash.SIGINT();
}
private:
Bash m_bash;
QPlainTextEdit m_message_board;
QLineEdit m_cmd_edit;
QPushButton m_sigint_btn;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
#include "main.moc"
I'm here to seek help on a strange behavior In am having w/ my project. So here goes !
Issue
I have a receiving class called DataReceiver, using a QTcpServer and a QTcpSocket, and I have the DataSender class feeding it via a QTcpSocket. I send the data periodically, and trigger the "send" slot w/ a QTimer.
BUT, after a few iterations, the feed stalls, and is not periodic anymore. I can't really understand what is happening.
I have confirmed this issue w/ terminal printing on the Receiver side.
The code
datasender.cpp
// Con/Destructors
DataSender::DataSender(QObject *parent) :
QObject(parent),
mTcpSocket(new QTcpSocket(this)),
mDestinationAddress("127.0.0.1"),
mDestinationPort(51470),
mTimer(new QTimer(this))
{
connectToHost();
connect(mTimer, SIGNAL(timeout(void)), this, SLOT(sendPeriodicData()));
mTimer->start(1000);
}
DataSender::~DataSender(){
mTcpSocket->disconnectFromHost();
mTcpSocket->waitForDisconnected();
delete mTcpSocket;
}
// Network Management
bool DataSender::connectToHost(void){
connect(mTcpSocket, SIGNAL(connected()), this, SLOT(onConnect()));
connect(mTcpSocket, SIGNAL(disconnected()), this, SLOT(onDisconnect()));
connect(mTcpSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(onBytesWritten(qint64)));
qDebug() << "connecting...";
mTcpSocket->setSocketOption(QAbstractSocket::KeepAliveOption, true);
mTcpSocket->setSocketOption(QAbstractSocket::WriteOnly);
mTcpSocket->connectToHost(getDestinationAddress(), getDestinationPort());
if(!mTcpSocket->waitForConnected(1000))
{
qDebug() << "Error: " << mTcpSocket->errorString();
return false;
}
// Setting meteo data to send
mMeteoData.messageID = METEO_MESSAGE;
mMeteoData.temperature = 5.5;
mMeteoData.pressure = 10.2;
mMeteoData.humidity = 45.5;
// Setting positiondata to send
mPositionData.messageID = POSITION_MESSAGE;
mPositionData.north = 120.3;
mPositionData.pitch = 1.5;
mPositionData.roll = 2.5;
mPositionData.yaw = 3.5;
mPositionData.a_x = 30.5;
mPositionData.a_y = 40.5;
mPositionData.a_z = 50.5;
return true;
}
void DataSender::sendData(void) const{
QByteArray lData("Hello, this is DataSender ! Do you copy ? I repeat, do you copy ?");
if(mTcpSocket->state() == QAbstractSocket::ConnectedState)
{
mTcpSocket->write(lData);
mTcpSocket->waitForBytesWritten();
}
}
void DataSender::sendData(const QByteArray &pData) const{
//QByteArray lData("Hello, this is DataSender ! Do you copy ? I repeat, do you copy ?");
if(mTcpSocket->state() == QAbstractSocket::ConnectedState)
{
mTcpSocket->write(pData);
mTcpSocket->waitForBytesWritten();
mTcpSocket->flush();
//usleep(1000);
}
}
// Getters
QString DataSender::getDestinationAddress(void) const{
return mDestinationAddress;
}
unsigned int DataSender::getDestinationPort(void) const{
return mDestinationPort;
}
// Setters
void DataSender::setDestinationAddress(const QString &pDestinationAddress){
mDestinationAddress = pDestinationAddress;
}
void DataSender::setDestinationPort(const unsigned int &pDestinationPort){
mDestinationPort = pDestinationPort;
}
// Public Slots
void DataSender::onConnect(){
qDebug() << "connected...";
}
void DataSender::onDisconnect(){
qDebug() << "disconnected...";
}
void DataSender::onBytesWritten(qint64 bytes){
qDebug() << bytes << " bytes written...";
}
void DataSender::sendPeriodicData(void){
mTcpSocket->
// Changing data for testing
mPositionData.north += 10;
mPositionData.north = std::fmod(mPositionData.north, 360);
mMeteoData.temperature += 10;
mMeteoData.temperature = std::fmod(mMeteoData.temperature, 500);
// Declaring QByteArrays
QByteArray lMeteoByteArray;
QByteArray lPositionByteArray;
// Serializing
lMeteoByteArray = serializeMeteoData(mMeteoData);
lPositionByteArray = serializePositionData(mPositionData);
// Sending
sendData(lMeteoByteArray);
sendData(lPositionByteArray);
}
datareceiver.cpp
// Con/Destructors
DataReceiver::DataReceiver(QObject *parent) :
QObject(parent),
mTcpServer(new QTcpServer(this)),
mSourceAddress("127.0.0.1"),
mSourcePort(51470)
{
initData();
connect(mTcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
if(!mTcpServer->listen(QHostAddress(getSourceAddress()), getSourcePort()))
qDebug() << "<DataReceiver> Server could not start. ";
else
qDebug() << "<DataReceiver> Server started !";
}
DataReceiver::DataReceiver(const QString &pSourceAddress,
const unsigned int &pSourcePort,
QObject *parent) :
QObject(parent),
mTcpServer(new QTcpServer(this)),
mSourceAddress(pSourceAddress),
mSourcePort(pSourcePort)
{
initData();
connect(mTcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
if(!mTcpServer->listen(QHostAddress(getSourceAddress()), getSourcePort()))
qDebug() << "<DataReceiver> Server could not start. ";
else
qDebug() << "<DataReceiver> Server started !";
}
DataReceiver::~DataReceiver(){
if(mTcpSocket != nullptr) delete mTcpSocket;
delete mTcpServer;
if(mTcpSocket != nullptr)
delete mTcpSocket;
}
// Getters
QTcpServer *DataReceiver::getTcpServer(void) const{
return mTcpServer;
}
QString DataReceiver::getSourceAddress(void) const{
return mSourceAddress;
}
unsigned int DataReceiver::getSourcePort(void) const{
return mSourcePort;
}
positionData_t DataReceiver::getPositionData(void) const{
return mPositionData;
}
meteoData_t DataReceiver::getMeteoData(void) const{
return mMeteoData;
}
// Setters
void DataReceiver::setSourceAddress(const QString &pSourceAddress){
mSourceAddress = pSourceAddress;
}
void DataReceiver::setSourcePort(const unsigned int &pSourcePort){
mSourcePort = pSourcePort;
}
void DataReceiver::setPositionData(const positionData_t &pPositionData){
mPositionData = pPositionData;
}
void DataReceiver::setMeteoData(const meteoData_t &pMeteoData){
mMeteoData = pMeteoData;
}
// Data Management
void DataReceiver::initPositionData(void){
mPositionData.messageID = METEO_MESSAGE;
mPositionData.pitch = .0;
mPositionData.roll = .0;
mPositionData.yaw = .0;
mPositionData.a_x = .0;
mPositionData.a_y = .0;
mPositionData.a_z = .0;
}
void DataReceiver::initMeteoData(void){
mMeteoData.messageID = POSITION_MESSAGE;
mMeteoData.temperature = .0;
mMeteoData.humidity = .0;
mMeteoData.pressure = .0;
}
void DataReceiver::initData(void){
initPositionData();
initMeteoData();
}
void DataReceiver::reinitData(void){
initData();
}
// Public Slots
void DataReceiver::onConnect(){
qDebug() << "QTcpSocket connected...";
}
void DataReceiver::onDisconnect(){
qDebug() << "QTcpSocket disconnected...";
disconnect(mTcpSocket, SIGNAL(readyRead()), this, SLOT(onDataReceived()));
disconnect(mTcpSocket, SIGNAL(disconnected()), this, SLOT(onDisconnect()));
}
void DataReceiver::onBytesWritten(qint64 bytes){
qDebug() << bytes << " bytes written to QTcpSocket...";
}
void DataReceiver::onDataReceived(){
// Not yet implemented, code is for testing
qDebug() << "onDataReceived called !";
QByteArray lReceivedData;
while(mTcpSocket->bytesAvailable()){
lReceivedData = mTcpSocket->read(mTcpSocket->bytesAvailable());
decodeData(lReceivedData);
qDebug() << lReceivedData << "\n";
}
}
void DataReceiver::onNewConnection(){
qDebug() << "onNewConnection called !";
mTcpSocket = mTcpServer->nextPendingConnection();
mTcpSocket->setSocketOption(QAbstractSocket::ReadOnly, true);
connect(mTcpSocket, SIGNAL(readyRead()), this, SLOT(onDataReceived()));
connect(mTcpSocket, SIGNAL(disconnected()), this, SLOT(onDisconnect()));
}
void DataReceiver::onDataChanged(void){
qDebug() << "onDataChanged called !";
qDebug() << "\nPrinting mMeteoData : ";
qDebug() << "mMeteoData.messageID" << mMeteoData.messageID;
qDebug() << "mMeteoData.temperature" << mMeteoData.temperature;
qDebug() << "mMeteoData.humidity" << mMeteoData.humidity;
qDebug() << "mMeteoData.pressure" << mMeteoData.pressure;
qDebug() << "\nPrinting mPositionData";
qDebug() << "mPositionData.messageID" << mPositionData.messageID;
qDebug() << "mPositionData.north" << mPositionData.north;
qDebug() << "mPositionData.pitch" << mPositionData.pitch;
qDebug() << "mPositionData.roll" << mPositionData.roll;
qDebug() << "mPositionData.yaw" << mPositionData.yaw;
qDebug() << "mPositionData.a_x" << mPositionData.a_x;
qDebug() << "mPositionData.a_y" << mPositionData.a_y;
qDebug() << "mPositionData.a_z" << mPositionData.a_z;
}
// Private Methods
void DataReceiver::decodeData(const QByteArray &pMessage){
// Not yet implemented
quint8 tempMessageID = fetchMessageID(pMessage);
switch(tempMessageID){
case UNKNOWN_MESSAGE:
break;
case METEO_MESSAGE:
mMeteoData = deserializeMeteoData(pMessage);
emit dataChanged();
break;
case POSITION_MESSAGE:
mPositionData = deserializePositionData(pMessage);
emit dataChanged();
break;
}
return;
}
More information
I send two types of data, both coming from structs. On the receiver side, I decode the data, identify it, and set corresponding attributes.
The receiver class is used in a QT GUI App. It is not particularly threaded. I do not really know if it is necessary, as at the beginning the data is received periodically as intended.
Conclusion
I would really like to find a solution for this... I tried a few things to no avail, like changing how I read the socket or using the QTcpSocket::flush() method (not sure it does what I thought it did)
Thanks a lot,
Clovel
Bonus
I would also like to be able to open de Receiver end and have the sender automatically detect it and start sending the data to the Receiver. I need to dig a little more into this, but if you have a tip, it would be welcome !
While the #Kuba Ober comment seems to solve the lock of your sender, i will also advice about TCP, because one send operation can result in multiples readyRead SIGNALs and also multiples send operations can result in a single readyRead SIGNAL, so my advice is to write a small protocol to avoid problems with corrupted data!
I wrote a protocol here and it was well accepted, now i'm improving it!
The following example is generic, so you have to adapt it to fit your needs, but i think you can do it without much hassle :), it has been tested with Qt 5.5.1 MinGW in Windows 10.
To reach your bonus i used a UDP socket to send a broadcast message and try to find peers in range.
You can see the full project here!
common.h:
#ifndef COMMON_H
#define COMMON_H
#include <QtCore>
#include <QtNetwork>
//Helper macro to set a QObject pointer to nullptr when it's get destroyed
#define SETTONULLPTR(obj) QObject::connect(obj, &QObject::destroyed, [=]{obj = nullptr;})
//Define a max size for each send data operation
#define MAX_NETWORK_CHUNK_SIZE 10*1024*1024
//Create differents types for incomming data, valid for both client and server
namespace Type
{
enum
{
DataType1,
DataType2
};
}
//Convert some data of type T to QByteArray, by default in big endian order
template <typename T>
static inline QByteArray getBytes(T input)
{
QByteArray tmp;
QDataStream data(&tmp, QIODevice::WriteOnly);
data << input;
return tmp;
}
//Convert some QByteArray to data of type T
template <typename T>
static inline T getValue(QByteArray bytes)
{
T tmp;
QDataStream data(&bytes, QIODevice::ReadOnly);
data >> tmp;
return tmp;
}
//Struct that holds data and information about the peer
typedef struct PeerData {
QByteArray data;
QHostAddress host;
qintptr descriptor;
} PeerData;
#endif // COMMON_H
client.h:
#ifndef CLIENT_H
#define CLIENT_H
#include <QtCore>
#include <QtNetwork>
#include "common.h"
class Client : public QObject
{
Q_OBJECT
public:
explicit Client(QObject *parent = nullptr);
~Client();
signals:
void peerFound(QHostAddress);
void searchPeersFinished();
void connected(PeerData);
void disconnected(PeerData);
void readyRead(PeerData);
void error(QString);
public slots:
void abort();
void connectToHost(const QString &host, quint16 port);
void searchPeers(quint16 port);
void stop();
int write(const QByteArray &data);
private slots:
void UDPReadyRead();
void UDPWrite();
void searchPeersEnd();
void timeout();
void connectedPrivate();
void disconnectedPrivate();
void errorPrivate(QAbstractSocket::SocketError e);
void readyReadPrivate();
private:
QTcpSocket *m_socket;
QUdpSocket *m_udp_socket;
quint16 m_udp_port;
QByteArray m_buffer;
qint32 m_size;
QTimer *m_timer;
};
#endif // CLIENT_H
client.cpp:
#include "client.h"
Client::Client(QObject *parent) : QObject(parent)
{
qRegisterMetaType<PeerData>("PeerData");
qRegisterMetaType<QHostAddress>("QHostAddress");
m_socket = nullptr;
m_size = 0;
m_udp_socket = nullptr;
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &Client::timeout);
m_timer->setSingleShot(true);
}
Client::~Client()
{
disconnectedPrivate();
}
//Disconnects the socket
void Client::abort()
{
if (m_socket)
m_socket->abort();
}
//Start connection to server
void Client::connectToHost(const QString &host, quint16 port)
{
if (m_socket)
return;
m_socket = new QTcpSocket(this);
SETTONULLPTR(m_socket);
connect(m_socket, &QTcpSocket::readyRead, this, &Client::readyReadPrivate);
connect(m_socket, &QTcpSocket::connected, this, &Client::connectedPrivate);
connect(m_socket, &QTcpSocket::disconnected, this, &Client::disconnectedPrivate);
connect(m_socket, static_cast<void(QTcpSocket::*)(QTcpSocket::SocketError)>(&QTcpSocket::error), this, &Client::errorPrivate);
m_timer->start(10 * 1000);
m_socket->connectToHost(host, port);
}
//Perform udp broadcast to search peers
void Client::searchPeers(quint16 port)
{
if (m_udp_socket)
return;
m_udp_socket = new QUdpSocket(this);
SETTONULLPTR(m_udp_socket);
connect(m_udp_socket, &QUdpSocket::readyRead, this, &Client::UDPReadyRead);
m_udp_port = port;
QTimer::singleShot(50, this, &Client::UDPWrite);
}
//Ready read specific for udp socket
void Client::UDPReadyRead()
{
while (m_udp_socket->hasPendingDatagrams())
{
QByteArray data;
data.resize(m_udp_socket->pendingDatagramSize());
QHostAddress peer_address;
quint16 peer_port;
m_udp_socket->readDatagram(data.data(), data.size(), &peer_address, &peer_port);
//Test the header used in this udp broadcast, you can freely change this value,
//but do for both client and server
if (QLatin1String(data) == QLatin1Literal("TEST"))
emit peerFound(peer_address);
}
}
//Send the udp broadcast message to all the network interfaces
void Client::UDPWrite()
{
QList<QHostAddress> broadcast;
foreach (QHostAddress address, QNetworkInterface::allAddresses())
{
if (address.protocol() == QAbstractSocket::IPv4Protocol)
{
address.setAddress(address.toIPv4Address());
QStringList list = address.toString().split(".");
list.replace(3, "255");
QString currentbroadcast = list.join(".");
QHostAddress address = QHostAddress(QHostAddress(currentbroadcast).toIPv4Address());
broadcast.append(address);
}
}
QByteArray datagram = QString("TEST").toLatin1();
foreach (const QHostAddress &address, broadcast)
m_udp_socket->writeDatagram(datagram, address, m_udp_port);
//Wait 0.5 seconds for an answer
QTimer::singleShot(500, this, &Client::searchPeersEnd);
}
//Stop the udp socket
void Client::searchPeersEnd()
{
m_udp_socket->deleteLater();
emit searchPeersFinished();
}
void Client::timeout()
{
emit error("Operation timed out");
stop();
}
//Handle connected state
void Client::connectedPrivate()
{
QHostAddress host = m_socket->peerAddress();
qintptr descriptor = m_socket->socketDescriptor();
PeerData pd;
pd.host = host;
pd.descriptor = descriptor;
emit connected(pd);
m_timer->stop();
}
//Handle disconnected state
void Client::disconnectedPrivate()
{
if (!m_socket)
return;
QHostAddress host = m_socket->peerAddress();
qintptr descriptor = m_socket->socketDescriptor();
stop();
PeerData pd;
pd.host = host;
pd.descriptor = descriptor;
emit disconnected(pd);
}
//Handle error
void Client::errorPrivate(QAbstractSocket::SocketError e)
{
if (e != QAbstractSocket::RemoteHostClosedError)
{
QString err = m_socket->errorString();
emit error(err);
}
stop();
}
//Stop the tcp socket
void Client::stop()
{
m_timer->stop();
m_size = 0;
m_buffer.clear();
if (m_socket)
{
m_socket->abort();
m_socket->deleteLater();
}
}
//Write data to the server
int Client::write(const QByteArray &data)
{
if (!m_socket)
return 0;
m_socket->write(getBytes<qint32>(data.size()));
m_socket->write(data);
return 1;
}
//Receive message from server
void Client::readyReadPrivate()
{
if (!m_socket)
return;
while (m_socket->bytesAvailable() > 0)
{
m_buffer.append(m_socket->readAll());
while ((m_size == 0 && m_buffer.size() >= 4) || (m_size > 0 && m_buffer.size() >= m_size))
{
if (m_size == 0 && m_buffer.size() >= 4)
{
m_size = getValue<qint32>(m_buffer.mid(0, 4));
m_buffer.remove(0, 4);
if (m_size < 0 || m_size > MAX_NETWORK_CHUNK_SIZE)
{
m_socket->abort();
return;
}
}
if (m_size > 0 && m_buffer.size() >= m_size)
{
QByteArray data = m_buffer.mid(0, m_size);
m_buffer.remove(0, m_size);
m_size = 0;
QHostAddress host = m_socket->peerAddress();
qintptr descriptor = m_socket->socketDescriptor();
PeerData pd;
pd.data = data;
pd.host = host;
pd.descriptor = descriptor;
emit readyRead(pd);
}
}
}
}
server.h:
#ifndef SERVER_H
#define SERVER_H
#include <QtCore>
#include <QtNetwork>
#include "common.h"
class Server : public QObject
{
Q_OBJECT
public:
explicit Server(QObject *parent = nullptr);
~Server();
signals:
void connected(PeerData);
void disconnected(PeerData);
void listening(quint16);
void readyRead(PeerData);
void error(QString);
public slots:
void abort(qintptr descriptor);
void listen(quint16 port);
void stop();
void writeToHost(const QByteArray &data, qintptr descriptor);
int writeToAll(const QByteArray &data);
private slots:
void newConnectionPrivate();
void UDPListen(quint16 port);
void UDPReadyRead();
void readyReadPrivate();
void disconnectedPrivate();
void removeSocket(QTcpSocket *socket);
private:
QTcpServer *m_server;
QUdpSocket *m_udp_server;
QList<QTcpSocket*> m_socket_list;
QHash<qintptr, QTcpSocket*> m_socket_hash;
QHash<QTcpSocket*, qintptr> m_descriptor_hash;
QHash<QTcpSocket*, QByteArray> m_buffer_hash;
QHash<QTcpSocket*, qint32> m_size_hash;
};
#endif // SERVER_H
server.cpp:
#include "server.h"
Server::Server(QObject *parent) : QObject(parent)
{
qRegisterMetaType<PeerData>("PeerData");
qRegisterMetaType<QHostAddress>("QHostAddress");
m_server = nullptr;
m_udp_server = nullptr;
}
Server::~Server()
{
stop();
}
//Disconnect the socket specified by descriptor
void Server::abort(qintptr descriptor)
{
QTcpSocket *socket = m_socket_hash.value(descriptor);
socket->abort();
}
//Try to start in listening state
void Server::listen(quint16 port)
{
if (m_server)
return;
m_server = new QTcpServer(this);
SETTONULLPTR(m_server);
connect(m_server, &QTcpServer::newConnection, this, &Server::newConnectionPrivate);
if (port < 1)
{
emit error("Invalid port value");
stop();
return;
}
bool is_listening = m_server->listen(QHostAddress::AnyIPv4, port);
if (!is_listening)
{
emit error(m_server->errorString());
stop();
return;
}
UDPListen(port);
emit listening(m_server->serverPort());
}
//Start the udp server, used to perform netowork search
void Server::UDPListen(quint16 port)
{
if (m_udp_server)
return;
m_udp_server = new QUdpSocket(this);
SETTONULLPTR(m_udp_server);
connect(m_udp_server, &QUdpSocket::readyRead, this, &Server::UDPReadyRead);
m_udp_server->bind(port);
}
//ReadyRead specific for udp
void Server::UDPReadyRead()
{
while (m_udp_server->hasPendingDatagrams())
{
QByteArray data;
QHostAddress address;
quint16 port;
data.resize(m_udp_server->pendingDatagramSize());
m_udp_server->readDatagram(data.data(), data.size(), &address, &port);
//Test the header used in this udp broadcast, you can freely change this value,
//but do for both client and server
if (QLatin1String(data) == QLatin1Literal("TEST"))
{
m_udp_server->writeDatagram(data, address, port);
}
}
}
//Stop both tcp and udp servers and also
//removes peers that may be connected
void Server::stop()
{
while (!m_socket_list.isEmpty())
removeSocket(m_socket_list.first());
if (m_server)
{
m_server->close();
m_server->deleteLater();
}
if (m_udp_server)
{
m_udp_server->deleteLater();
}
}
//Handle new connection
void Server::newConnectionPrivate()
{
while (m_server->hasPendingConnections())
{
QTcpSocket *socket = m_server->nextPendingConnection();
QHostAddress host = socket->peerAddress();
qintptr descriptor = socket->socketDescriptor();
QByteArray m_buffer;
qint32 size = 0;
m_descriptor_hash.insert(socket, descriptor);
m_socket_hash.insert(descriptor, socket);
m_buffer_hash.insert(socket, m_buffer);
m_size_hash.insert(socket, size);
m_socket_list.append(socket);
connect(socket, &QTcpSocket::disconnected, this, &Server::disconnectedPrivate);
connect(socket, &QTcpSocket::readyRead, this, &Server::readyReadPrivate);
PeerData pd;
pd.host = host;
pd.descriptor = descriptor;
emit connected(pd);
}
}
//Write to specific socket if more than one is connected
void Server::writeToHost(const QByteArray &data, qintptr descriptor)
{
if (!m_socket_hash.contains(descriptor))
return;
QTcpSocket *socket = m_socket_hash.value(descriptor);
socket->write(getBytes<qint32>(data.size()));
socket->write(data);
}
//Write to all sockets
int Server::writeToAll(const QByteArray &data)
{
foreach (QTcpSocket *socket, m_socket_list)
{
socket->write(getBytes<qint32>(data.size()));
socket->write(data);
}
return m_socket_list.size();
}
//ReadyRead function shared by all sockets connected
void Server::readyReadPrivate()
{
QTcpSocket *socket = static_cast<QTcpSocket*>(sender());
QByteArray *m_buffer = &m_buffer_hash[socket];
qint32 *size = &m_size_hash[socket];
Q_UNUSED(size)
#define m_size *size
while (socket->bytesAvailable() > 0)
{
m_buffer->append(socket->readAll());
while ((m_size == 0 && m_buffer->size() >= 4) || (m_size > 0 && m_buffer->size() >= m_size))
{
if (m_size == 0 && m_buffer->size() >= 4)
{
m_size = getValue<qint32>(m_buffer->mid(0, 4));
m_buffer->remove(0, 4);
if (m_size < 0 || m_size > MAX_NETWORK_CHUNK_SIZE)
{
socket->abort();
return;
}
}
if (m_size > 0 && m_buffer->size() >= m_size)
{
QByteArray data = m_buffer->mid(0, m_size);
m_buffer->remove(0, m_size);
m_size = 0;
QHostAddress host = socket->peerAddress();
qintptr descriptor = socket->socketDescriptor();
PeerData pd;
pd.data = data;
pd.host = host;
pd.descriptor = descriptor;
emit readyRead(pd);
}
}
}
}
//Handle socket disconnection
void Server::disconnectedPrivate()
{
QTcpSocket *socket = static_cast<QTcpSocket*>(sender());
QHostAddress host = socket->peerAddress();
qintptr descriptor = m_descriptor_hash.value(socket);
removeSocket(socket);
PeerData pd;
pd.host = host;
pd.descriptor = descriptor;
emit disconnected(pd);
}
//Handle socket removal
void Server::removeSocket(QTcpSocket *socket)
{
qintptr descriptor = m_descriptor_hash.value(socket);
m_socket_hash.remove(descriptor);
m_descriptor_hash.remove(socket);
m_buffer_hash.remove(socket);
m_size_hash.remove(socket);
m_socket_list.removeAll(socket);
socket->abort();
socket->deleteLater();
}
Client/worker.h
#ifndef WORKER_H
#define WORKER_H
#include <QtCore>
#include "client.h"
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
public slots:
void start(quint16 port);
private:
Client m_client;
quint16 m_port;
QTimer m_timer;
double m_double_1;
double m_double_2;
QMetaObject::Connection m_connection;
};
#endif // WORKER_H
Client/worker.cpp
#include "worker.h"
Worker::Worker(QObject *parent) : QObject(parent)
{
m_port = 0;
m_double_1 = 0;
m_double_2 = 0;
m_timer.setInterval(1000);
}
void Worker::start(quint16 port)
{
m_port = port;
connect(&m_client, &Client::searchPeersFinished, []{
qDebug() << "Search peers finished!";
});
m_connection = connect(&m_client, &Client::peerFound, [&](const QHostAddress &peer_address){
disconnect(m_connection); //Disconnect signal, only first peer found will be handled!
m_client.connectToHost(QHostAddress(peer_address.toIPv4Address()).toString(), m_port);
});
connect(&m_client, &Client::error, [](const QString &error){
qDebug() << "Error:" << qPrintable(error);
});
connect(&m_client, &Client::connected, [&](const PeerData &pd){
qDebug() << "Connected to:" << qPrintable(pd.host.toString());
m_timer.start();
});
connect(&m_client, &Client::disconnected, [](const PeerData &pd){
qDebug() << "Disconnected from:" << qPrintable(pd.host.toString());
});
connect(&m_client, &Client::readyRead, [](const PeerData &pd){
qDebug() << "Data from" << qPrintable(pd.host.toString())
<< qPrintable(QString::asprintf("%.2f", getValue<double>(pd.data)));
});
connect(&m_timer, &QTimer::timeout, [&]{
m_double_1 += 0.5; //Just an example of data
QByteArray data1;
data1.append(getBytes<quint8>(Type::DataType1)); //Data 1 has Type 1, added as header
data1.append(getBytes<double>(m_double_1)); //The data itself
m_client.write(data1); //Write the data1 to the server
m_double_2 += 1.0; //Just an example of data
QByteArray data2;
data2.append(getBytes<quint8>(Type::DataType2)); //Data 2 has Type 2, added as header
data2.append(getBytes<double>(m_double_2)); //The data itself
m_client.write(data2); //Write the data2 to the server
});
qDebug() << "Searching...";
m_client.searchPeers(m_port); //Search for peers in range, if found, handle the first and connect to it!
}
Client/main.cpp
#include <QtCore>
#include "worker.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Worker worker;
worker.start(1024);
return a.exec();
}
Server/worker.h
#ifndef WORKER_H
#define WORKER_H
#include <QtCore>
#include "server.h"
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
public slots:
void start(quint16 port);
private:
Server m_server;
quint16 m_port;
};
#endif // WORKER_H
Server/worker.cpp
#include "worker.h"
Worker::Worker(QObject *parent) : QObject(parent)
{
m_port = 0;
}
void Worker::start(quint16 port)
{
m_port = port;
connect(&m_server, &Server::error, [](const QString &error){
if (!error.isEmpty())
qDebug() << "Error:" << qPrintable(error);
});
connect(&m_server, &Server::listening, [](quint16 port){
qDebug() << "Listening on port:" << port;
});
connect(&m_server, &Server::connected, [](const PeerData &pd){
qDebug() << "Connected to:" << qPrintable(pd.host.toString());
});
connect(&m_server, &Server::disconnected, [](const PeerData &pd){
qDebug() << "Disconnected from:" << qPrintable(pd.host.toString());
});
connect(&m_server, &Server::readyRead, [&](const PeerData &pd){
QByteArray data = pd.data;
if (data.isEmpty())
return;
quint8 header = getValue<quint8>(data.mid(0, 1)); //Read the 1 byte header
data.remove(0, 1); //Remove the header from data
switch (header)
{
case Type::DataType1:
{
qDebug() << "Data from" << qPrintable(pd.host.toString())
<< "DataType1" << qPrintable(QString::asprintf("%.2f", getValue<double>(data)));
break;
}
case Type::DataType2:
{
qDebug() << "Data from" << qPrintable(pd.host.toString())
<< "DataType2" << qPrintable(QString::asprintf("%.2f", getValue<double>(data)));
break;
}
default:
break;
}
m_server.writeToHost(data, pd.descriptor);
});
m_server.listen(m_port);
}
Server/main.cpp
#include <QtCore>
#include "worker.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Worker worker;
worker.start(1024);
return a.exec();
}
I'm using the Qt settings and it saves the object into a file. it saves to a file called sessionrc.
Now I'm trying to load the object from the settings and save it back.
The problem is I can not identify the object from the settings, so that I can load all the profiles that are saved.
I'm using the following load and save functionality
void ProfileManager::loadFrom(Settings &set, bool ownGroup)
{
qDebug()<<"LOAD";
foreach (const QString &group, set.childGroups()) {
if(group == "Profile")
{
Profile *profile = new Profile();
profile->setObjectName(group);
profile->loadFrom(set);
m_Profiles << profile;
}
}
EraObject::staticLoadFrom(set, this);
}
void ProfileManager::saveTo(Settings &set, bool ownGroup, bool force)
{
EraObject::staticSaveTo(set, this, ownGroup, force);
foreach(Profile * profile, m_Profiles) {
profile->saveTo(set);
}
}
The current setting file is
[www]
Ta=20
Te=48
Texp=38
lim1=0
lim2=0
offset=0
profilename=www
[www] is the profile that is saved. but I have many of it. How would I load it back and save it correctly
// main.cpp
#include <QCoreApplication>
#include <QSettings>
#include <QVector>
#include <QDebug>
#include <QMetaProperty>
class Profile : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName )
Q_PROPERTY(QString title READ title WRITE setTitle )
public:
explicit Profile(QObject *parent = 0) : QObject(parent) {
}
QString name() const {
return name_;
}
void setName(QString name) {
name_ = name;
}
QString title() const {
return title_;
}
void setTitle(QString title) {
title_ = title;
}
void save(QSettings& settings) const {
for(int i=0; i<metaObject()->propertyCount(); ++i) {
const auto& p = metaObject()->property(i);
if(p.isStored(this)) {
settings.setValue(p.name(), property(p.name()));
}
}
}
void load(QSettings& settings) {
for(int i=0; i<metaObject()->propertyCount(); ++i) {
const auto& p = metaObject()->property(i);
if(p.isStored(this)) {
setProperty(p.name(), settings.value(p.name()));
}
}
}
private:
QString name_;
QString title_;
};
#include "main.moc"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QObject garbageCollector;
QVector<Profile*> profiles;
{
Profile* p1 = new Profile(&garbageCollector);
p1->setName("profilename1");
p1->setTitle("Profile 1");
Profile* p2 = new Profile(&garbageCollector);
p2->setName("profilename2");
p2->setTitle("Profile 2");
profiles.append(p1);
profiles.append(p2);
}
QSettings s("profiles.ini", QSettings::IniFormat);
// write profiles
{
s.beginGroup("profiles");
foreach(const Profile*p, profiles) {
s.beginGroup(p->name());
p->save(s);
s.endGroup();
}
s.endGroup();
s.sync(); // force write
}
// read profiles
{
s.beginGroup("profiles");
foreach(const QString& g, s.childGroups()) {
Profile p;
s.beginGroup(g);
p.load(s);
s.endGroup();
qDebug() << p.name();
qDebug() << p.title();
}
s.endGroup();
}
return 0;
}
Given a pointer to a method of a QObject derived class: Is there a way of getting the QMetaMethod of the method the pointer is pointing to? I am basically looking for a function like QMetaMethod::fromSignal, but for slots.
Note:
I tried getting the index through static_metacall with QMetaObject::IndexOfMethod and using that for QMetaObject::method:
void(Class::*method)() = &Class::method;
int methodIndex = -1;
void *metaArgs[] = {&methodIndex, reinterpret_cast<void **>(&method)};
const QMetaObject mo = Class::staticMetaObject;
mo.static_metacall(QMetaObject::IndexOfMethod, 0, metaArgs);
qDebug() << methodIndex;
// QMetaMethod mm = mo.method(methodIndex)
The output is always -1.
At the moment, the only solution is to modify moc. The patch is rather trivial, though:
diff --git a/src/tools/moc/generator.cpp b/src/tools/moc/generator.cpp
index d831edf..7dcefcc 100644
--- a/src/tools/moc/generator.cpp
+++ b/src/tools/moc/generator.cpp
## -1311,15 +1311,12 ## void Generator::generateStaticMetacall()
isUsed_a = true;
}
- }
- if (!cdef->signalList.isEmpty()) {
- Q_ASSERT(needElse); // if there is signal, there was method.
fprintf(out, " else if (_c == QMetaObject::IndexOfMethod) {\n");
fprintf(out, " int *result = reinterpret_cast<int *>(_a[0]);\n");
fprintf(out, " void **func = reinterpret_cast<void **>(_a[1]);\n");
bool anythingUsed = false;
- for (int methodindex = 0; methodindex < cdef->signalList.size(); ++methodindex) {
- const FunctionDef &f = cdef->signalList.at(methodindex);
+ for (int methodindex = 0; methodindex < methodList.size(); ++methodindex) {
+ const FunctionDef &f = methodList.at(methodindex);
if (f.wasCloned || !f.inPrivateClass.isEmpty() || f.isStatic)
continue;
anythingUsed = true;
The following then works as expected:
// https://github.com/KubaO/stackoverflown/tree/master/questions/metamethod-lookup-24577095
#include <QtCore>
class MyObject : public QObject {
Q_OBJECT
public:
Q_SLOT void aSlot() {}
Q_SLOT void aSlot2(int) {}
Q_SLOT int aSlot3(int) { return 0; }
Q_SIGNAL void aSignal();
Q_SIGNAL void aSignal2(int);
};
template <typename Func> int indexOfMethod(Func method)
{
using FuncType = QtPrivate::FunctionPointer<Func>;
int methodIndex = -1;
void *metaArgs[] = {&methodIndex, reinterpret_cast<void **>(&method)};
auto mo = FuncType::Object::staticMetaObject;
mo.static_metacall(QMetaObject::IndexOfMethod, 0, metaArgs);
return methodIndex;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << indexOfMethod(&MyObject::aSlot)
<< indexOfMethod(&MyObject::aSlot3) << indexOfMethod(&MyObject::aSignal2);
return 0;
}
#include "main.moc"
Sorry for basic question. I'm trying to show json in QPlainTextWidget. I have api function which have console output and contains all needed data. Looks like that:
int iperf_run_server(struct iperf_test *test)
{
int result, s, streams_accepted;
fd_set read_set, write_set;
struct iperf_stream *sp;
struct timeval now;
struct timeval* timeout;
......
if (test->json_output)
if (iperf_json_start(test) < 0)
return -1;
if (test->json_output) {
cJSON_AddItemToObject(test->json_start, "version", cJSON_CreateString(version));
cJSON_AddItemToObject(test->json_start, "system_info", cJSON_CreateString(get_system_info()));
} else if (test->verbose) {
iprintf(test, "%s\n", version);
iprintf(test, "%s", "");
fflush(stdout);
printf("%s\n", get_system_info());
}
.....
cleanup_server(test);
if (test->json_output) {
if (iperf_json_finish(test) < 0)
return -1;
}
....
return 0;
}
For now I have first thread with my gui, and second thread, contains class which run this function on a signal. All things works normally, but i'm not fully understand, how I can "stop" iperf_run_server for "reading/buffering" output, without any changes in api.
The simplest thing to do would be to collect each message in a string, and emit a signal from the object running in the second thread. You can connect that signal to a slot in an object in the GUI thread.A zero-timeout timer is invoked each time the event loop is done processing other events - it is a useful mechanism to leverage to run things "continuously".
For example:
#include <QApplication>
#include <QPlainTextEdit>
#include <QThread>
#include <QBasicTimer>
#include <QTextStream>
//! A thread that's always safe to destruct.
class Thread : public QThread {
private:
// This is a final class.
using QThread::run;
public:
Thread(QObject * parent = 0) : QThread(parent) {}
~Thread() {
quit();
wait();
}
};
class IperfTester : public QObject {
Q_OBJECT
struct Test { int n; Test(int n_) : n(n_) {} };
QList<Test> m_tests;
QBasicTimer m_timer;
public:
IperfTester(QObject * parent = 0) : QObject(parent) {
for (int i = 0; i < 50; ++i) m_tests << Test(i+1);
}
//! Run the tests. This function is thread-safe.
Q_SLOT void runTests() {
QMetaObject::invokeMethod(this, "runTestsImpl");
}
Q_SIGNAL void message(const QString &);
private:
Q_INVOKABLE void runTestsImpl() {
m_timer.start(0, this);
}
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_timer.timerId()) return;
if (m_tests.isEmpty()) {
m_timer.stop();
return;
}
runTest(m_tests.first());
m_tests.removeFirst();
}
void runTest(Test & test) {
// do the work
QString msg;
QTextStream s(&msg);
s << "Version:" << "3.11" << "\n";
s << "Number:" << test.n << "\n";
emit message(msg);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPlainTextEdit log;
// This order is important: the thread must be defined after the object
// to be moved into the thread.
IperfTester tester;
Thread thread;
tester.moveToThread(&thread);
thread.start();
log.connect(&tester, SIGNAL(message(QString)), SLOT(appendPlainText(QString)));
log.show();
tester.runTests();
return a.exec();
// Here, the thread is stopped and destructed first, following by a now threadless
// tester. It would be an error if the tester object was destructed while its
// thread existed (even if it was stopped!).
}
#include "main.moc"