C++ QTcpSocket:读写
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20546750/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
QTcpSocket: reading and writing
提问by DiscobarMolokai
I know some similar questions may have been asked already, but the answers to those I found covered very specific problems and I still haven't figured it out.
我知道可能已经有人问过一些类似的问题,但我发现的那些问题的答案涵盖了非常具体的问题,我仍然没有弄清楚。
In my program I'm creating a QObject (called QPeer) that uses a QTcpSocket to communicate with another such object over a network. QPeer has a slot that accepts a QByteArray with data (sendData(QByteArray)
). The entire contents of that array are considered to be one 'message' and they are written to the socket. I want to do the following: every time a message is written, I want the receiving QPeer to emit its signal dataReceived(QByteArray)
exactly once, that QByteArray containing the entire message. (NOTE: all signals/slots, both private ones connecting the QPeer with its socket and the public ones such as sendData(QByteArray)
are serialized by using Qt::QueuedConnection
whenever necessary.)
在我的程序中,我创建了一个 QObject(称为 QPeer),它使用 QTcpSocket 通过网络与另一个此类对象进行通信。QPeer 有一个插槽,它接受带有数据 ( sendData(QByteArray)
)的 QByteArray 。该数组的全部内容被视为一个“消息”,并将它们写入套接字。我想执行以下操作:每次写入消息时,我希望接收方 QPeer 只发出dataReceived(QByteArray)
一次信号,即包含整个消息的 QByteArray。(注意:所有信号/插槽,包括将 QPeer 与其套接字连接的私有信号/插槽和公共信号/插槽,例如在必要时sendData(QByteArray)
使用序列化Qt::QueuedConnection
。)
I use the signal QTcpSocket::readyRead()
for asynchronous reading from the socket. Now I know I can't just call QTcpSocket::write()
once in sendData and then assume that for every write I do, the QTcpSocket on the other side produces exactly one readyRead signal. So what should I do?
我使用信号QTcpSocket::readyRead()
从套接字异步读取。现在我知道我不能只QTcpSocket::write()
在 sendData 中调用一次,然后假设对于我所做的每次写入,另一端的 QTcpSocket 恰好产生一个 readyRead 信号。所以我该怎么做?
This is my idea, please tell me if this will work:
这是我的想法,请告诉我这是否可行:
WRITING:
写作:
void QPeer::sendData(QByteArray data)
{
// TODO: write data.size() as raw int of exactly 4 bytes to socket
const char *bytes = data.constData();
int bytesWritten = 0;
while (bytesWritten < data.size())
bytesWritten += _socket->write(bytes + bytesWritten);
}
READING:
读:
now I want the read function (connected to QTcpSocket::readyRead()
) to use the header (the 4 byte int specifying the length of the message) and then read that amount of bytes; next emit dataReceived with exactly those bytes. I'm having serious trouble trying to do this. For example: what to do if readyRead is emitted and I can read the header of a message, but not the amount of bytes specified? Or what if a header has only been received partially?
现在我希望读取函数(连接到QTcpSocket::readyRead()
)使用标头(指定消息长度的 4 字节整数),然后读取该字节数;接下来发出 dataReceived 正是这些字节。我在尝试这样做时遇到了严重的麻烦。例如:如果发出 readyRead 并且我可以读取消息的标题,但不能读取指定的字节数,该怎么办?或者如果只收到了部分报头怎么办?
1. How do I correctly write the header (4 byte int) to the socket?
1. 如何正确地将标头(4 字节整数)写入套接字?
2. How do I correctly implement the read function so that it does what I want?
2.我如何正确实现读取功能,以便它做我想要的?
Any tips are welcome. Thanks!
欢迎任何提示。谢谢!
回答by Antonio Dias
I worked on a project that does what you expect, see here the solution that I developed to our problems, simplified to be easier to understand:
我从事了一个符合您期望的项目,请在此处查看我针对我们的问题开发的解决方案,简化为更易于理解:
Edited, added support to the server deal with multiple clients.
编辑,添加了对服务器处理多个客户端的支持。
Client.h:
客户端.h:
#include <QtCore>
#include <QtNetwork>
class Client : public QObject
{
Q_OBJECT
public:
explicit Client(QObject *parent = 0);
public slots:
bool connectToHost(QString host);
bool writeData(QByteArray data);
private:
QTcpSocket *socket;
};
Client.cpp:
客户端.cpp:
#include "client.h"
static inline QByteArray IntToArray(qint32 source);
Client::Client(QObject *parent) : QObject(parent)
{
socket = new QTcpSocket(this);
}
bool Client::connectToHost(QString host)
{
socket->connectToHost(host, 1024);
return socket->waitForConnected();
}
bool Client::writeData(QByteArray data)
{
if(socket->state() == QAbstractSocket::ConnectedState)
{
socket->write(IntToArray(data.size())); //write size of data
socket->write(data); //write the data itself
return socket->waitForBytesWritten();
}
else
return false;
}
QByteArray IntToArray(qint32 source) //Use qint32 to ensure that the number have 4 bytes
{
//Avoid use of cast, this is the Qt way to serialize objects
QByteArray temp;
QDataStream data(&temp, QIODevice::ReadWrite);
data << source;
return temp;
}
Server.h:
服务器.h:
#include <QtCore>
#include <QtNetwork>
class Server : public QObject
{
Q_OBJECT
public:
explicit Server(QObject *parent = 0);
signals:
void dataReceived(QByteArray);
private slots:
void newConnection();
void disconnected();
void readyRead();
private:
QTcpServer *server;
QHash<QTcpSocket*, QByteArray*> buffers; //We need a buffer to store data until block has completely received
QHash<QTcpSocket*, qint32*> sizes; //We need to store the size to verify if a block has received completely
};
Server.cpp:
服务器.cpp:
#include "server.h"
static inline qint32 ArrayToInt(QByteArray source);
Server::Server(QObject *parent) : QObject(parent)
{
server = new QTcpServer(this);
connect(server, SIGNAL(newConnection()), SLOT(newConnection()));
qDebug() << "Listening:" << server->listen(QHostAddress::Any, 1024);
}
void Server::newConnection()
{
while (server->hasPendingConnections())
{
QTcpSocket *socket = server->nextPendingConnection();
connect(socket, SIGNAL(readyRead()), SLOT(readyRead()));
connect(socket, SIGNAL(disconnected()), SLOT(disconnected()));
QByteArray *buffer = new QByteArray();
qint32 *s = new qint32(0);
buffers.insert(socket, buffer);
sizes.insert(socket, s);
}
}
void Server::disconnected()
{
QTcpSocket *socket = static_cast<QTcpSocket*>(sender());
QByteArray *buffer = buffers.value(socket);
qint32 *s = sizes.value(socket);
socket->deleteLater();
delete buffer;
delete s;
}
void Server::readyRead()
{
QTcpSocket *socket = static_cast<QTcpSocket*>(sender());
QByteArray *buffer = buffers.value(socket);
qint32 *s = sizes.value(socket);
qint32 size = *s;
while (socket->bytesAvailable() > 0)
{
buffer->append(socket->readAll());
while ((size == 0 && buffer->size() >= 4) || (size > 0 && buffer->size() >= size)) //While can process data, process it
{
if (size == 0 && buffer->size() >= 4) //if size of data has received completely, then store it on our global variable
{
size = ArrayToInt(buffer->mid(0, 4));
*s = size;
buffer->remove(0, 4);
}
if (size > 0 && buffer->size() >= size) // If data has received completely, then emit our SIGNAL with the data
{
QByteArray data = buffer->mid(0, size);
buffer->remove(0, size);
size = 0;
*s = size;
emit dataReceived(data);
}
}
}
}
qint32 ArrayToInt(QByteArray source)
{
qint32 temp;
QDataStream data(&source, QIODevice::ReadWrite);
data >> temp;
return temp;
}
Note:Do not use this method to transfer large files because with this method the entire contents of the message is put inside the memory before sent and this causes a high memory usage. And because 32 bits signed INT has max value to 2,147,483,647, if your input data has a value higher than that in bytes it won't work. Take care.
注意:不要使用这种方法传输大文件,因为这种方法在发送之前将消息的全部内容放在内存中,这会导致内存使用率很高。并且因为 32 位有符号 INT 的最大值为2,147,483,647,如果您的输入数据的值高于以字节为单位的值,则它将不起作用。小心。
回答by epsilon
As you said, you need to wait that your header is entirely sent, before reading it, and then read the good number of bytes and emit a signal for data availability.
正如您所说,您需要等待您的标头完全发送,然后再读取它,然后读取足够数量的字节并发出数据可用性信号。
Here an example (untested) :
这是一个示例(未经测试):
//header file
class Peer {
//[...]
protected:
bool m_headerRead; //initialize to false
unsigned int m_size_of_data_to_read;
//[...]
};
//source file
void QPeer::sendData(QByteArray data)
{
int size = data.size();
_socket->write((const char*) &size, sizeof(int);
//use directly QIODevice::write(QByteArray)
_socket->write(data);
}
void QPeer::readData()
{
int bytes = _socket->bytesAvailable();
bool contains_enough_data = true;
while (contains_enough_data) {
if (! m_headerRead && _socket->bytesAvailable() >= sizeof(int)) {
//read header only and update m_size_of_data_to_read
m_headerRead = true;
} else if (m_headerRead && _socket->bytesAvailable >= m_size_of_data_to_read) {
//read data here
m_headerRead = false;
emit dataAvailable();
} else {
contains_enough_data = false; //wait that further data arrived
}
}
}