Qt - Create WriteOnly and ReadOnly socket - qt

I have a socketDescriptor.
I need to two sockets. One of them just for ReadOnly and another one for WriteOnly.
Is it impossible? If so, how?

Qt's sockets don't support having more than one socket object per native socket, but you can approximate this by creating appropriate unidirectional interfaces.
In the example below, the interfaces don't perform any buffering, thus they enforce that there is only once of each kind of interface per socket. Once you use the interface, you shouldn't be using the socket directly.
// https://github.com/KubaO/stackoverflown/tree/master/questions/socket-split-43409221
#include <QtNetwork>
class SocketBase : public QIODevice {
Q_OBJECT
public:
explicit SocketBase(QAbstractSocket * parent) : QIODevice{parent} {
connect(parent, &QAbstractSocket::connected, this, &SocketBase::connected);
connect(parent, &QAbstractSocket::disconnected, this, &SocketBase::disconnected);
connect(parent, &QAbstractSocket::stateChanged, this, [this](QAbstractSocket::SocketState state){
emit stateChanged(state);
setOpenMode(m_dev->openMode());
});
connect(parent,
static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
this, [this](QAbstractSocket::SocketError error){
setErrorString(m_dev->errorString());
emit this->error(error);
});
setOpenMode(m_dev->openMode());
}
QAbstractSocket::SocketError error() const {
return m_dev->error();
}
QAbstractSocket::SocketState state() const {
return m_dev->state();
}
Q_SIGNAL void connected();
Q_SIGNAL void disconnected();
Q_SIGNAL void error(QAbstractSocket::SocketError);
Q_SIGNAL void stateChanged(QAbstractSocket::SocketState);
bool isSequential() const override { return true; }
protected:
QAbstractSocket * const m_dev = static_cast<QAbstractSocket*>(parent());
};
class ReadSocket : public SocketBase {
Q_OBJECT
public:
explicit ReadSocket(QAbstractSocket * parent) : SocketBase(parent) {
for (auto proxy : parent->findChildren<ReadSocket*>())
Q_ASSERT(proxy == this);
connect(m_dev, &QIODevice::readyRead, this, &QIODevice::readyRead);
}
bool atEnd() const override {
return QIODevice::atEnd() && m_dev->atEnd();
}
qint64 bytesAvailable() const override {
return m_dev->bytesAvailable();
}
bool canReadLine() const override {
return m_dev->canReadLine();
}
protected:
qint64 readData(char * data, qint64 maxLength) override {
return m_dev->read(data, maxLength);
}
qint64 readLineData(char *data, qint64 maxLength) override {
return m_dev->readLine(data, maxLength);
}
qint64 writeData(const char *, qint64) override {
return -1;
}
};
class WriteSocket : public SocketBase {
Q_OBJECT
public:
explicit WriteSocket(QAbstractSocket * parent) : SocketBase(parent) {
for (auto proxy : parent->findChildren<WriteSocket*>())
Q_ASSERT(proxy == this);
connect(m_dev, &QIODevice::bytesWritten, this, &QIODevice::bytesWritten);
}
qint64 bytesToWrite() const override {
return m_dev->bytesToWrite();
}
bool flush() {
return m_dev->flush();
}
protected:
qint64 readData(char *, qint64) override {
return -1;
}
qint64 writeData(const char * data, qint64 length) override {
return m_dev->write(data, length);
}
};
int main(int argc, char *argv[])
{
QCoreApplication app{argc, argv};
QHostAddress addr{"127.0.0.1"};
quint16 port{9341};
QTcpServer server;
if (! server.listen(addr, port)) qFatal("can't listen");
QObject::connect(&server, &QTcpServer::newConnection, &server, [&]{
auto s = server.nextPendingConnection();
QObject::connect(s, &QTcpSocket::readyRead, s, [s]{
s->write(s->readAll());
});
QObject::connect(s, &QTcpSocket::disconnected, s, &QObject::deleteLater);
});
const char data_[] = "dhfalksjdfhaklsdhfklasdfs";
auto const data = QByteArray::fromRawData(data_, sizeof(data_));
QTcpSocket client;
WriteSocket writer(&client);
ReadSocket reader(&client);
QObject::connect(&writer, &WriteSocket::connected, [&]{
writer.write(data);
});
QObject::connect(&reader, &ReadSocket::readyRead, [&]{
if (reader.bytesAvailable() >= data.size()) {
auto const read = reader.read(data.size());
Q_ASSERT(read == data);
qApp->quit();
}
});
client.connectToHost(addr, port);
return app.exec();
}
#include "main.moc"

Related

Subclassing QIODevice: Wrapper for QUdpSocket

I am trying to implement my own wrapper over QUdpSocket because of it is uncomfortable to use. I can use it, but anyway I need to implement some intermediate buffer for access to QDataStream operations. In additional:
I sublass QIODevice,
header (bit simplified):
class BerUdp : public QIODevice
{
Q_OBJECT
void startup();
public:
void setHostToWrite(const QHostAddress &address, quint16 port);
void setPortToRead(quint16 port);
qint64 bytesAvailable() const;
protected: // Reimplement:
qint64 readData(char *data, qint64 maxSize);
qint64 writeData(const char *data, qint64 maxSize);
private:
QUdpSocket* dev_write; // udp socket to write
QUdpSocket* dev_read; // udp socket to read
QBuffer m_buffer; // buffer to store received datagrams
};
.cpp file:
void BerUdp::startup()
{
m_buffer.open(QIODevice::ReadWrite);
open(QIODevice::ReadWrite);
}
void BerUdp::setHostToWrite(const QHostAddress &address, quint16 port)
{
dev_write->connectToHost(address, port);
dev_write->waitForConnected();
}
void BerUdp::setPortToRead(quint16 port)
{
dev_read->bind(port);
dev_read->open(QIODevice::ReadOnly);
bool ok = connect(dev_read, &QIODevice::readyRead,
this, &BerUdp::onReceive);
}
// Read new received data to my internal buffer
void BerUdp::onReceive()
{
bool av = dev_read->hasPendingDatagrams();
if (av)
{
int dSize = dev_read->pendingDatagramSize();
QByteArray dtg(dSize, 0);
dev_read->readDatagram(dtg.data(), dtg.size());
// write new data to the end
int buf_read_pos = m_buffer.pos();
m_buffer.seek(m_buffer.size());
m_buffer.write(dtg);
m_buffer.seek(buf_read_pos);
}
}
From the Qt documuntation on QIODevice::readData()
..When reimplementing this function it is important that this function
reads all the required data before returning. This is required in
order for QDataStream to be able to operate on the class. QDataStream
assumes all the requested information was read and therefore does not
retry reading if there was a problem...:
// Main read data function. There are only 4 bytes required, but maxSize == 0x4000 here:
qint64 BerUdp::readData(char *data, qint64 maxSize)
{
int n = m_buffer.read(data, maxSize);
// clear the data which has already read:
QByteArray& ba = m_buffer.buffer();
ba.remove(0, n); // m_buffer.size() == 0 after this
m_buffer.seek(0);
return n;
}
The problem is that after the first read I have empty buffer, and therefore my bytesAvailable() method returns 0:
qint64 BerUdp::bytesAvailable() const
{
qint64 my_size = m_buffer.size();
qint64 builtin_size = QIODevice::bytesAvailable();
return (my_size + builtin_size); // == 0 after 1st read
}
So I can not find out how many bytes are available, when use the class, for example:
BerUdp udp;
QDataStream stream;
stream.setDevice(&udp);
...
QIODevice* dev = stream.device(); // 19 bytes available here
if (dev->bytesAvailable() > 0) // == true
{
quint32 val;
stream >> val;
}
if (dev->bytesAvailable() > 0) // == false
{
//...
}
How can own wrapper of QUdpSocket be written correctly?
The idea of using intermediate buffer worked well, until I decided to move the logic to the separate QIODevice-derived class.
In the process of debugging with the Qt sources, it was found out that I should set the isSequential() property to true. Now my class works correct.
bool BerUdp::isSequential() const
{
return true;
}
Full class:
BerUdp.h
#pragma once
#include <QIODevice>
#include <QBuffer>
class QUdpSocket;
class QHostAddress;
class BerUdp : public QIODevice
{
Q_OBJECT
public:
BerUdp(QObject *parent = 0);
void startup();
void setHostToWrite(const QHostAddress &address, quint16 port);
void setPortToRead(quint16 port);
bool flush();
qint64 bytesAvailable() const;
bool waitForReadyRead(int msecs);
bool isSequential() const;
protected: // Main necessary reimplement
qint64 readData(char *data, qint64 maxSize);
qint64 writeData(const char *data, qint64 maxSize);
private slots:
void onReceive();
private:
void read_udp_datagram();
void write_new_data_to_buffer(QByteArray dtg);
private:
QUdpSocket* dev_write; // One udp socket to write
QUdpSocket* dev_read; // Another udp socket to read
// intermediate buffer to store received datagrams
// and to provide access to read- and QDataStream- operations
QBuffer m_buffer;
};
BerUdp.cpp
#include "BerUdp.h"
#include <QUdpSocket>
BerUdp::BerUdp(QObject *parent)
: QIODevice(parent)
{
startup();
}
// Initialization
void BerUdp::startup()
{
dev_write = new QUdpSocket(this);
dev_read = new QUdpSocket(this);
m_buffer.open(QIODevice::ReadWrite);
open(QIODevice::ReadWrite);
}
// Set a virtual connection to "host"
void BerUdp::setHostToWrite(const QHostAddress &address, quint16 port)
{
dev_write->connectToHost(address, port);
dev_write->waitForConnected();
}
// Bind a port for receive datagrams
void BerUdp::setPortToRead(quint16 port)
{
dev_read->bind(port);
dev_read->open(QIODevice::ReadOnly);
connect(dev_read, &QIODevice::readyRead,
this, &QIODevice::readyRead);
connect(dev_read, &QIODevice::readyRead,
this, &BerUdp::onReceive);
}
// Flush written data
bool BerUdp::flush()
{
return dev_write->flush();
}
// Returns the number of bytes that are available for reading.
// Subclasses that reimplement this function must call
// the base implementation in order to include the size of the buffer of QIODevice.
qint64 BerUdp::bytesAvailable() const
{
qint64 my_size = m_buffer.size();
qint64 builtin_size = QIODevice::bytesAvailable();
return (my_size + builtin_size);
}
bool BerUdp::waitForReadyRead(int msecs)
{
return dev_read->waitForReadyRead(msecs);
}
// Socket device should give sequential access
bool BerUdp::isSequential() const
{
return true;
}
// This function is called by QIODevice.
// It is main function for provide access to read data from QIODevice-derived class.
// (Should be reimplemented when creating a subclass of QIODevice).
qint64 BerUdp::readData(char *data, qint64 maxSize)
{
int n = m_buffer.read(data, maxSize);
// clear the data which has already been read
QByteArray& ba = m_buffer.buffer();
ba.remove(0, n);
m_buffer.seek(0);
return n;
}
// This function is called by QIODevice.
// It is main function for provide access to write data to QIODevice-derived class.
// (Should be reimplemented when creating a subclass of QIODevice).
qint64 BerUdp::writeData(const char *data, qint64 maxSize)
{
return dev_write->write(data, maxSize);
}
// Read new available datagram
void BerUdp::read_udp_datagram()
{
int dSize = dev_read->pendingDatagramSize();
QByteArray dtg(dSize, 0);
dev_read->readDatagram(dtg.data(), dtg.size());
write_new_data_to_buffer(dtg);
}
// Write received data to the end of internal intermediate buffer
void BerUdp::write_new_data_to_buffer(QByteArray dtg)
{
int buf_read_pos = m_buffer.pos();
m_buffer.seek(m_buffer.size());
m_buffer.write(dtg);
m_buffer.seek(buf_read_pos);
}
// Is called on readyRead signal
void BerUdp::onReceive()
{
bool available = dev_read->hasPendingDatagrams();
if (available)
{
read_udp_datagram();
}
}

Qt : How to emit QNetworkReply Slot after QNetworkRequest accoumplish?

I am trying to use multiple threads to request data from cloud. My problem is after the QnetworkReply finishes replyFinished(QnetworkReply*) slot never emitted. How do i do this ?
pc_gateway.h
//Main class
#include "dhvac_datacollector_thread.h"
class PC_Gateway : public QMainWindow
{
Q_OBJECT
DHVAC_DataCollector_Thread *m_DHVAC_DataCollector_Thread[70];
PC_Gateway(QWidget *parent = 0, Qt::WFlags flags = 0);
~PC_Gateway();
public slots:
void slot_start_comm();
};
pc_gateway.cpp
#include "pc_gateway.h"
PC_Gateway::PC_Gateway(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
}
PC_Gateway::~PC_Gateway()
{
}
void PC_Gateway::slot_start_comm()
{
int nNumOfThreads = 4;
for(int i=0; i<nNumOfThreads ; i++)
{
this->m_DHVAC_DataCollector_Thread[i] = new DHVAC_DataCollector_Thread(this);
this->m_DHVAC_DataCollector_Thread[i]->start();
}
}
DHVAC_DataCollector_Thread.h
//Thread class
class DHVAC_DataCollector_Thread : public QThread
{
Q_OBJECT
public:
DHVAC_DataCollector_Thread(QObject *parent);
~DHVAC_DataCollector_Thread();
void run();
void Stop();
public slots:
void replyFinished(QNetworkReply*);
};
DHVAC_DataCollector_Thread.cpp
#include "dhvac_datacollector_thread.h"
DHVAC_DataCollector_Thread::DHVAC_DataCollector_Thread(QObject *parent)
: QThread(parent)
{
}
DHVAC_DataCollector_Thread::~DHVAC_DataCollector_Thread()
{
}
void DHVAC_DataCollector_Thread::run()
{
while(true)
{
QString sUrl = "https://api.uno.taww.com/api/v2/data/sensor/"+sNodeId+"/10m/_";
QUrl url(sUrl);
bool bUrl = url.isValid();
if(!bUrl) //If URL is invalid
return false;
QNetworkRequest request;
request.setUrl(url);
request.setRawHeader("api_token","eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9");
request.setRawHeader("user_id","102473df5c9106e55d");
request.setRawHeader("cache-control","no-cache");
QNetworkAccessManager *manager = new QNetworkAccessManager();
connect(manager, SIGNAL(finished(QNetworkReply*)),this,
SLOT(replyFinished(QNetworkReply*)));
QNetworkReply *reply = manager->get(request);
}
}
void DHVAC_DataCollector_Thread::replyFinished(QNetworkReply *reply)
{
QByteArray responseData;
if(reply->error() == QNetworkReply::NoError)
{
responseData = reply->readAll();
QString sData (responseData);
reply->close();
reply->deleteLater();
qDebug()<<"Raw data:" <<sData;
}
}
You can try send your requests after receiving reply, in your replyFinished slot not in a loop. Just do one request, if replyFinished works fine that way then do the second request.
void DHVAC_DataCollector_Thread::replyFinished(QNetworkReply *reply)
{
QByteArray responseData;
if(reply->error() == QNetworkReply::NoError)
{
responseData = reply->readAll();
QString sData (responseData);
reply->close();
reply->deleteLater();
qDebug()<<"Raw data:" <<sData;
}
// nextRequest()
}
Also you have a leak. You are creating tons of QNetworkAccessManager and never releasing.

QMessagebox not show text when call show()

my problem is I need to show a message ask users wait when I check network availability of other clients.My way is I have a class workerThread to do the business, before start it I create a qMessageBox. But the message only shows the title, not the content. I have no idea why, pls help :(
Here's the worker thread:
#include <QObject>
#include <QString>
#include "clientdataobj.h"
class WorkerThread : public QObject
{
Q_OBJECT
public:
explicit WorkerThread(QObject *parent = 0);
QList<ClientDataObj> listClient() const;
void setListClient(const QList<ClientDataObj> &listClient);
signals:
void finished();
void error(QString err);
void listClientPingChecked( QList <ClientDataObj> list);
public slots:
void testPing();
private:
QList <ClientDataObj> mListClient;
bool pingEachClient(QString ip);
};
implement:
#include "workerthread.h"
#include <QFile>
#include <QMessageBox>
#include <QTextStream>
WorkerThread::WorkerThread(QObject *parent) :
QObject(parent)
{
}
void WorkerThread::testPing()
{
if (mListClient.size()==0) {
emit finished();
return;
}
else{
for(unsigned i=0;i<mListClient.size();i++){
bool result = pingEachClient(mListClient[i].ip());
if(result)
mListClient[i].setStatus(true);
else
mListClient[i].setStatus(false);
}
emit listClientPingChecked(mListClient);
}
emit finished();
}
bool WorkerThread::pingEachClient(QString ip)
{
QString pingCommand = "ping " +ip + " -c 3 | grep loss | awk ' {print $7}' > pingResult.txt";
system(qPrintable(pingCommand));
QString lossPercentTxt = readFileText("pingResult.txt") ;
lossPercentTxt.chop(1);
int lossPercent = lossPercentTxt.toInt();
if(lossPercent<10){
return true;
}
else return false;
}
QList<ClientDataObj> WorkerThread::listClient() const
{
return mListClient;
}
void WorkerThread::setListClient(const QList<ClientDataObj> &listClient)
{
mListClient = listClient;
}
How I call it in MainWindow:
on_pbSendUpdate_clicked()
{
changeModeWaitPing();
getClientOnlineList();
}
getClientOnlineList()
{
if(mListClient.size()==0){
return;
}
mpThreadPing = new QThread;
mpWorkerThread = new WorkerThread;
mpWorkerThread->setListClient(mListClient);
connectThreadPingToGui();
mpThreadPing->start();
}
changeModeWaitPing()
{
ui->pbSendUpdate->setEnabled(false);
callMsgBox("Pinging client... Pls wait!");
// callWaitDialog();
}
callMsgBox( QString text)
{
if (NULL==mMsg) {
return;
}
mMsg->setWindowTitle("INFO");
// mMsg->setAttribute(Qt::WA_DeleteOnClose);
mMsg->setWindowModality(Qt::NonModal);
mMsg->setModal(false);
QString info ="Pinging client... Pls wait!";
mMsg->setText(info);
mMsg->show();
}
connectThreadPingToGui()
{
connect(mpWorkerThread, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
connect(mpThreadPing, SIGNAL(started()), mpWorkerThread, SLOT(testPing()));
connect(mpWorkerThread, SIGNAL(finished()), mpThreadPing, SLOT(quit()));
connect(mpWorkerThread, SIGNAL(finished()), mpWorkerThread, SLOT(deleteLater()));
connect(mpThreadPing, SIGNAL(finished()), mpThreadPing, SLOT(deleteLater()));
connect(mpWorkerThread,SIGNAL(listClientPingChecked(QList<ClientDataObj>)),this,SLOT(updateListClientOnline(QList<ClientDataObj>)));
}
updateListClientOnline(QList<ClientDataObj> list)
{
mListClientOnline = list;
mPingDone = true;
if (NULL==mMsg) {
return;
}
else{
mMsg->hide();
}
if(mpDialogWaitPing==NULL){
return;
}
else{
mpDialogWaitPing->hide();
}
launchClientListTable();
}
You create a new thread, but you don't move any objects to that thread. So your new thread does nothing. I assume you wan't mpWorkerThread to be moved to the new thread. In that case you're missing mpWorkerThread->moveToThread(mpThreadPing);

QTcpSocket handling in another QThread

I need to handle incoming tcp connections on individual QThread's.
After successful client authentication, the according socket should be stored in an QList object.
[simplified main/server-side application]
class Server : public QObject
{
Q_OBJECT
public:
Server();
private:
QList<QTcpSocket*> m_connections;
QTcpServer m_server;
void handleIncomingConnection();
void handleWaiterThread();
private slots:
void treatFinishedWaiterThread();
}
[according function definitions]
handleIncomingConnection() slot is connected with the server object's (m_server) newConnection() signal.
void Server::handleIncomingConnection()
{
QThread *waiter = new QThread();
connect(waiter, SIGNAL(started()), this, SLOT(handleWaiterThread()));
connect(waiter, SIGNAL(finished()), this, SLOT(treatFinishedWaiterThread()));
moveToThread(waiter);
waiter->start();
}
void Server::handleWaiterThread()
{
// fetch requesting socket
QTcpSocket *socket = m_server->nextPendingConnection();
// HANDLE PASSWORD AUTHENTICATION HERE ...
// IF SUCCESSFUL, CONTINUE
connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
// add to list
m_connections.append(socket);
}
void Server::treatFinishedWaiterThread()
{
QThread *caller = qobject_cast<QThread*>(sender());
caller->deleteLater();
}
If I try to run this, the threads get created but no SIGNAL is emitted when they're done, so I can't delete threads afterwards. Additionally I get this message:
QObject::moveToThread: Widgets cannot be moved to a new thread
How to fix this?
[01.06.2016]
According to QTcpServer::nextPendingConnection() it says:
The returned QTcpSocket object cannot be used from another thread. If you want to use an incoming connection from another thread, you need to override incomingConnection().
So in the end I have to create another class that inherits from QTcpServer.
[01.07.2016 #1]
I revised my code and added a custom server and waiter thread class.
[custom server class]
class CustomServer : public QTcpServer
{
Q_OBJECT
public:
WServer(QObject* = nullptr) : QTcpServer(parent) {}
signals:
void connectionRequest(qintptr);
protected:
void incomingConnection(qintptr socketDescriptor)
{
emit connectionRequest(socketDescriptor);
}
};
[custom thread class]
class Waiter : public QThread
{
Q_OBJECT
public:
Waiter(qintptr socketDescriptor, QObject *parent = nullptr)
: QThread(parent)
{
// Create socket
m_socket = new QTcpSocket(this);
m_socket->setSocketDescriptor(socketDescriptor);
}
signals:
void newSocket(QTcpSocket*);
protected:
void run()
{
// DO STUFF HERE
msleep(2500);
emit newSocket(m_socket);
}
private:
QTcpSocket *m_socket;
};
[new main class]
class ServerGUI : public QWidget
{
Q_OBJECT
public:
Server(QObject*);
private:
QList<QTcpSocket*> m_connections;
CustomServer m_server;
private slots:
void handleConnectionRequest(qintptr);
void handleNewSocket(QTcpSocket*);
}
void CustomServer::handleConnectionRequest(qintptr socketDescriptor)
{
Waiter *nextWaiter = new Waiter(socketDescriptor, this);
connect(nextWaiter, SIGNAL(newSocket(QTcpSocket*)), this, SLOT(handleNewSocket(QTcpSocket*)));
connect(nextWaiter, SIGNAL(finished()), this, SLOT(deleteLater()));
nextWaiter->start();
}
void CustomServer::handleNewSocket(QTcpSocket *socket)
{
// DO STUFF HERE ...
connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
// FINALLY ADD TO ACTIVE-CLIENT LIST ...
}
Signal & Slot specific settings:
Since CustomServer is defined as a class member (m_server) within my main widget class (that handles GUI; called ServerGUI),
connectionRequest(qintptr) signal of m_server gets connected with handleConnectionRequest(qintptr) slot of ServerGUI instance.
But now my application is crashing immediately after startup, showing following message in debug window:
HEAP[qtapp.exe]: Invalid address specified to RtlValidateHeap( 000002204F430000, 0000006E0090F4C0 )
What may be the cause of this?
[01.10.2016 #2]
I adapted my code according to user2014561's answer.
for CustomServer class
class CustomServer : public QTcpServer
{
Q_OBJECT
public:
WServer(QHostAddress, quint16, quint16, QObject* = nullptr);
~WServer();
void kickAll();
void kickClient(qintptr);
QHostAddress localAddress() const;
quint16 serverPort() const;
bool isReady() const;
bool alreadyConnected(qintptr) const;
bool clientLimitExhausted() const;
signals:
void clientConnected(qintptr);
void clientDisconnected(qintptr);
private slots:
void destroyedfunc(QObject*);
// JUST FOR TESTING PURPOSES
void waiterFinished();
private:
QList<ServerPeer*> m_connections;
quint16 m_maxAllowedClients;
bool m_readyState;
void incomingConnection(qintptr);
};
for kickAll():
void WServer::kickAll()
{
while (!m_connections.isEmpty())
{
ServerPeer *peer = m_connections.first();
QEventLoop loop;
connect(peer->thread(), SIGNAL(destroyed()), &loop, SLOT(quit())); // ### PROBLEM ENCOUNTERED HERE
QMetaObject::invokeMethod(peer, "deleteLater", Qt::QueuedConnection);
loop.exec();
}
}
for kickClient(qintptr):
void WServer::kickClient(qintptr client_id)
{
foreach (ServerPeer *peer, m_connections)
{
bool peerState;
QMetaObject::invokeMethod(peer, "hasSocket", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, peerState), Q_ARG(qintptr, client_id));
if (peerState)
{
QEventLoop loop;
connect(peer->thread(), SIGNAL(destroyed()), &loop, SLOT(quit()));
QMetaObject::invokeMethod(peer, "deleteLater", Qt::QueuedConnection);
loop.exec();
break;
}
}
}
for destroyedfunc(QObject*):
void CustomServer::destroyedfunc(QObject *obj)
{
ServerPeer *peer = static_cast<ServerPeer*>(obj);
m_connections.removeAll(peer);
}
for incomingConnection(qintptr):
void WServer::incomingConnection(qintptr handle)
{
ServerPeer *peer = new ServerPeer();
QThread *waiter = new QThread();
m_connections.append(peer); // add to list
peer->moveToThread(waiter);
// notify about client connect
connect(peer, SIGNAL(connected(qintptr)), this, SIGNAL(clientConnected(qintptr)));
// stop waiter thread by indirectly raising finished() signal
connect(peer, SIGNAL(finished()), waiter, SLOT(quit()));
// notify about client disconnect
connect(peer, SIGNAL(disconnected(qintptr)), this, SIGNAL(clientDisconnected(qintptr)));
// remove client from list
connect(peer, SIGNAL(destroyed(QObject*)), this, SLOT(destroyedfunc(QObject*)));
// notify about finished waiter thread; only for debug purposes
connect(waiter, SIGNAL(finished()), this, SLOT(waiterFinished()));
// remove waiter thread when finished
connect(waiter, SIGNAL(finished()), waiter, SLOT(deleteLater()));
QMetaObject::invokeMethod(peer, "start", Qt::QueuedConnection,
Q_ARG(qintptr, handle));
waiter->start();
}
for ServerPeer class
class ServerPeer : public QObject
{
Q_OBJECT
public:
ServerPeer(QObject* = nullptr);
~ServerPeer();
bool hasSocket(qintptr) const;
signals:
void connected(qintptr);
void disconnected(qintptr);
void finished();
public slots:
void start(qintptr);
void disconnect();
private slots :
void notifyConnect();
void notifyDisconnect();
private:
QTcpSocket *m_peer;
qintptr m_id;
};
for ServerPeer(QObject*):
ServerPeer::ServerPeer(QObject *parent) : QObject(parent), m_peer(nullptr)
{
}
for ~ServerPeer():
ServerPeer::~ServerPeer()
{
disconnect();
}
for start(qintptr):
void ServerPeer::start(qintptr handle)
{
qDebug() << "New waiter thread has been started.";
m_peer = new QTcpSocket(this);
if (!m_peer->setSocketDescriptor(handle))
{
this->deleteLater();
return;
}
if (true /*verification here*/)
{
connect(m_peer, SIGNAL(disconnected()), this, SLOT(notifyDisconnect()));
connect(m_peer, SIGNAL(disconnected()), this, SLOT(deleteLater()));
// manually do connected notification
QTimer::singleShot(0, this, SLOT(notifyConnect()));
}
else
{
this->deleteLater();
}
emit finished();
}
for disconnect():
void ServerPeer::disconnect()
{
if (m_peer != nullptr)
{
if (m_peer->state() != QAbstractSocket::SocketState::ClosingState
&& m_peer->state() != QAbstractSocket::SocketState::UnconnectedState)
m_peer->abort();
delete m_peer;
m_peer = nullptr;
}
}
for notifyConnect():
void ServerPeer::notifyConnect()
{
emit connected(m_peer);
}
for notifyDisconnect():
void ServerPeer::notifyDisconnect()
{
emit disconnected(m_peer);
}
for ServerGUI class
class ServerGUI : public QWidget
{
Q_OBJECT
public:
ServerGUI(QWidget* = nullptr);
private:
Ui::ServerWindow ui;
CustomServer *m_server;
private slots:
// For further handling, e.g. updating client view
void handleNewClient(qintptr);
void handleRemovedClient(qintptr);
}
for ServerGUI(QWidget*):
ServerGUI::ServerGUI(QWidget *parent) : QWidget(parent)
{
// initialize gui elements;
// GENERATED WITH ACCORDING *.ui FILE
ui.setupUi(this);
m_server = new WServer(QHostAddress::LocalHost, 1234, 2, this);
if (!m_server->isReady())
{
qDebug() << "Server could not start!";
delete m_server;
m_server = nullptr;
return;
}
connect(m_server, SIGNAL(clientConnected(qintptr)), this, SLOT(handleNewClient(qintptr)));
connect(m_server, SIGNAL(clientDisconnected(qintptr)), this, SLOT(handleRemovedClient(qintptr)));
}
And here my main function:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ServerGUI w;
w.show();
return a.exec();
}
With given code following message pops up if I try to kick a (selected) client:
QMetaObject::invokeMethod: No such method ServerPeer::hasSocket(qintptr)
QObject::connect: Cannot connect (null)::destroyed() to QEventLoop::quit()
How to fix this?
If I understood correctly, you want to run each peer of your server on a separate thread, if so, then the following may help you:
create a subclass of QTcpServer
reimplement incomingConnection() method
create an instance (without parent) of QThread and of ServerPeer and start the thread
do the SIGNAL - SLOT connections to remove the peer from the list and delete the thread and the peers instances
add ServerPeer to your QList
once time started, do the credentials verification; if you reject them, abort the connection
Edit, considerations:
You haven't got the connected SIGNAL because when you set a socketDescriptor to the socket was already connected, so you can simple assume after setSocketDescriptor that the socket is connected and do what you want.
About the error when closing, it's happens because you not releasing properly the threads, see my edit how can you solve that.
Finally QTcpSocket must not be accessed by diferent threads, if you need to call ServerPeer from another thread use QMetaObject::invokeMethod with QueuedConnection or BlockingQueuedConnection and SIGNAL SLOT mechanism.
Edit 2:
Now the server and it's peers will be deleted on MainWindow::closeEvent and that way you can see the disconnected function being called. I guess the problem happens depending of the order that the classes will be deleted.
You can interact with the socket including send data through it, but I believe that will be painless use the Qt methods for cross-thread calls already mentioned. In my example you can easily write to a specific peer or to all peers.
customserver.h:
//Step 1
#include <QtCore>
#include <QtNetwork>
#include "serverpeer.h"
class CustomServer : public QTcpServer
{
Q_OBJECT
public:
explicit CustomServer(const QHostAddress &host, quint16 port, quint16 maxconnections, QObject *parent = nullptr);
~CustomServer();
void kickAll();
void kickClient(qintptr id);
void writeData(const QByteArray &data, qintptr id);
void writeData(const QByteArray &data);
QHostAddress localAddress();
quint16 serverPort();
bool isReady();
signals:
void clientConnected(qintptr);
void clientDisconnected(qintptr);
private slots:
void destroyedfunc(QObject *obj);
private:
void incomingConnection(qintptr handle);
QList<ServerPeer*> m_connections;
int m_maxAllowedClients;
};
customserver.cpp:
#include "customserver.h"
CustomServer::CustomServer(const QHostAddress &host, quint16 port, quint16 maxconnections, QObject *parent) :
m_maxAllowedClients(maxconnections), QTcpServer(parent)
{
listen(host, port);
}
CustomServer::~CustomServer()
{
kickAll();
}
//Step 2
void CustomServer::incomingConnection(qintptr handle)
{
// handle client limit
if (m_connections.size() >= m_maxAllowedClients)
{
qDebug() << "Can't allow new connection: client limit reached!";
QTcpSocket *socket = new QTcpSocket();
socket->setSocketDescriptor(handle);
socket->abort();
delete socket;
return;
}
//Step 3
ServerPeer *peer = new ServerPeer();
QThread *waiter = new QThread();
peer->moveToThread(waiter);
//Step 4
connect(peer, SIGNAL(connected(qintptr)), this, SIGNAL(clientConnected(qintptr)));
connect(peer, SIGNAL(disconnected(qintptr)), this, SIGNAL(clientDisconnected(qintptr)));
connect(peer, SIGNAL(destroyed()), waiter, SLOT(quit()));
connect(peer, SIGNAL(destroyed(QObject*)), this, SLOT(destroyedfunc(QObject*)));
connect(waiter, SIGNAL(finished()), waiter, SLOT(deleteLater()));
QMetaObject::invokeMethod(peer, "start", Qt::QueuedConnection, Q_ARG(qintptr, handle));
waiter->start();
//Step 5
m_connections.append(peer);
}
void CustomServer::kickAll()
{
while (!m_connections.isEmpty())
{
ServerPeer *peer = m_connections.first();
QEventLoop loop;
connect(peer->thread(), SIGNAL(destroyed()), &loop, SLOT(quit()));
QMetaObject::invokeMethod(peer, "deleteLater", Qt::QueuedConnection);
loop.exec();
}
}
void CustomServer::kickClient(qintptr id)
{
foreach (ServerPeer *peer, m_connections)
{
ServerPeer::State hassocket;
QMetaObject::invokeMethod(peer, "hasSocket", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ServerPeer::State, hassocket), Q_ARG(qintptr, id));
if (hassocket == ServerPeer::MyTRUE)
{
QEventLoop loop;
connect(peer->thread(), SIGNAL(destroyed()), &loop, SLOT(quit()));
QMetaObject::invokeMethod(peer, "deleteLater", Qt::QueuedConnection);
loop.exec();
break;
}
}
}
void CustomServer::writeData(const QByteArray &data)
{
foreach (ServerPeer *peer, m_connections)
QMetaObject::invokeMethod(peer, "writeData", Qt::QueuedConnection, Q_ARG(QByteArray, data));
}
void CustomServer::writeData(const QByteArray &data, qintptr id)
{
foreach (ServerPeer *peer, m_connections)
{
ServerPeer::State hassocket;
QMetaObject::invokeMethod(peer, "hasSocket", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ServerPeer::State, hassocket), Q_ARG(qintptr, id));
if (hassocket == ServerPeer::MyTRUE)
{
QMetaObject::invokeMethod(peer, "writeData", Qt::QueuedConnection, Q_ARG(QByteArray, data));
break;
}
}
}
QHostAddress CustomServer::localAddress()
{
return QTcpServer::serverAddress();
}
quint16 CustomServer::serverPort()
{
return QTcpServer::serverPort();
}
bool CustomServer::isReady()
{
return QTcpServer::isListening();
}
void CustomServer::destroyedfunc(QObject *obj)
{
ServerPeer *peer = static_cast<ServerPeer*>(obj);
m_connections.removeAll(peer);
}
serverpeer.h:
#include <QtCore>
#include <QtNetwork>
class ServerPeer : public QObject
{
Q_OBJECT
public:
explicit ServerPeer(QObject *parent = nullptr);
~ServerPeer();
enum State
{
MyTRUE,
MyFALSE
};
signals:
void connected(qintptr id);
void disconnected(qintptr id);
public slots:
ServerPeer::State hasSocket(qintptr id);
void start(qintptr handle);
void writeData(const QByteArray &data);
private slots:
void readyRead();
void notifyConnect();
void notifyDisconnect();
private:
QTcpSocket *m_peer;
qintptr m_id;
};
serverpeer.cpp:
#include "serverpeer.h"
ServerPeer::ServerPeer(QObject *parent) : QObject(parent), m_peer(nullptr)
{
}
ServerPeer::~ServerPeer()
{
if (m_peer)
m_peer->abort();
}
ServerPeer::State ServerPeer::hasSocket(qintptr id)
{
if (m_id == id)
return MyTRUE;
else
return MyFALSE;
}
void ServerPeer::start(qintptr handle)
{
m_peer = new QTcpSocket(this);
m_peer->setSocketDescriptor(handle);
//Step 6
if (true /*verification here*/)
{
m_id = handle;
QTimer::singleShot(0, this, SLOT(notifyConnect()));
connect(m_peer, SIGNAL(readyRead()), this, SLOT(readyRead()));
connect(m_peer, SIGNAL(disconnected()), this, SLOT(notifyDisconnect()));
connect(m_peer, SIGNAL(disconnected()), this, SLOT(deleteLater()));
}
else
{
m_peer->abort();
this->deleteLater();
}
}
void ServerPeer::readyRead()
{
qDebug() << m_peer->readAll() << QThread::currentThread();
}
void ServerPeer::writeData(const QByteArray &data)
{
m_peer->write(data);
m_peer->flush();
}
void ServerPeer::notifyConnect()
{
emit connected(m_id);
}
void ServerPeer::notifyDisconnect()
{
emit disconnected(m_id);
}
mainwindow.cpp:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
qRegisterMetaType<qintptr>("qintptr");
m_server = new CustomServer(QHostAddress::LocalHost, 1024, 2, this);
if (!m_server->isReady())
{
qDebug() << "Server could not start!";
delete m_server;
m_server = nullptr;
return;
}
connect(m_server, SIGNAL(clientConnected(qintptr)), this, SLOT(handleNewClient(qintptr)));
connect(m_server, SIGNAL(clientDisconnected(qintptr)), this, SLOT(handleRemovedClient(qintptr)));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::closeEvent(QCloseEvent *)
{
if (m_server)
{
delete m_server;
m_server = nullptr;
}
}
void MainWindow::handleNewClient(qintptr id)
{
qDebug() << __FUNCTION__ << id;
m_server->writeData(QString("Hello client id: %0\r\n").arg(id).toLatin1(), id);
m_server->writeData(QString("New client id: %0\r\n").arg(id).toLatin1());
}
void MainWindow::handleRemovedClient(qintptr id)
{
qDebug() << __FUNCTION__ << id;
}

Passing a QObject to an Script function with QJSEngine?

I'm trying to call a function in an external script while passing a QObject as a parameter.
My QObject is defined as this:
#ifndef INSERTVALUES_H
#define INSERTVALUES_H
#include <QObject>
struct insertValueDef
{
QString name;
QString xmlCode;
QString value;
bool key;
bool insert;
};
typedef insertValueDef TinsertValueDef;
class insertValues : public QObject
{
Q_OBJECT
public:
explicit insertValues(QObject *parent = 0);
~insertValues();
void insertValue(TinsertValueDef value);
int count();
void setItemName(int index, QString name);
void setItemXMLCode(int index, QString xmlCode);
void setItemValue(int index, QString value);
void setItemIsKey(int index, bool isKey);
void setItemToInsert(int index, bool toInsert);
QString itemName(int index);
QString itemXMLCode(int index);
QString itemValue(int index);
bool itemIsKey(int index);
bool itemToInsert(int index);
bool valueIsNumber(int index);
int getIndexByColumnName(QString name);
private:
QList<TinsertValueDef> m_insertList;
};
#endif // INSERTVALUES_H
My JS Script function is this:
function beforeInsert(table,data)
{
if (table == "tmpTable")
{
var index = data.getIndexByColumnName("tmpfield");
if (index >= 0)
{
data.setItemValue(index,"Carlos Quiros");
}
}
}
The code that runs runs the script is the following:
QFile scriptFile(javaScript);
if (!scriptFile.open(QIODevice::ReadOnly))
{
log("Error: Script file defined but cannot be opened");
return 1;
}
JSEngine.evaluate(scriptFile.readAll(), javaScript);
scriptFile.close();
insertValues insertObject;
TinsertValueDef tfield;
tfield.key = false;
tfield.name = "tmpfield";
tfield.xmlCode = "tmpCode";
tfield.value = "tmpValue";
tfield.insert = true;
insertObject.insertValue(tfield);
QString error;
beforeInsertFunction = JSEngine.evaluate("beforeInsert",error);
if (!beforeInsertFunction.isError())
{
QJSValue insertListObj = JSEngine.newQObject(&insertObject);
QJSValue result = beforeInsertFunction.call(QJSValueList() << "tmpTable" << insertListObj);
if (result.isError())
{
log("Error calling BeforInsert JS function.");
return 1;
}
else
{
log("JS function seems to be ok");
for (int pos = 0; pos < insertObject.count(); pos++)
{
log(insertObject.itemName(pos) + "-" + insertObject.itemValue(pos));
}
return 1;
}
}
else
{
log("Error evaluating BeforInsert JS function. [" + error + "]");
return 1;
}
I can see that the parameter "table" is passing properly but the rest of the code is not working. I guess I cannot do:
var index = data.getIndexByColumnName("tmpfield");
Any idea what am I doing wrong? and what else should I do to make it work?
Thanks,
In order to access properties or invoke methods of QObjects passed to QJSEngine (or to QML), you need to declare them using Q_PROPERTY and Q_INVOKABLE macros in your QObject-derived class declaration.
Please see the Qt documentation for more details: Exposing Attributes of C++ Types to QML

Resources