How to interact with a child QProcess? - c++

In my program I have a child process that interacts with a serial port specified to it when the parent process demands it. For example the parent process commands the child process to read a number of bytes with a certain time out from the opened port by sending this command: read 100 1000. The child process launches and opens the port successfully and I can see the message port openned successfully! but from there onwards it won't read the parent commands.
Here's the child source code:
SerialPortHandler.h
#ifndef SERIALPORTHANDLER_H
#define SERIALPORTHANDLER_H
#include <QObject>
#include <QSocketNotifier>
#include <QTextStream>
#include <QSerialPort>
#include <QFile>
#include <QTimer>
#include <QDebug>
#include <QtCore>
enum CommandType { READ, WRITE, BAD, UNKNOWN };
class SerialPortHandler : public QObject
{
Q_OBJECT
public:
explicit SerialPortHandler(QString portname, QString baudrate, QObject *parent = 0);
signals:
public slots:
void execmd();
bool open(const QString& portname, const QString& baudrate);
qint64 read(char * buff, const qint64 size, const qint32 timeout);
QString convertToCaseInsensitiveRegExp(QString str);
CommandType analyze(const QString& line);
qint64 getNum(const QString &line, int index);
void reply(char *buff);
private:
QSocketNotifier *innotif;
QSerialPort *sp;
QTimer *timer;
};
#endif // SERIALPORTHANDLER_H
SerialPortHandler.cpp
#include "SerialPortHandler.h"
#include <unistd.h>
#include <limits>
SerialPortHandler::SerialPortHandler(QString portname, QString baudrate, QObject *parent) :
QObject(parent)
{
timer = new QTimer(this);
sp = new QSerialPort(this);
if(!open(portname, baudrate)) {
qDebug() << sp->error() << sp->errorString();
exit(sp->error());
}
innotif = new QSocketNotifier(STDIN_FILENO, QSocketNotifier::Read, this);
connect(innotif, SIGNAL(activated(int)), this, SLOT(execmd()));
}
void SerialPortHandler::execmd()
{
qDebug() << "command received. analyzing...";
// qint64 nbr = -1, size = -1;
// qint32 timeout = -1;
// char * buff = 0;
// QTextStream in(stdin);
// QString ln = in.readAll();
// switch (analyze(ln)) {
// case READ:
// size = getNum(ln, 1);
// timeout = getNum(ln, 2);
// if(size > -1 && timeout > -1)
// nbr = read(buff, size, timeout);
// if(nbr > -1)
// reply(buff);
// break;
// default:
// break;
// }
}
bool SerialPortHandler::open(const QString &portname, const QString &baudrate)
{
sp->setPortName(portname);
if (!sp->open(QIODevice::ReadWrite) ||
!sp->setBaudRate(baudrate.toInt()) ||
!sp->setDataBits(QSerialPort::Data8) ||
!sp->setParity(QSerialPort::NoParity) ||
!sp->setStopBits(QSerialPort::OneStop) ||
!sp->setFlowControl(QSerialPort::NoFlowControl)) {
return false;
}
sp->clear();
qDebug() << "port openned successfully!";
return true;
}
//day light wont affect this timer so the system wont freeze
qint64 SerialPortHandler::read(char *buff, const qint64 size, const qint32 timeout)
{
qint64 numbytesread = -1;
timer->start(timeout);
while (true) {
if(timer->remainingTime() > 0) {
return -1;
}
if((sp->isReadable() && sp->bytesAvailable() > 0) ||
(sp->isReadable() && sp->waitForReadyRead(10))) {
numbytesread += sp->read(buff, size);
}
if(numbytesread < 0) {
return -1;
}
if(numbytesread == size) {
break;
}
}
return numbytesread;
}
void SerialPortHandler::notify()
{
}
QString SerialPortHandler::convertToCaseInsensitiveRegExp(QString str)
{
QString result;
for(int i = 0 ; i < str.size() ; ++i) {
result.append("[");
result.append(str.at(i).toLower());
result.append(str.at(i).toUpper());
result.append("]");
}
return result;
}
CommandType SerialPortHandler::analyze(const QString &line)
{
QString read, write;
read = convertToCaseInsensitiveRegExp("read");
write = convertToCaseInsensitiveRegExp("write");
if(line.contains(QRegExp(QString("^.*%1\\s+[1-9]\\d*\\s+[1-9]\\d*.*").arg(read)))) {
return READ;
}
return UNKNOWN;
}
qint64 SerialPortHandler::getNum(const QString& line, int index) {
QStringList args(line.split(QRegExp("\\s+")));
bool done;
qint64 size = args.at(index).toInt(&done, 10);
if(done) {
return size;
}
return -1;
}
void SerialPortHandler::reply(char * buff) {
QDataStream out(stdout);
out << buff;
}
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include "SerialPortHandler.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
if(argc != 3) {
qDebug() << "usage:" << argv[0] << "port" << "baudrate";
} else {
SerialPortHandler *sph = new SerialPortHandler(argv[1], argv[2]);
}
return a.exec();
}
My parent process consists of the following:
ParentProcess.h
#ifndef PARENTPROCESS_H
#define PARENTPROCESS_H
#include <QObject>
#include <QtCore>
class ParentProcess : public QObject
{
Q_OBJECT
public:
explicit ParentProcess(QObject *parent = 0);
signals:
public slots:
private slots:
void sendRead();
void writeSomething();
void handleError(QProcess::ProcessError error);
private:
QProcess *p;
};
#endif // PARENTPROCESS_H
ParentProcess.cpp
#include "ParentProcess.h"
#include <QDebug>
ParentProcess::ParentProcess(QObject *parent) :
QObject(parent)
{
p = new QProcess(this);
connect(p, SIGNAL(readyReadStandardOutput()), this, SLOT(sendRead()));
connect(p, SIGNAL(readyReadStandardError()), this, SLOT(sendRead()));
connect(p, SIGNAL(started()), this, SLOT(writeSomething()));
connect(p, SIGNAL(error(QProcess::ProcessError)), this, SLOT(handleError(QProcess::ProcessError)));
QStringList args;
args << "/dev/ttyUSB0" << "115200";
p->start("/home/moki/Work/Programs/build-serialio-Desktop_Qt_5_3_0_GCC_64bit-Debug/serialio", args, QProcess::ReadWrite);
}
void ParentProcess::sendRead() {
qDebug() << "data:" << p->readAllStandardError() << p->readAllStandardOutput();
}
void ParentProcess::writeSomething() {
qDebug() << "writing";
QString cmd = "read 10 10000\n";
qint64 a = p->write(cmd.toStdString().c_str());
qDebug() << "wrote:" << a;
}
void ParentProcess::handleError(QProcess::ProcessError error)
{
switch (error) {
case QProcess::FailedToStart:
qDebug() << "failed to start";
break;
case QProcess::Crashed:
qDebug() << "crashed.";
break;
default:
break;
}
}
main.cpp
#include <QCoreApplication>
#include "ParentProcess.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
ParentProcess p;
return a.exec();
}
I have seen a couple of other answers in SO but none of them address my issue. As you can see my child process is not supposed to complete and exit. It will remain launched as long as the parent process wishes. Is it correct to use QProcess-launched processes this way?

I changed my code to the following and now it's working. But I haven't understood why this code works and my original one doesn't.
in the parent process source i changed the constructor as follows:
ParentProcess::ParentProcess(QObject *parent) :
QObject(parent)
{
p = new QProcess(this);
connect(p, SIGNAL(error(QProcess::ProcessError)), this, SLOT(handleError(QProcess::ProcessError)));
connect(p, SIGNAL(readyRead()), this, SLOT(sendRead()));
connect(p, SIGNAL(started()), this, SLOT(writeSomething()));
QStringList args;
args << "/dev/ttyUSB0" << "115200";
p->setProcessChannelMode(QProcess::MergedChannels);
p->start("/home/moki/Work/Programs/build-serialio-Desktop_Qt_5_3_0_GCC_64bit-Debug/serialio", args, QProcess::ReadWrite);
}
and the sendRead() function to this:
void ParentProcess::sendRead() {
int bytes = p->bytesAvailable();
qDebug() << "data:" << p->read(bytes);
}
finally, the writeSomething() to:
void ParentProcess::writeSomething() {
qDebug() << "gonna write.";
if(p->state() == QProcess::Running) {
qDebug() << "writing";
QString cmd = "read 10 10000\n";
qint64 a = p->write(cmd.toStdString().c_str());
qDebug() << "wrote:" << a << "bytes.";
}
}
and now it works. If anyone could please explain this to me, I would be really grateful.

Related

Value lost after 2nd call of method

I have a problem with this situation (underneath) in the console.
The data is lost after passing twice in my method called in main.ccp after making the MyClass object.
main.ccp
#include <QCoreApplication>
#include <QDebug>
#include <iostream>
#include <myclass.h>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyClass* myClass = new MyClass();
qDebug() << "Debug part 1";
myClass->method();
qDebug() << "Debug part 2";
myClass->method();
return a.exec();
}
The result in console:
Debug part 1
0
1 ".." "0 Bytes" "26.03.2022 08:21:13"
2 "stephane/" "0 Bytes" "26.04.2022 19:48:04"
3 ".localized" "0 Bytes" "26.03.2022 08:21:13"
4 "Shared/" "0 Bytes" "26.03.2022 08:21:13"
Debug part 2
0
The sources files:
myclass.h
myclass.ccp
entrys.h
entrys.ccp
entry.h
entry.ccp
myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QObject>
#include <QString>
#include <QDateTime>
#include "entrys.h"
class MyClass : public QObject
{
Q_OBJECT
public:
explicit MyClass(QObject *parent = nullptr);
void method();
signals:
private:
Entrys* entrys;
};
#endif // MYCLASS_H
myclass.ccp
#include "myclass.h"
#include <iostream>
#include "myclass.h"
#include "entry.h"
#include "entrys.h"
MyClass::MyClass(QObject *parent) : QObject(parent) {
this->entrys = new Entrys();
try {
this->entrys->setDir("/Users/","L");
} catch(ErrDirNotFound &e) {
qDebug() << e.description << " " << e.what();
}
}
void MyClass::method() {
int i = 0;
qDebug() << i;
foreach(Entry *v, this->entrys->getEntrys("L")) {
i++;
qDebug() << i << v->getName() << " " << v->getSizeString(2) << " " << v->getDateLastChangeString();
}
}
entrys.h
#ifndef ENTRYS_H
#define ENTRYS_H
#include <QObject>
#include "entry.h"
struct ErrDirNotFound: public std::exception {
QString description;
const char *what() const throw() {
return "Directory not found";
}
};
class Entrys : public QObject
{
Q_OBJECT
public:
explicit Entrys(QObject *parent = nullptr);
void setDir(QString dir, QString side);
QVector<Entry*> getEntrys(QString side);
Entry* getEntry(QString side, QString key);
QString getPath(QString side);
protected:
signals:
private:
QHash<QString, QString> hash_path;
QHash<QString, QVector<Entry*>> hash_side_entry;
void setList(QString side);
};
#endif // ENTRYS_H
entrys.ccp
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <QDebug>
#include <iostream>
#include <QDateTime>
#include <QProcess>
#include "entry.h"
#include "entrys.h"
Entrys::Entrys(QObject *parent)
: QObject{parent}
{
}
void Entrys::setList(QString side) {
QVector<Entry*> vec_entry;
QString path = this->getPath(side);
QByteArray path_ba = path.toLocal8Bit();
const char* path_cstr = path_ba.constData();
struct dirent *lecture;
DIR *dir;
struct stat buf;
QString currentPath;
int row = 0;
dir = opendir(path_cstr);
if (dir == NULL) {
ErrDirNotFound e;
QString description = "Path " + path + " don't exist !";
e.description = description;
throw e;
}
while ((lecture = readdir(dir)) != NULL) {
if (strcmp(lecture->d_name, ".") != 0) {
currentPath = path + lecture->d_name;
QByteArray path_qb = currentPath.toLocal8Bit();
const char *charCurrentPath = path_qb.constData();
if ((stat(charCurrentPath, &buf)) == -1) {
qCritical() << "stat" << currentPath;
}
int size = buf.st_size;
QDateTime modif = QDateTime::fromSecsSinceEpoch(buf.st_mtime);
Entry *entry = new Entry();
if (!strcmp(lecture->d_name, "..")) {
if (this->getPath(side) != "/") {
entry->setValue(lecture->d_name, 0, modif, 0);
}
} else {
if (S_ISDIR(buf.st_mode)) {
QString qstringTemp = lecture->d_name;
qstringTemp += "/";
entry->setValue(qstringTemp, 0, modif, buf.st_mode);
} else {
entry->setValue(lecture->d_name, size, modif, buf.st_mode);
}
}
vec_entry.append(entry);
row++;
}
}
delete lecture;
closedir(dir);
this->hash_side_entry.insert(side, vec_entry);
}
void Entrys::setDir(QString dir, QString side) {
this->hash_path.insert(side, dir);
this->setList(side);
}
QVector<Entry*> Entrys::getEntrys(QString side) {
return this->hash_side_entry.take(side);
}
QString Entrys::getPath(QString side) {
return this->hash_path[side];
}
Entry* Entrys::getEntry(QString side, QString key) {
QVector<Entry*> entry = this->getEntrys(side);
for (int i = 0; i < entry.length(); i++) {
if (entry[i]->getName() == key) {
return entry[i];
}
}
return nullptr;
}
entry.h
#ifndef ENTRY_H
#define ENTRY_H
#include <QObject>
#include <QString>
#include <QDateTime>
class Entry : public QObject
{
Q_OBJECT
public:
explicit Entry(QObject *parent = nullptr);
Entry(QString name, int size_file, QDateTime date_last_change, mode_t mode);
void setValue(QString name, int size_file, QDateTime date_last_change, mode_t mode);
QString getName();
QString getSizeString(int decimals);
QString getDateLastChangeString();
signals:
private:
QString name;
int size_file;
QDateTime date_last_change;
mode_t mode;
};
#endif // ENTRY_H
entry.ccp
#include <QDateTime>
#include "entry.h"
Entry::Entry(QObject *parent)
: QObject{parent}
{
}
Entry::Entry(QString name, int size_file, QDateTime date_last_change, mode_t mode)
{
this->name = name;
this->size_file = size_file;
this->date_last_change = date_last_change;
this->mode = mode;
}
void Entry::setValue(QString name, int size_file, QDateTime date_last_change, mode_t mode)
{
this->name = name;
this->size_file = size_file;
this->date_last_change = date_last_change;
this->mode = mode;
}
QString Entry::getName()
{
return this->name;
}
QString Entry::getSizeString(int decimals) {
int bytes = this->size_file;
if (bytes == 0) return "0 Bytes";
const int K = 1024;
const QStringList SIZES = { "Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
const int I = std::floor((std::log(bytes) / std::log(K)));
int dm = decimals < 0 ? 0 : decimals;
if (I == 0) dm = 0;
return QString::number((bytes / std::pow(K, I)),'f', dm) + " " + SIZES[I];
}
QString Entry::getDateLastChangeString() {
return this->date_last_change.toString("dd.MM.yyyy hh:mm:ss");
}
Tracking through your code by eye, I find this concerning:
QVector<Entry*> Entrys::getEntrys(QString side) {
return this->hash_side_entry.take(side);
}
A bit of googling indicates that QHash's take "Removes the item with the key from the hash and returns the value associated with it." So your getEntrys is modifying your hash_side_entry - taking data out of it. Thus when your second call to method ends up calling getEntrys a second time, there's nothing in hash_side_entry anymore.

QAbstractNativeEventFilter not receive XCB_KEY_PRESS

I'm building a Qt application that process native xcb events. But cannot receive key press event or mouse events.
Below are my codes.
// myfilter.h
class MyFilter : public QObject, public QAbstractNativeEventFilter
{
Q_OBJECT
public:
explicit MyFilter(xcb_connection_t *conn, QObject *parent = nullptr);
~MyFilter();
virtual bool nativeEventFilter(const QByteArray &eventType, void *message,
long *result) override;
signals:
private:
xcb_connection_t *_conn;
xcb_window_t _rootWindow;
};
// myfilter.cpp
MyFilter::MyFilter(xcb_connection_t *conn, QObject *parent)
: QObject(parent)
{
this->_conn = conn;
xcb_screen_t *screen;
screen = xcb_setup_roots_iterator(xcb_get_setup(this->_conn)).data;
this->_rootWindow = screen->root;
qDebug() << "Root window: " << this->_rootWindow;
// xcb_grab_key(this->_conn, 1, screen->root, XCB_MOD_MASK_ANY, XCB_GRAB_ANY,
// XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
QCoreApplication::instance()->installNativeEventFilter(this);
}
MyFilter::~MyFilter()
{
xcb_disconnect(this->_conn);
}
bool MyFilter::nativeEventFilter(const QByteArray &eventType,
void *message, long *result)
{
if (eventType == "xcb_generic_event_t") {
xcb_generic_event_t *ev = static_cast<xcb_generic_event_t*>(message);
auto responseType = ev->response_type & ~0x80;
switch (responseType) {
case XCB_KEY_PRESS:
qDebug() << "XCB_KEY_PRESS";
break;
case XCB_EXPOSE:
qDebug() << "XCB_EXPOSE";
break;
case XCB_PROPERTY_NOTIFY:
{
xcb_property_notify_event_t *property_notify = (xcb_property_notify_event_t*)ev;
qDebug() << "XCB_PROPERTY_NOTIFY" << property_notify->atom << property_notify->window;
break;
}
case XCB_CLIENT_MESSAGE:
{
xcb_client_message_event_t *client_message = (xcb_client_message_event_t*)ev;
qDebug() << "XCB_CLIENT_MESSAGE" << client_message->window;
break;
}
default:
qDebug() << responseType;
break;
}
} else {
qDebug() << "Not a xcb_generic_event_t";
}
return false;
}
and
// main.cpp
#include <QApplication>
#include <QX11Info>
#include <xcb/xcb.h>
#include "myfilter.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
xcb_connection_t *conn = QX11Info::connection();
MyFilter myFilter(conn);
return a.exec();
}
I want to receive XCB_KEY_PRESS but when I run this application and press any key, it always prints 85 which is defined as XCB_ALLOC_NAMED_COLOR.
What is this event and why response_type reports this rather than XCB_KEY_PRESS?

QAudioInput from array

I have a device with microphones that connects to my computer through Ethernet and it cannot be seen by Qt as an audio device, so, I get packets from it and put them to QByteArray. I need to play these packets from stream. Somewhere in the Internet I found a solution to almost the same problem, but there internal microphone was used.
#include <QApplication>
#include <iostream>
#include <cassert>
#include <QCoreApplication>
#include <QAudioInput>
#include <QAudioOutput>
#include <QBuffer>
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
QBuffer rdBuff;
QBuffer wrBuff;
wrBuff.open(QBuffer::WriteOnly);
rdBuff.open(QBuffer::ReadOnly);
QObject::connect(&wrBuff, &QIODevice::bytesWritten, [&wrBuff, &rdBuff](qint64)
{
rdBuff.buffer().remove(0, rdBuff.pos());
// set pointer to the beginning of the unread data
const auto res = rdBuff.seek(0);
assert(res);
// write new data
rdBuff.buffer().append(wrBuff.buffer());
// remove all data that was already written
wrBuff.buffer().clear();
wrBuff.seek(0);
});
const auto decideAudioFormat = [](const QAudioDeviceInfo& devInfo)
{
QAudioFormat format;
format.setSampleRate(8000);
format.setChannelCount(1);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
if (devInfo.isFormatSupported(format))
{
return format;
}
else
{
std::cerr << "Raw audio format not supported by backend, cannot play audio.\n";
throw 0;
}
};
QAudioInput audioInput(decideAudioFormat(QAudioDeviceInfo::defaultInputDevice()));
QAudioOutput audioOutput(decideAudioFormat(QAudioDeviceInfo::defaultOutputDevice()));
audioInput.start(&wrBuff);
audioOutput.start(&rdBuff);
return app.exec();
}
It works quite well, but I need to set QByteArray as QAudioInput's source.
Is there any possible solution?
Not sure if i'm directly answering your question. But a possible solution is feed the output audio device manually (push mode) when new data comes.
You can also use a custom (QFile inherited) class to record sound, and when sound come, feeds both the file and output audio device.
Here is a example:
AudioOutput.h:
#ifndef AUDIOOUTPUT_H
#define AUDIOOUTPUT_H
#include <QtCore>
#include <QtMultimedia>
#define MAX_BUFFERED_TIME 10*1000
static inline int timeToSize(int ms, const QAudioFormat &format)
{
return ((format.channelCount() * (format.sampleSize() / 8) * format.sampleRate()) * ms / 1000);
}
class AudioOutput : public QObject
{
Q_OBJECT
public:
explicit AudioOutput(QObject *parent = nullptr);
public slots:
bool start(const QAudioDeviceInfo &devinfo,
const QAudioFormat &format,
int time_to_buffer);
void write(const QByteArray &data);
private slots:
void verifyBuffer();
void preplay();
void play();
private:
bool m_initialized;
QAudioOutput *m_audio_output;
QIODevice *m_device;
QByteArray m_buffer;
bool m_buffer_requested;
bool m_play_called;
int m_size_to_buffer;
int m_time_to_buffer;
int m_max_size_to_buffer;
QAudioFormat m_format;
};
#endif // AUDIOOUTPUT_H
AudioRecorder.h:
#ifndef AUDIORECORDER_H
#define AUDIORECORDER_H
#include <QtCore>
#include <QtMultimedia>
class AudioRecorder : public QFile
{
Q_OBJECT
public:
explicit AudioRecorder(const QString &name, const QAudioFormat &format, QObject *parent = nullptr);
~AudioRecorder();
using QFile::open;
public slots:
bool open();
qint64 write(const QByteArray &data);
void close();
private:
void writeHeader();
bool hasSupportedFormat();
QAudioFormat format;
};
#endif // AUDIORECORDER_H
AudioOutput.cpp:
#include "audiooutput.h"
AudioOutput::AudioOutput(QObject *parent) : QObject(parent)
{
m_initialized = false;
m_audio_output = nullptr;
m_device = nullptr;
m_buffer_requested = true;
m_play_called = false;
m_size_to_buffer = 0;
m_time_to_buffer = 0;
m_max_size_to_buffer = 0;
}
bool AudioOutput::start(const QAudioDeviceInfo &devinfo,
const QAudioFormat &format,
int time_to_buffer)
{
if (!devinfo.isFormatSupported(format))
{
qDebug() << "Format not supported by output device";
return m_initialized;
}
m_format = format;
int internal_buffer_size;
//Adjust internal buffer size
if (format.sampleRate() >= 44100)
internal_buffer_size = (1024 * 10) * format.channelCount();
else if (format.sampleRate() >= 24000)
internal_buffer_size = (1024 * 6) * format.channelCount();
else
internal_buffer_size = (1024 * 4) * format.channelCount();
//Initialize the audio output device
m_audio_output = new QAudioOutput(devinfo, format, this);
//Increase the buffer size to enable higher sample rates
m_audio_output->setBufferSize(internal_buffer_size);
m_time_to_buffer = time_to_buffer;
//Compute the size in bytes to be buffered based on the current format
m_size_to_buffer = timeToSize(m_time_to_buffer, m_format);
//Define a highest size that the buffer are allowed to have in the given time
//This value is used to discard too old buffered data
m_max_size_to_buffer = m_size_to_buffer + timeToSize(MAX_BUFFERED_TIME, m_format);
m_device = m_audio_output->start();
if (!m_device)
{
qDebug() << "Failed to open output audio device";
return m_initialized;
}
//Timer that helps to keep playing data while it's available on the internal buffer
QTimer *timer_play = new QTimer(this);
timer_play->setTimerType(Qt::PreciseTimer);
connect(timer_play, &QTimer::timeout, this, &AudioOutput::preplay);
timer_play->start(10);
//Timer that checks for too old data in the buffer
QTimer *timer_verifier = new QTimer(this);
connect(timer_verifier, &QTimer::timeout, this, &AudioOutput::verifyBuffer);
timer_verifier->start(qMax(m_time_to_buffer, 10));
m_initialized = true;
return m_initialized;
}
void AudioOutput::verifyBuffer()
{
if (m_buffer.size() >= m_max_size_to_buffer)
m_buffer.clear();
}
void AudioOutput::write(const QByteArray &data)
{
m_buffer.append(data);
preplay();
}
void AudioOutput::preplay()
{
if (!m_initialized)
return;
//Verify if exists a pending call to play function
//If not, call the play function async
if (!m_play_called)
{
m_play_called = true;
QMetaObject::invokeMethod(this, "play", Qt::QueuedConnection);
}
}
void AudioOutput::play()
{
//Set that last async call was triggered
m_play_called = false;
if (m_buffer.isEmpty())
{
//If data is empty set that nothing should be played
//until the buffer has at least the minimum buffered size already set
m_buffer_requested = true;
return;
}
else if (m_buffer.size() < m_size_to_buffer)
{
//If buffer doesn't contains enough data,
//check if exists a already flag telling that the buffer comes
//from a empty state and should not play anything until have the minimum data size
if (m_buffer_requested)
return;
}
else
{
//Buffer is ready and data can be played
m_buffer_requested = false;
}
int readlen = m_audio_output->periodSize();
int chunks = m_audio_output->bytesFree() / readlen;
//Play data while it's available in the output device
while (chunks)
{
//Get chunk from the buffer
QByteArray samples = m_buffer.mid(0, readlen);
int len = samples.size();
m_buffer.remove(0, len);
//Write data to the output device
if (len)
m_device->write(samples);
//If chunk is smaller than the output chunk size, exit loop
if (len != readlen)
break;
//Decrease the available number of chunks
chunks--;
}
}
AudioRecorder.cpp:
#include "audiorecorder.h"
AudioRecorder::AudioRecorder(const QString &name, const QAudioFormat &format, QObject *parent) : QFile(name, parent), format(format)
{
}
AudioRecorder::~AudioRecorder()
{
if (!isOpen())
return;
close();
}
bool AudioRecorder::hasSupportedFormat()
{
return (format.sampleSize() == 8
&& format.sampleType() == QAudioFormat::UnSignedInt)
|| (format.sampleSize() > 8
&& format.sampleType() == QAudioFormat::SignedInt
&& format.byteOrder() == QAudioFormat::LittleEndian);
}
bool AudioRecorder::open()
{
if (!hasSupportedFormat())
{
setErrorString("Wav PCM supports only 8-bit unsigned samples "
"or 16-bit (or more) signed samples (in little endian)");
return false;
}
else
{
if (!QFile::open(ReadWrite | Truncate))
return false;
writeHeader();
return true;
}
}
qint64 AudioRecorder::write(const QByteArray &data)
{
return QFile::write(data);
}
void AudioRecorder::writeHeader()
{
QDataStream out(this);
out.setByteOrder(QDataStream::LittleEndian);
// RIFF chunk
out.writeRawData("RIFF", 4);
out << quint32(0); // Placeholder for the RIFF chunk size (filled by close())
out.writeRawData("WAVE", 4);
// Format description chunk
out.writeRawData("fmt ", 4);
out << quint32(16); // "fmt " chunk size (always 16 for PCM)
out << quint16(1); // data format (1 => PCM)
out << quint16(format.channelCount());
out << quint32(format.sampleRate());
out << quint32(format.sampleRate() * format.channelCount()
* format.sampleSize() / 8 ); // bytes per second
out << quint16(format.channelCount() * format.sampleSize() / 8); // Block align
out << quint16(format.sampleSize()); // Significant Bits Per Sample
// Data chunk
out.writeRawData("data", 4);
out << quint32(0); // Placeholder for the data chunk size (filled by close())
Q_ASSERT(pos() == 44); // Must be 44 for WAV PCM
}
void AudioRecorder::close()
{
// Fill the header size placeholders
quint32 fileSize = size();
QDataStream out(this);
// Set the same ByteOrder like in writeHeader()
out.setByteOrder(QDataStream::LittleEndian);
// RIFF chunk size
seek(4);
out << quint32(fileSize - 8);
// data chunk size
seek(40);
out << quint32(fileSize - 44);
QFile::close();
}
main.cpp:
#include <QtCore>
#include "audiooutput.h"
#include "audiorecorder.h"
#include <signal.h>
QByteArray tone_generator()
{
//Tone generator from http://www.cplusplus.com/forum/general/129827/
const unsigned int samplerate = 8000;
const unsigned short channels = 1;
const double pi = M_PI;
const qint16 amplitude = std::numeric_limits<qint16>::max() * 0.5;
const unsigned short n_frequencies = 8;
const unsigned short n_seconds_each = 1;
float frequencies[n_frequencies] = {55.0, 110.0, 220.0, 440.0, 880.0, 1760.0, 3520.0, 7040.0};
const int n_samples = channels * samplerate * n_frequencies * n_seconds_each;
QVector<qint16> data;
data.resize(n_samples);
int index = n_samples / n_frequencies;
for (unsigned short i = 0; i < n_frequencies; ++i)
{
float freq = frequencies[i];
double d = (samplerate / freq);
int c = 0;
for (int j = index * i; j < index * (i + 1); j += 2)
{
double deg = 360.0 / d;
data[j] = data[j + (channels - 1)] = qSin((c++ * deg) * pi / 180.0) * amplitude;
}
}
return QByteArray((char*)data.data(), data.size() * sizeof(qint16));
}
void signalHandler(int signum)
{
qDebug().nospace() << "Interrupt signal (" << signum << ") received.";
qApp->exit();
}
int main(int argc, char *argv[])
{
//Handle console close to ensure destructors being called
#ifdef Q_OS_WIN
signal(SIGBREAK, signalHandler);
#else
signal(SIGHUP, signalHandler);
#endif
signal(SIGINT, signalHandler);
QCoreApplication a(argc, argv);
QAudioFormat format;
format.setSampleRate(8000);
format.setChannelCount(1);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
AudioOutput output;
AudioRecorder file("tone.wav", format);
if (!output.start(QAudioDeviceInfo::defaultOutputDevice(), format, 10 * 1000)) //10 seconds of buffer
return a.exec();
if (!file.open())
{
qDebug() << qPrintable(file.errorString());
return a.exec();
}
qDebug() << "Started!";
QByteArray audio_data = tone_generator();
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&]{
qDebug() << "Writting" << audio_data.size() << "bytes";
output.write(audio_data);
file.write(audio_data);
});
qDebug() << "Writting" << audio_data.size() << "bytes";
output.write(audio_data);
file.write(audio_data);
timer.start(8000); //8 seconds because we generated 8 seconds of sound
return a.exec();
}
Well. I tried to apply the code (just playback for now) to my situation but there is a problem that it plays data once, if there is no emitting "MorePackets()", or doesn't play at all, if "MorePackets()" occurs.
micserver.h
#ifndef MICSERVER_H
#define MICSERVER_H
//-----------------------------//
#include <QObject>
#include <QDebug>
#include <QTcpServer>
#include <QTcpSocket>
#include <QAudioFormat>
#include <QAudioOutput>
#include <QFile>
#include <QBuffer>
#include <QByteArray>
#include <QDataStream>
#include <iostream>
#include "audiooutput.h"
//-----------------------------//
class MicServer : public QObject {
Q_OBJECT
public:
explicit MicServer(QObject *parent = nullptr);
QTcpSocket* Socket;
private:
QTcpServer* Server;
QAudioFormat format;
AudioOutput output;
QByteArray GetPackets(QTcpSocket* ClientP, int PacketsNumP);
signals:
void MorePackets();
public slots:
void NewConnection();
void Play();
};
//-----------------------------//
#endif
micserver.cpp
#include "micserver.h"
//-----------------------------//
QByteArray MicServer :: GetPackets(QTcpSocket* ClientP, int PacketsNumP) {
QBuffer BufferL;
BufferL.open(QBuffer :: WriteOnly);
QDataStream InputL(&BufferL);
InputL.setVersion(QDataStream::Qt_5_10);
QByteArray TempArray;
for (int i = 0; i < PacketsNumP; i++) {
ClientP -> waitForReadyRead(3000);
InputL << ClientP -> readAll();
}
for (int i = 0; i < PacketsNumP; i++) {
TempArray.push_back(BufferL.buffer().mid(76 + i * 1172, 256));
}
BufferL.close();
return TempArray;
}
//-----------------------------//
MicServer :: MicServer(QObject *parent) : QObject(parent) {
Server = new QTcpServer;
if (!Server -> listen(QHostAddress :: Any, 49112)) {
qDebug() << "Failed to launch server!";
} else {
qDebug() << "Server launched!";
}
connect(Server, SIGNAL(newConnection()), this, SLOT(NewConnection()));
connect(Server, SIGNAL(newConnection()), this, SLOT(Play()));
connect(this, SIGNAL(MorePackets()), this, SLOT(Play()));
format.setSampleRate(16000);
format.setChannelCount(1);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
output.start(QAudioDeviceInfo::defaultOutputDevice(), format);
}
void MicServer :: NewConnection() {
Socket = Server -> nextPendingConnection();
qDebug() << "New connection!";
qDebug() << Socket -> localAddress().toString();
}
void MicServer :: Play() {
QByteArray audio_data = GetPackets(Socket, 250);
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&]{
qDebug() << "Writting" << audio_data.size() << "bytes";
output.write(audio_data);
});
qDebug() << "Writting" << audio_data.size() << "bytes";
output.write(audio_data);
timer.start(2000);
emit(MorePackets());
}

Periodic sending w/ QTcpSocket, QTcpServer & QTimer

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();
}

Qt Message Handler crashes if installed on separate class?

Whenever there is a message sent to my custom message handler, it is crashing the application at the marked line through a segmentation fault error.
// main.cpp
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QStringList data;
data.append(context.category);
data.append(context.file);
data.append(context.function);
data.append(QString(context.line));
data.append(QString(context.version));
Logger::get_obj()->myMessageOutput(type, data, msg);
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setApplicationName("MyApp");
a.setWindowIcon(QIcon(":/Icons/MyApp.png"));
Logger log;
qInstallMessageHandler(myMessageOutput);
log.turnOnDebug();
MySplashScreen splash;
splash.show();
a.processEvents();
...
splash.close();
MainWindow *w = new MainWindow();
int errorCode = a.exec();
delete w;
session.destruct();
qDebug() << "\n*** Application exited with error code " << errorCode << " ***";
return errorCode;
}
// logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <QObject>
#include <QFile>
#include <QTextStream>
#include <QMutex>
class Logger : public QObject
{
Q_OBJECT
public:
void myMessageOutput(QtMsgType type, QStringList context, const QString msg);
explicit Logger(QObject *parent = 0);
~Logger();
static Logger *get_obj() {return ptr;}
static void turnOnDebug() {get_obj()->debugMode = true;}
static void turnOffDebug() {get_obj()->debugMode = false;}
static void moveLogToSession(QString sessionName);
private:
QFile *logFile;
QTextStream *stream;
QMutex *mutex;
bool debugMode;
bool errorMsg;
static Logger *ptr;
void write(QString str);
signals:
void SaveSession();
void FatalSaveSession();
public slots:
};
#endif // LOGGER_H
// logger.cpp
#include <QDir>
#include <QFile>
#include <QMessageBox>
#include <QApplication>
#include "logger.h"
#include "systemvariables.h"
Logger *Logger::ptr = NULL;
Logger::Logger(QObject *parent) : QObject(parent)
{
logFile = NULL;
stream = NULL;
mutex = NULL;
debugMode = false;
errorMsg = false;
ptr = this;
QString path = SystemVariables::getAppData();
QDir dir = QDir(path);
if(dir.exists())
{
path.append("/MyApp/Logs");
dir = QDir(path);
if(!dir.exists())
dir.mkpath(path);
logFile = new QFile(path + "/Session.log");
if(logFile->exists())
logFile->remove();
if(!logFile->open(QFile::WriteOnly | QFile::Text))
{
qFatal("Could not create log file.");
}
stream = new QTextStream(logFile);
stream->setRealNumberNotation(QTextStream::SmartNotation);
stream->setRealNumberPrecision(15);
*stream << "*** MyApp Session Begins ***\n";
}
else
qFatal("Could not create log file.");
mutex = new QMutex();
}
void Logger::myMessageOutput(QtMsgType type, QStringList context, const QString msg)
{
mutex->lock();
QString str = "";
switch (type)
{
case QtDebugMsg:
if(!debugMode)
{
mutex->unlock();
return;
}
write(msg);
break;
case QtInfoMsg:
str.append("\n*** Information ***\n");
str.append(msg + "\n");
str.append("Category: " + context.at(0) + "\n");
str.append("File: " + context.at(1) + "\n");
str.append("Function: " + context.at(2) + "\n");
str.append("Line: " + context.at(3) + "\n");
str.append("Version: " + context.at(4));
str.append("\n*** Information Complete ***\n");
write(str);
break;
case QtWarningMsg:
if(!(context.at(2).contains("setGeometry")))
{
str.append("\n*** Warning ***\n");
...
str.append("\n*** Warning Complete ***\n");
write(str);
errorMsg = true;
emit Logger::ptr->SaveSession();
}
break;
case QtCriticalMsg:
str.append("\n*** Critical ***\n");
...
str.append("\n*** Critical Complete ***\n");
write(str);
errorMsg = true;
emit Logger::ptr->SaveSession();
break;
case QtFatalMsg:
str.append("\n*** Fatal ***\n");
...
str.append("\n*** Fatal Complete ***\n");
write(str);
errorMsg = false;
emit Logger::ptr->FatalSaveSession();
QApplication::exit(-2);
}
Logger::mutex->unlock();
}
void Logger::write(QString str)
{
if(!stream)
return;
if(str.isEmpty())
return;
(*stream) << str; //!!!!!!!!!!!!CRASHES HERE***********
stream->flush();
}
void Logger::moveLogToSession(QString sessionName)
{
QMutex *myMutex = get_obj()->mutex;
myMutex->lock();
QTextStream *myStream = get_obj()->stream;
myStream->flush();
QFile *myLogFile = get_obj()->logFile;
myLogFile->flush();
myLogFile->close();
delete myStream;
QString path = SystemVariables::getAppData() + "/Smovault/Logs";
if(!myLogFile->open(QFile::ReadOnly | QFile::Text))
{
QString errtitle = "FATAL ERROR!";
QString errtxt = myLogFile->errorString() + "\nCould not open the log file: ";
errtxt.append(path + "/Session.log");
errtxt.append(".\nApplication Abort!");
QMessageBox *mb = new QMessageBox(QMessageBox::Critical, errtitle, errtxt);
mb->exec();
delete mb;
QApplication::exit(-1);
}
QTextStream in(myLogFile);
QString data = in.readAll();
myLogFile->close();
myLogFile->remove();
delete myLogFile;
path = SystemVariables::getAppData() + "/Smovault/Sessions/" + sessionName;
myLogFile = new QFile(path + "/Session.log");
if(!myLogFile->open(QFile::WriteOnly | QFile::Text))
{
QString errtitle = "FATAL ERROR!";
QString errtxt = myLogFile->errorString() + "\nCould not write to the log file: ";
errtxt.append(path + "/Session.log");
errtxt.append(".\nApplication Abort!");
QMessageBox *mb = new QMessageBox(QMessageBox::Critical, errtitle, errtxt);
mb->exec();
delete mb;
QApplication::exit(-1);
}
QTextStream out(myLogFile);
out << data;
out.flush();
myLogFile->flush();
myStream = new QTextStream(myLogFile);
myStream->setRealNumberNotation(QTextStream::SmartNotation);
myStream->setRealNumberPrecision(15);
myMutex->unlock();
}
I would really be thankful if someone could help me by pointing out my mistakes.
If crash happens after calling moveLogToSession, i guess that happens because you destroy stream
QTextStream *myStream = get_obj()->stream;
myStream->flush();
...
delete myStream; // here
And never setting up the new stream
myStream = new QTextStream(myLogFile);
get_obj()->stream = myStream; // you need THIS