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
でスタックします。
問題は、切断が検出されないことです。 OSオプションからネットワークから切断した場合、またはイーサネットワイヤーを取り外した場合でも、動作は同じです。ソケットの状態は_QAbstractSocket::ConnectedStat
_ eと等しいため、続行されますが、当然何も受信されません。
また、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;
}
_
最後にこれで解決策が見つかりました 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));
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();
}
この信号スロット接続を試してください:
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
}
コンストラクタとスロット接続として一部のコードを省略しました。これを試してみて、それが機能しない場合は、サーバー側に問題がある可能性があります。