Состояние QTcpSocket всегда подключено, даже отключая Ethernet-провод
У меня есть QTcpSocket, и я читаю в цикле. Каждый раз, когда был прочитан полный пакет или произошла ошибка, я вручную проверяю состояние сокета внутри цикла с помощью:
while(true){
if(socket->state()==QAbstractSocket::ConnectedState){
qDebug()<<"Socket status: connected. Looking for packets...";
if(socket->waitForReadyRead(2000)){
//...
}
Когда я выполняю программу де, после подключения и запуска цикла, она всегда печатает qDebug()<<"Socket status: connected. Looking for packets..."
; а затем застревает на waitForReadyRead
пока некоторые данные не будут готовы для чтения.
Проблема в том, что разъединения не обнаруживаются. Если я отключаюсь от сети с опциями ОС, или даже если я отключаю провод Ethernet, он ведет себя так же: состояние сокета равно QAbstractSocket::ConnectedStat
Да, это продолжается, но, конечно, ничего не получая.
Я также пытался обнаружить разрыв соединения disconnected()
сигнал (после первого подключения) к функции повторного подключения:
// Detect disconnection in order to reconnect
connect(socket, SIGNAL(disconnected()), this, SLOT(reconnect()));
void MyClass::reconnect(){
qDebug()<<"Signal DISCONNECTED emitted. Now trying to reconnect";
panelGUI->mostrarValueOffline();
socket->close();
prepareSocket((Global::directionIPSerialServer).toLocal8Bit().data(), 8008, socket);
qDebug()<<"Reconnected? Status: "<<socket->state();
}
Но сигнал никогда не излучается, потому что этот код никогда не выполняется. Что логично, так как похоже, что состояние сокета всегда ConnectedState
,
Если я подключу снова, соединение восстановится и снова начнет получать данные, но я хочу обнаружить отключения, чтобы в графическом интерфейсе отображалось сообщение "Отключено".
Почему QTcpSocket ведет себя так и как я могу решить эту проблему?
РЕДАКТИРОВАТЬ: я создаю сокет в конструкторе класса, а затем инициализирую вызов функции prepareSocket:
socket = new QTcpSocket();
socket->moveToThread(this);
bool prepareSocket(QString address, int port, QTcpSocket *socket) {
socket->connectToHost(address, port);
if(!socket->waitForConnected(2000)){
qDebug()<<"Error creating socket: "<<socket->errorString();
sleep(1);
return false;
}
return true;
}
4 ответа
Наконец-то нашел решение на этом форуме Qt:
Если в течение определенного времени данные не обмениваются, TCP начнет отправлять сегменты подтверждения активности (в основном, сегменты ACK с номером подтверждения, установленным на текущий порядковый номер, меньший единицы). Затем другой узел отвечает другим подтверждением. Если это подтверждение не получено в течение определенного количества сегментов зонда, соединение автоматически прерывается. Небольшая проблема заключается в том, что ядро начинает посылать сегменты keep-alive через 2 часа после того, как соединение становится свободным! Поэтому вам нужно изменить это значение (если ваша ОС это позволяет) или внедрить свой собственный механизм keep-alive в протокол (как это делают многие протоколы, например, SSH). Linux позволяет изменить его с помощью setsockopt:
int enableKeepAlive = 1;
int fd = socket->socketDescriptor();
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &enableKeepAlive, sizeof(enableKeepAlive));
int maxIdle = 10; /* seconds */
setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &maxIdle, sizeof(maxIdle));
int count = 3; // send up to 3 keepalive packets out, then disconnect if no response
setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &count, sizeof(count));
int interval = 2; // send a keepalive packet out every 2 seconds (after the 5 second idle period)
setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
Я сталкивался с подобными проблемами с клиентским приложением QT. В основном я справляюсь с таймерами, сигналами и слотами. Когда приложение запускается, оно запускает 4-секундный checkConnectionTimer. Каждые 4 секунды таймер истекает, если состояние сокета клиента!= AbstractSocket::Connected или Connecting, он пытается соединиться с clientSocket->connectToHost
Когда сокет подает сигнал "connected()", запускается 5-секундный таймер пульса сервера. Сервер должен отправлять однобайтовое контрольное сообщение своим клиентам каждые 4 секунды. Когда я получаю сердцебиение (или сообщение любого типа, сигнализируемое readyRead()), я перезапускаю таймер сердцебиения. Так что, если таймер пульса когда-либо имеет тайм-аут, я предполагаю, что соединение не работает, и он вызывает clientSocket->disconnectFromHost ();
Это работает очень хорошо для всех видов разъединений на сервере, изящных или других (дергание кабеля). Да, это требует нестандартного типа пульса, но в конце концов это было самое быстрое и самое портативное решение.
Я не был заинтересован в установке тайм-аутов KEEPALIVE в ядре. Таким образом, его более портативный. В конструкторе:
connect(clientSocket, SIGNAL(readyRead()), this, SLOT(readMessage()));
connect(clientSocket, SIGNAL(connected()), this, SLOT(socketConnected()));
connect(clientSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
connect(heartbeatTimer, SIGNAL(timeout()), this, SLOT(serverTimeout()));
...
// Other Methods
void NetworkClient::checkConnection(){
if (clientSocket->state() != QAbstractSocket::ConnectedState &&
clientSocket->state() != QAbstractSocket::ConnectingState){
connectSocketToHost(clientSocket, hostAddress, port);
}
}
void NetworkClient::readMessage()
{
// Restart the timer by calling start.
heartbeatTimer->start(5000);
//Read the data from the socket
...
}
void NetworkClient::socketConnected (){
heartbeatTimer->start(5000);
}
void NetworkClient::socketDisconnected (){
prioResponseTimer->stop();
}
void NetworkClient::serverTimeout () {
clientSocket->disconnectFromHost();
}
Попробуйте подключение к этому сигнальному слоту:
connect(this, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this, SLOT(onStateChanged(QAbstractSocket::SocketState)));
при реализации слота:
void TCPWorker::onStateChanged(QAbstractSocket::SocketState socketState ){
qDebug()<< "|GSTCPWorkerThread::onStateChanged|"<<socketState;
...}
У меня та же проблема, но вместо вашей проблемы (всегда подключенной) у меня есть задержка 4-5 секунд для получения сигналов отключения, после отключения сетевого кабеля.
Все еще ищите решение, отправьте ответ, если найдете.
Попробуйте мой шаблон клиента в Qt:
class Client: public QTcpSocket {
Q_OBJECT
public:
Client(const QHostAddress&, int port, QObject* parent= 0);
~Client();
void Client::sendMessage(const QString& );
private slots:
void readyRead();
void connected();
public slots:
void doConnect();
};
на cpp:
void Client::readyRead() {
// if you need to read the answer of server..
while (this->canReadLine()) {
}
}
void Client::doConnect() {
this->connectToHost(ip_, port_);
qDebug() << " INFO : " << QDateTime::currentDateTime()
<< " : CONNESSIONE...";
}
void Client::connected() {
qDebug() << " INFO : " << QDateTime::currentDateTime() << " : CONNESSO a "
<< ip_ << " e PORTA " << port_;
//do stuff if you need
}
void Client::sendMessage(const QString& message) {
this->write(message.toUtf8());
this->write("\n"); //every message ends with a new line
}
я пропустил некоторый код в качестве соединений конструктора и слотов.. попробуйте с этим, и если это не работает, возможно, что-то не так на стороне сервера..