诡异的 QTcpSocket 行为
Spooky QTcpSocket behaviour
我在调试接下来的几行代码时一直在做噩梦,一定有什么东西隐藏在我所看到的之外。
这是客户端和服务器之间的连接
QByteArray Client::request(QByteArray cmd)
{
//DEBUG << "Command: " << cmd.toStdString().c_str();
QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost(hostAddress, port, QIODevice::ReadWrite);
if (socket->waitForConnected(TIMEOUT))
{
socket->write(cmd);
DEBUG << "Size of bytes written :" << sizeof (cmd);
}
else DEBUG << "Couldn't connect to socket";
if (socket->waitForBytesWritten(TIMEOUT)) DEBUG << "Command sent";
else DEBUG << "Couldn't write to socket";
if (socket->waitForReadyRead(TIMEOUT))
{
QByteArray data = socket->readAll();
socket->close();
return data;
}
else DEBUG << "No reply from Server";
return QByteArray();
}
对于客户端来说基本上就是这样,对于服务器来说,这里是重要的片段。
class ConnectionHandler : public QTcpServer
{
Q_OBJECT
public:
explicit ConnectionHandler(QObject *parent = 0);
void write(QByteArray);
protected:
void ConnectionHandler::incomingConnection(qintptr descriptor)
{
DEBUG << "ConnectionHandler:" << "Incoming Connection :" << descriptor;
ConnectionThread *thread = new ConnectionThread(this, descriptor);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
connect(thread, &ConnectionThread::signalIncomingMessage, this, &ConnectionHandler::slotIncomingMessage);
connect(this, &ConnectionHandler::signalOutgoingMessage, thread, &ConnectionThread::slotOutgoingMessage);
thread->start();
}
public slots:
void slotIncomingMessage(QByteArray);
void slotListen(bool checked){
if (checked)
{
if (!this->listen(QHostAddress::LocalHost, PORT_NUMBER)) {
DEBUG << "ConnectionHandler:" << "Could not start the server!";
} else {
DEBUG << "ConnectionHandler:" << "Listening...";
}
} else {
this->close();
DEBUG << "ConnectionHandler:" << "Connection Closed!";
}
}
signals:
void signalOutgoingMessage(QByteArray);
};
class ConnectionThread : public QThread
{
Q_OBJECT
public:
ConnectionThread(QObject* parent = 0, qintptr descriptor = -1){
if (descriptor != -1)
{
socket = new QTcpSocket();
DEBUG << "ConnectionThread: Connecting to socket number" << descriptor;
if (!socket->setSocketDescriptor(descriptor))
{
DEBUG << "ConnectionThread: Connection failed.";
DEBUG << socket->errorString();
}
else
{
DEBUG << "ConnectionThread: Connected Successfully.";
connect(socket, &QAbstractSocket::disconnected, this, &ConnectionThread::slotSocketDisconnected);
}
}
else {
DEBUG << "ConnectionThread: Please provide a descriptor for the connection thread";
}
}
void run() override{
if (socket->state() != QAbstractSocket::ConnectedState)
{
DEBUG << "ConnectionThread: Socket is not connected!";
DEBUG << "ConnectionThread: Closing Thread!";
emit signalThreadError(socket->errorString());
}
else
{
DEBUG << "ConnectionThread: Socket is Connected.";
connect(socket, &QIODevice::readyRead, this, &ConnectionThread::slotThreadReadyRead, Qt::DirectConnection);
exec();
}
}
signals:
void signalThreadError(QString);
void signalIncomingMessage(QByteArray);
public slots:
void slotThreadReadyRead(){
QByteArray msg = socket->readAll();
if (!msg.isEmpty()) {
emit signalIncomingMessage(msg);
}
DEBUG << "ConnectionThread: Data in:" << msg;
}
void slotSocketDisconnected();
void slotOutgoingMessage(QByteArray msg)
{
if (socket != nullptr) {
socket->write(msg);
if (socket->waitForBytesWritten(TIMEOUT)) {
DEBUG << "ConnectionThread: Outgoing Message: " << msg;
} else {
DEBUG << "ConnectionThread: Outgoing Message Timeout";
}
}
}
private:
QTcpSocket *socket = nullptr;
};
基本上会发生什么,它有时会收到来自客户端的传入命令,而大多数时候却不会,不仅如此,它实际上会收到一些命令并忽略一些不同的命令。
这是一个更诡异的行为,请注意 Client::request()
中的 //DEBUG << "Command: " << cmd.toStdString().c_str();
行,当此行被注释掉时,服务器实际上会收到一些命令,否则,服务器就会失聪。
注释掉该行后的输出:
Size of bytes written : 8
Command sent
target num = 4 (a reply was received)
Size of bytes written : 8
Command sent
No reply from Server
当提到的行未被注释掉时,这是另一个输出:
Command: NUMB
Size of bytes written : 8
Command sent
No reply from Server
target num = 0
Command: TRAN
Size of bytes written : 8
Command sent
No reply from Server
更新
解决客户端套接字写入和线程打开之间发生的竞争为我解决了问题,在我这里的情况下不需要使用线程,所以我就放弃了它。 @Botje回答
问题是由竞争条件引起的:
- (主线程)ConnectionHandler::incomingConnection 创建一个新的 ConnectionThread
- (主线程)ConnectionThread构造函数创建一个socket,也属于主线程
- (主线程)returns 到事件循环,轮询套接字上的传入数据
- (ConnectionThread) 运行 方法将槽连接到 readyRead 信号
如果数据到达 2 和 4 之间的 window,readyRead 信号将不会拾取它。
有几种可能的解决方案,但最简单的可能是在构建之后将套接字移动到 ConnectionThread,或者将其构建延迟到 ConnectionThread::run 方法。
我在调试接下来的几行代码时一直在做噩梦,一定有什么东西隐藏在我所看到的之外。
这是客户端和服务器之间的连接
QByteArray Client::request(QByteArray cmd)
{
//DEBUG << "Command: " << cmd.toStdString().c_str();
QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost(hostAddress, port, QIODevice::ReadWrite);
if (socket->waitForConnected(TIMEOUT))
{
socket->write(cmd);
DEBUG << "Size of bytes written :" << sizeof (cmd);
}
else DEBUG << "Couldn't connect to socket";
if (socket->waitForBytesWritten(TIMEOUT)) DEBUG << "Command sent";
else DEBUG << "Couldn't write to socket";
if (socket->waitForReadyRead(TIMEOUT))
{
QByteArray data = socket->readAll();
socket->close();
return data;
}
else DEBUG << "No reply from Server";
return QByteArray();
}
对于客户端来说基本上就是这样,对于服务器来说,这里是重要的片段。
class ConnectionHandler : public QTcpServer
{
Q_OBJECT
public:
explicit ConnectionHandler(QObject *parent = 0);
void write(QByteArray);
protected:
void ConnectionHandler::incomingConnection(qintptr descriptor)
{
DEBUG << "ConnectionHandler:" << "Incoming Connection :" << descriptor;
ConnectionThread *thread = new ConnectionThread(this, descriptor);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
connect(thread, &ConnectionThread::signalIncomingMessage, this, &ConnectionHandler::slotIncomingMessage);
connect(this, &ConnectionHandler::signalOutgoingMessage, thread, &ConnectionThread::slotOutgoingMessage);
thread->start();
}
public slots:
void slotIncomingMessage(QByteArray);
void slotListen(bool checked){
if (checked)
{
if (!this->listen(QHostAddress::LocalHost, PORT_NUMBER)) {
DEBUG << "ConnectionHandler:" << "Could not start the server!";
} else {
DEBUG << "ConnectionHandler:" << "Listening...";
}
} else {
this->close();
DEBUG << "ConnectionHandler:" << "Connection Closed!";
}
}
signals:
void signalOutgoingMessage(QByteArray);
};
class ConnectionThread : public QThread
{
Q_OBJECT
public:
ConnectionThread(QObject* parent = 0, qintptr descriptor = -1){
if (descriptor != -1)
{
socket = new QTcpSocket();
DEBUG << "ConnectionThread: Connecting to socket number" << descriptor;
if (!socket->setSocketDescriptor(descriptor))
{
DEBUG << "ConnectionThread: Connection failed.";
DEBUG << socket->errorString();
}
else
{
DEBUG << "ConnectionThread: Connected Successfully.";
connect(socket, &QAbstractSocket::disconnected, this, &ConnectionThread::slotSocketDisconnected);
}
}
else {
DEBUG << "ConnectionThread: Please provide a descriptor for the connection thread";
}
}
void run() override{
if (socket->state() != QAbstractSocket::ConnectedState)
{
DEBUG << "ConnectionThread: Socket is not connected!";
DEBUG << "ConnectionThread: Closing Thread!";
emit signalThreadError(socket->errorString());
}
else
{
DEBUG << "ConnectionThread: Socket is Connected.";
connect(socket, &QIODevice::readyRead, this, &ConnectionThread::slotThreadReadyRead, Qt::DirectConnection);
exec();
}
}
signals:
void signalThreadError(QString);
void signalIncomingMessage(QByteArray);
public slots:
void slotThreadReadyRead(){
QByteArray msg = socket->readAll();
if (!msg.isEmpty()) {
emit signalIncomingMessage(msg);
}
DEBUG << "ConnectionThread: Data in:" << msg;
}
void slotSocketDisconnected();
void slotOutgoingMessage(QByteArray msg)
{
if (socket != nullptr) {
socket->write(msg);
if (socket->waitForBytesWritten(TIMEOUT)) {
DEBUG << "ConnectionThread: Outgoing Message: " << msg;
} else {
DEBUG << "ConnectionThread: Outgoing Message Timeout";
}
}
}
private:
QTcpSocket *socket = nullptr;
};
基本上会发生什么,它有时会收到来自客户端的传入命令,而大多数时候却不会,不仅如此,它实际上会收到一些命令并忽略一些不同的命令。
这是一个更诡异的行为,请注意 Client::request()
中的 //DEBUG << "Command: " << cmd.toStdString().c_str();
行,当此行被注释掉时,服务器实际上会收到一些命令,否则,服务器就会失聪。
注释掉该行后的输出:
Size of bytes written : 8
Command sent
target num = 4 (a reply was received)
Size of bytes written : 8
Command sent
No reply from Server
当提到的行未被注释掉时,这是另一个输出:
Command: NUMB
Size of bytes written : 8
Command sent
No reply from Server
target num = 0
Command: TRAN
Size of bytes written : 8
Command sent
No reply from Server
更新 解决客户端套接字写入和线程打开之间发生的竞争为我解决了问题,在我这里的情况下不需要使用线程,所以我就放弃了它。 @Botje回答
问题是由竞争条件引起的:
- (主线程)ConnectionHandler::incomingConnection 创建一个新的 ConnectionThread
- (主线程)ConnectionThread构造函数创建一个socket,也属于主线程
- (主线程)returns 到事件循环,轮询套接字上的传入数据
- (ConnectionThread) 运行 方法将槽连接到 readyRead 信号
如果数据到达 2 和 4 之间的 window,readyRead 信号将不会拾取它。 有几种可能的解决方案,但最简单的可能是在构建之后将套接字移动到 ConnectionThread,或者将其构建延迟到 ConnectionThread::run 方法。