25

QTcpSocketがあり、ループを読み込んでいます。完全なパケットが読み取られるたび、またはエラーが発生するたびに、ループ内のソケットのステータスを手動で確認します。

    while(true){
    if(socket->state()==QAbstractSocket::ConnectedState){
        qDebug()<<"Socket status: connected. Looking for packets...";
        if(socket->waitForReadyRead(2000)){
        //...
    }

de programを実行すると、接続されてループが開始されると、常にqDebug()<<"Socket status: connected. Looking for packets...";が出力されます。そして、waitForReadyReadいくつかのデータを読み取る準備ができるまで立ち往生します。

問題は、切断が検出されないことです。OSオプションからネットワークを切断した場合、またはイーサネットワイヤを抜いた場合でも、同じように動作します。ソケットの状態はQAbstractSocket::ConnectedStateに等しいため、続行されますが、もちろん何も受信しません。

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です。

再度接続すると、接続が復元され、データの受信が再開されますが、切断を検出してGUIに「切断済み」と表示したいのですが。

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

4 に答える 4

22

最後に、このQtフォーラムで解決策を見つけました:

しばらくの間データが交換されない場合、TCPはキープアライブセグメントの送信を開始します(基本的に、確認応答番号が現在のシーケンス番号から1を引いた値に設定されたACKセグメント)。次に、他のピアは別の確認応答で応答します。この確認応答が特定の数のプローブセグメント内で受信されない場合、接続は自動的に切断されます。小さな問題は、接続がアイドル状態になってから2時間後に、カーネルがキープアライブセグメントの送信を開始することです。したがって、この値を変更するか(OSで許可されている場合)、プロトコルに独自のキープアライブメカニズムを実装する必要があります(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));
于 2012-05-04T11:02:46.737 に答える
12

QTクライアントアプリで同様の問題に直面しています。基本的に私はタイマー、信号、スロットでそれを処理します。アプリが起動すると、4秒のcheckConnectionTimerが開始されます。4秒ごとにタイマーが期限切れになり、クライアントソケットの状態が!= AbstractSocket :: ConnectedまたはConnectingの場合、clientSocket->connectToHostとの接続を試みます。

ソケットが「connected()」を通知すると、5秒のサーバーハートビートタイマーが開始されます。サーバーは、4秒ごとに1バイトのハートビートメッセージをクライアントに送信する必要があります。ハートビート(または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();
}
于 2012-09-13T18:48:33.153 に答える
1

この信号スロット接続を試してください:

connect(this, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onStateChanged(QAbstractSocket::SocketState)));

スロット実装時:

void TCPWorker::onStateChanged(QAbstractSocket::SocketState socketState ){
qDebug()<< "|GSTCPWorkerThread::onStateChanged|"<<socketState;
...}

私は同じ問題を抱えていますが、代わりにあなたの問題(常に接続されています)、イーサネットワイヤを抜いた後、切断信号を受信するのに4〜5秒の遅延があります。

まだ解決策を探しています。見つかった場合は回答を投稿してください。

于 2013-07-29T11:30:26.157 に答える
0

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
}

コンストラクターとスロット接続としていくつかのコードを省略しました。これを試してみてください。うまくいかない場合は、サーバー側に問題がある可能性があります。

于 2012-05-04T09:12:14.870 に答える