2012-05-04 13 views
24

Mam QTcpSocket i czytam w pętli. Za każdym razem pełny pakiet został przeczytany, czy nastąpił błąd, ręcznie sprawdzić stan gniazda wewnątrz pętli, z:QTcpSocket stan zawsze podłączony, nawet odłączenie przewodu ethernetowego

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

Kiedy wykonać de program, po podłączeniu i pętla zaczyna, zawsze drukuje qDebug()<<"Socket status: connected. Looking for packets..."; a następnie utknąć w waitForReadyRead, aż niektóre dane będą gotowe do odczytania.

Problem polega na tym, że rozłączenia nie są wykrywane. Jeśli odłączę się od sieci od opcji systemu operacyjnego, lub nawet jeśli odłączyłem przewód ethernetowy, zachowuje się on tak samo: Stan gniazda jest równy QAbstractSocket::ConnectedStat e, więc trwa, ale bez otrzymywania niczego oczywiście.

Próbowałem też wykryć rozłączenia łączące disconnected() sygnał (po podłączeniu pięści) do funkcji Ponowne łączenie:

// 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(); 
} 

Ale sygnał nigdy nie jest emitowane, ponieważ kod ten nie jest wykonywany. Co jest logiczne, ponieważ wygląda na to, że stan gniazda jest zawsze ConnectedState.

Jeśli ponownie podłączę, połączenie zostanie przywrócone i zacznie otrzymywać dane ponownie, ale chcę wykryć rozłączenia, aby pokazać "Rozłączony" w GUI.

Dlaczego QTcpSocket zachowuje się w ten sposób i jak mogę rozwiązać ten problem?

EDIT: Tworzę gniazdo w konstruktorze klasy, a następnie inicjalizacji wywołanie funkcji 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; 
} 
+2

Jak długo czekałeś z odłączonym kablem? – Mat

+0

Nie wiem ... 10 sekund? Znacznie mniej było konieczne dla gniazd POSIX, aby ustalić, że zostały rozłączone. Ile powinienem czekać? –

+0

Limity czasu TCP są o wiele wyższe. Poczekaj przynajmniej kilka minut. – Mat

Odpowiedz

20

Wreszcie znalazł rozwiązanie w tym Qt forum:

Jeśli dane nie są wymieniane przez pewien czas, TCP będzie rozpocznij wysyłanie segmentów utrzymania aktywności (zasadniczo segmenty ACK z numerem potwierdzenia ustawionym na bieżący numer kolejny, mniejszy). Drugi rówieśnik następnie odpowiada innym potwierdzeniem. Jeśli to potwierdzenie nie zostanie odebrane w pewnej liczbie segmentów sondy, połączenie zostanie automatycznie upuszczone. Mały problem polega na tym, że jądro rozpoczyna wysyłanie segmentów podtrzymujących aktywność po 2 godzinach, od kiedy połączenie staje się bezczynne! Dlatego musisz zmienić tę wartość (jeśli pozwala na to Twój OS ) lub wdrożyć własny mechanizm utrzymywania aktywności w twoim protokole (podobnie jak w przypadku wielu protokołów, np. SSH).Linux pozwala zmianie go przy użyciu 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)); 
+4

Dla każdego, kto próbuje tego: '#include #include #include ' – kwahn

+1

Czy ten kod jest tworzony zaraz po utworzeniu QTcpSocket? I czy musisz wywołać inną funkcję, aby ustawić te opcje na gnieździe Qt (lub od razu działają na gniazdo Qt). – TSG

0

wypróbować mój szablon klienta w 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(); 
}; 

na 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 
} 

Pominąłem niektóre kod jako konstruktor i gniazda połączeń .. próby z tym i jeśli nie robi t pracę może coś jest nie tak po stronie serwera ..

+0

Jestem pewien, że to działa, to mniej więcej to, co mam. Ale połączenie już działa, chcę wykryć rozłączenie, a to nie dzieje się w twoim szablonie.Poza tym, po stronie serwera jest OK, ponieważ działało z gniazdami POSIX –

+0

czy używasz 'void QAbstractSocket :: disconnectFromHost()' gdzieś? –

10

Byłem skierowaną podobne problemy z aplikacji klienckiej QT. Zasadniczo obsługuję to za pomocą timerów, sygnałów i gniazd. Po uruchomieniu aplikacji rozpoczyna się 4-sekundowy test CheckConnectionTimer. Co 4 sekundy, gdy upłynie czas timera, jeśli stan gniazda klienta! = AbstractSocket :: Connected or Connecting, spróbuje połączyć się z clientSocket-> connectToHost

Gdy gniazdo sygnalizuje "connected()", uruchamia 5-sekundowy serwer minutnik pulsu. Serwer powinien wysyłać jednobajtowy komunikat pulsu do swoich klientów co 4 sekundy. Kiedy dostanę puls (lub jakikolwiek typ wiadomości sygnalizowany przez readyRead()), zrestartuję timer taktowania. Tak więc, jeśli licznik pulsu ma kiedykolwiek limit czasu, zakładam, że połączenie jest wyłączone i wywołuje ono clientSocket->disconnectFromHost();

Działa to bardzo dobrze dla wszystkich rodzajów rozłączeń na serwerze, z wdziękiem lub w inny sposób (szarpiąc kabel). Tak, to wymaga niestandardowych typów pulsu, ale na koniec dnia było to najszybsze i najbardziej przenośne rozwiązanie.

Nie chciałem ustawiać limitów czasu KEEPALIVE w jądrze. W ten sposób jest bardziej przenośny. W konstruktorze:

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(); 
} 
+0

Próbowałem wszystkich odpowiedzi na stronie. To jest jedyny, który pracował dla mnie. – kwahn

+0

co, jeśli sieć jest przeciążona, a pakiety (bicie serca) zostały opóźnione o 2 lub 3 sekundy, a następnie nadejdzie? – Xsmael

1

spróbować tego połączenia gniazda sygnału:

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

w realizacji gniazd:

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

Mam ten sam problem, ale zamiast problem (zawsze podłączony), mam opóźnienie 4-5 sekund, aby odbierać sygnały rozłączenia, po odłączeniu kabla ethernetowego.

Nadal szukam rozwiązania, post odpowiedź, jeśli znajdziesz.

+3

Dla innych czytających to nie działa. Funkcja onStateChanged() nie jest wywoływana, gdy przewód jest odłączony. – kwahn