Reading from TCP socket - c++

I have to receive data from TCP socket. It has fixed 16-bytes header (one header's field is data length) and data. I receive it with BigEndian, but it was send with LittleEndian.
I can' find good solution for such a data reception. What is working for me now is (reading one of the header fields):
QByteArray packetType = tcpSocket->read(2);
QDataStream in(packetType);
in.setByteOrder(QDataStream::LittleEndian);
quint16 pT = 0;
in >> pT;
Is there any better way to set QByteArray endianess?
Or a way to read specified number of bytes with QDataStream?

You're almost doing it right. The intermediate QByteArray is unnecessary. You can set the data stream directly on the socket:
QTcpSocket socket;
QDataStream in(&socket);
in.setByteOrder(QDataStream::LittleEndian);
...
void onReadyRead() {
in.startTransaction();
uint16_t packet_type;
in >> packet_type;
if (in.status == QDataStream::Ok) {
if (packet_type == Foo) {
uint32_t foo_data;
in >> foo_data;
if (! in.commitTransaction())
return; // not enough data
// got a full packet here
}
}
in.abortTransaction();
}

Related

Sending hex data via serial communication with QT

I am finding way to send hex data via serial communication
i searched it several times and followed some ways but it didn't work.
i checked that protocol is working with using other software that sending hex data to device
below is my code
const char data[]={0xAA,0xAA,0x01,0x00,0x00,0x0E,0x00,0x01,0x00,0x00,0x00,0x2D,0x37,0x1D,0xAA,0xAA,0x01,0x00,0x00,0x0E,0x00,0x0C,0x10,0x00,0x00,0x01,0x76,0x13};
serial->setPortName(("COM8"));
initSerialPort(); // baud rate and etc
if(serial->open(QIODevice::ReadWrite))
{
qDebug()<<"Port is open!";
if(serial->isWritable())
{
qDebug()<<"Yes, i can write to port!";
int size = sizeof(data);
serial->write(data,size);
}
}
and when i use other declare like uint16_t, uchar, write function cannot convert argument 1 from uint16_t (or uchar) to const char *
i did try also this form
QByteArray hex("AAAA0100000E00010000002D371DAAAA0100000E000C100000017613");
QByteArray data = QByteArray::fromHex(hex);
and it also didnt work
You can do this using only QByteArray like:
connect(serial, &QSerialPort::readyRead, this, &YourClass::doSomeStuff);
QByteArray arr;
arr += static_cast<char>(0xAA);
arr += static_cast<char>(0x01);
<...>
serial->setPortName("COM8");
initSerialPort(); // baud rate and etc
if(serial->open(QIODevice::ReadWrite) && serial->isWritable())
serial->write(arr);

QSerialPort - wating for whole data from sender

I'm working with a serial device.
The QSerialPort is in a separate thread.
Thread is created this way:
QThread* serialthread = new QThread;
Serial* serial = new Serial();
serial->moveToThread(serialthread);
When Data is available this signal in my thread worker is emited:
void Serial::process()
{
serialport = new QSerialPort();
connect(this->serialport,SIGNAL(readyRead()),this,SLOT(readyToRead()));
}
void Serial::readyToRead()
{
emit SIG_dataAvailable(this->read());
}
This is the function that reads the data and checks if the data is correct - the second byte on my serial device says how long the rest of the packet is...
QByteArray Serial::read() const
{
QByteArray receivedData;
int length;
receivedData = serialport->readAll();
length = receivedData[1];
if(length != receivedData.length() - 1)
{
qDebug() << "protocol error.";
return NULL;
}
return receivedData;
}
My problem is that the signal QSerialPort::readyRead is emited before the data from the serial device is complete in the buffer. Any idea how to solve this problem?
There is absolutely NO guarantee that you'll get whole data at ONCE. You can solve this problem in some ways.
1) If you have fixed size package you can do something like this:
void foo::onSerialRead()
{
//! Is there whole datagram appears?
if (m_serial->bytesAvailable() < ::package_size) {
//! If not, waiting for other bytes
return;
}
//! Read fixed size datagram.
QByteArray package = m_serial->read(::package_size);
//! And notify about it.
emit packageReady(package);
}
2) If your package size may vary. Then you have to include "hader" in to your package. This header should contain at least "start" byte and data size (Its second byte in your case). And header shuld be fixed size. Then you can do something like this:
void foo::onSerialRead()
{
static QByteArray package;
static bool isHeaderRead = false;
static quint8 startByte = 0;
static quint8 dataSize = 0;
//! Is there whole header appears?
if (m_serial->bytesAvailable() < ::header_size) {
//! If not, waiting for other bytes
return;
}
if (!isHeaderRead) {
//! Read fixed size header.
package.append(m_serial->read(::header_size));
QDataStream out(&package);
out >> startByte;
//! Check is it actually beginning of our package?
if (Q_UNLIKELY(startByte != ::protocol_start_byte)) {
return;
}
out >> dataSize;
isHeaderRead = true;
}
//! Check is there whole package available?
if (Q_LIKELY(dataSize > m_serial->bytesAvailable())) {
//! If not, waiting for other bytes.
return;
}
//! Read rest.
package.append(m_serial->read(dataSize));
//! And notify about it.
emit packageReady(package);
package.clear();
isHeaderRead = false;
}
And there is absolutely no point in putting your QSerial in to different thread.

QSerialPort readyread() SIGNAL

I have a problem when receiving bytes from RS232 in QByteArray. I connected readyread() signal to call my serialport method and inside it I am reading bytes with readAll() to an QByteArray. Whenever data is available it rewrites QByteArray, but I want to receive it all, and then use data, but now I cannot because it is in parts. What to do?
Simply append to the array. You'll also need some criterion to determine when you've received all the data you wished. This can be, e.g. a given number of bytes:
class Communicator {
int expect;
QSerialPort port;
QByteArray reply;
void processReply() {
...
}
public:
Communicator() {
QObject::connect(&port, &QIODevice::readyRead, [this]{
reply += port.readAll();
if (expect && reply.size() >= expect) {
processReply();
reply.clear();
expect = 0;
}
});
...
};

Manage stream client server with Qt

I wonder how complex software client-server manage stream.
For example when I play counter strike, they are lots of packet send from client to server and server to clients. What is the best method to manage stream ?
I think :
Server send data to client
void Server::send(const QString &message)
{
QByteArray paquet;
QDataStream out(&paquet, QIODevice::WriteOnly);
out << (quint16) 0; //packet size
out << (quint8)14; //packet type to execute a special action on the client
out << message; // data, here string but can be another class
out.device()->seek(0);
out << (quint16) (paquet.size() - sizeof(quint16)); //write packet size
socket->write(paquet);
}
Client receive data from server
void Client::receiveData()
{
QDataStream in(socket);
if (size_packet == 0) // size_packet, initilize to 0
{
if (socket->bytesAvailable() < (int)sizeof(quint16))
return;
in >> tailleMessage;
}
if (socket->bytesAvailable() < tailleMessage)
return;
//now I can get data
quint8 typeObject;
in >> typeObject;
switch(typeObject){
case 1:
...
break;
case 2:
...
break;
case 3:
...
break;
...
...
case 14:
QString data; //can be another custom class
in >> data;
write(data);
break;
//my solution, the server can execute an action on the client depending type packet
}
size_packet= 0;
}
I guess I have to use a shared library between the client and the server and serialized my custom class.
But should I do otherwise? Use json or xml or protocol buffer (google) or something else ???
As #MrEricSir mention, games usually use UDP protocol. It uses datagrams, not streams. So some datagrams can be lost during data transmission. You protocol should work with data lost. Often such protocol are binary, they do not use xml, json or protobuf because of overhead.

How to read to asio buffer say `1` byte from one socket and than `read_some` more from another?

So I am trying to implement timed http connection Keep-Alive. And I need to be capable of killing it on some time-out. So currently I have (or at least I would like to have):
void http_request::timed_receive_base(boost::asio::ip::tcp::socket& socket, int buffer_size, int seconds_to_wait, int seconds_to_parse)
{
this->clear();
http_request_parser_state parser_state = METHOD;
char* buffer = new char[buffer_size];
std::string key = "";
std::string value = "";
boost::asio::ip::tcp::iostream stream;
stream.rdbuf()->assign( boost::asio::ip::tcp::v4(), socket.native() );
try
{
do
{
stream.expires_from_now(boost::posix_time::seconds(seconds_to_wait));
int bytes_read = stream.read_some(boost::asio::buffer(buffer, buffer_size));
stream.expires_from_now(boost::posix_time::seconds(seconds_to_parse));
if (stream) // false if read timed out or other error
{
parse_buffer(buffer, parser_state, key, value, bytes_read);
}
else
{
throw std::runtime_error("Waiting for 2 long...");
}
} while (parser_state != OK);
}
catch (...)
{
delete buffer;
throw;
}
delete buffer;
}
But there is no read_some in tcp::iostream, so compiler gives me an error:
Error 1 error C2039: 'read_some' : is not a member of 'boost::asio::basic_socket_iostream<Protocol>'
That is why I wonder - how to read 1 byte via stream.read (like stream.read(buffer, 1);) and than read_some to that very buffer via socket API ( it would look like int bytes_read = socket.read_some(boost::asio::buffer(buffer, buffer_size)); and than call my parse_buffer function with real bytes_read value)
BTW it seems like there will be a really sad problem of 1 last byte..(
Sorry to be a bit rough, but did you read the documentation? The socket iostream is supposed to work like the normal iostream, like cin and cout. Just do stream >> var. Maybe you want basic_stream_socket::read_some instead?