connect(), select() und SA_RESTART-flag bei Signalen
-
Zum Aufbau einer Socket-Verbindung zu einem Server (über TCP/IP) verwende ich z.Z. die folgende Member-Methode connectTo() (die ich so in versch. Forums-Beiträgen gesehen und geklaut habe):
int InetClientSocket::connectTo() { int iRet = -1; int flags; // set socket non-blocking if ((flags = fcntl(m_fdSocket, F_GETFL, 0)) < 0) return -1 flags |= O_NONBLOCK; if (fcntl(m_fdSocket, F_SETFL, flags) < 0) return -1; // try connect; it will set errno and abort instantly; then call "select()" if EINPROGRESS is set iRet = connect(m_fdSocket, (struct sockaddr*)&m_destAddr, sizeof(struct sockaddr_in)); if(iRet < 0) { // "errno" is set to EINPROGRESS if a listen-port has been found. connect() only returned "-1" because // the socket is non-blocking and so connect() has no time to build up the connection - so let's do it here! if(EINPROGRESS == errno) { struct timeval timeout = m_ConnectTimeout; fd_set wfds; FD_ZERO(&wfds); FD_SET(m_fdSocket, &wfds); // wait for connect() only a limited time iRet = select(m_fdSocket+1, NULL, &wfds, NULL, &timeout); // if( iRet > 0 ) if(FD_ISSET(m_fdSocket, &wfds)) iRet = 0; // connect successful else { // if !EINTR: select returned with error -> cancel! // if EINTR: Timeout in select() has been exceeded -> cancel! iRet = -1; } } } // else the connect() has been successful instantly (which is quite unlikely, but in this case it returned the correct return value iRet = 0) // set socket blocking again flags &= ~O_NONBLOCK; if (fcntl(m_fdSocket, F_SETFL, flags) < 0) iRet = -1; return iRet; }
Diese Funktion hat jetzt immer gut geklappt, bis ich die Notwendigkeit gesehen habe, für die Signale zur Programmbeendigung das SA_RESTART-Flag zu setzen, so dass die System Calls read() und write() durch das Eintreffen eines Signals nicht abrupt beendet werden. Allerdings liefert mir das connect() jetzt immer den errno == EINPROGRESS, und das FD_ISSET nach select() liefert immer "Erfolg" zurück...auch wenn am anderen Ende der Leitung kein Host ist. Warum zeigen die System Calls mit SA_RESTART dieses komische Verhalten, bzw. wie kann ich das Problem umgehen?
Ich muss auch gestehen dass ich die Funktion, die ich hier verwende, nicht ganz verstanden habe:
- Warum funktioniert das select() mit 'nem Timeout auf nicht-block. Sockets? Sollte das select() dann nicht immer sofort abbrechen einen "Fehler" zurückliefern (so wie das connect() vorher auch)? Ich persönlich hätte vermutet dass ich den Socket direkt nach dem connect()-Aufruf (und v.a. vor dem select()) wieder blockierend machen muss.
- Das connect() wird nur einmal aufgerufen und sofort abgebrochen. Wird es während des select()-Aufrufs trotzdem im Hintergrund vom Betriebssystem weitergeführt, so dass das select() nach gewisser Zeit einen funktionierenden Socket vorfinden kann?Wär' nett wenn mir jemand auf die Sprünge helfen könnte.
-
Um nochmal auf meinen eigenen Beitrag einzugehen:
Das SA_RESTART hat mir im Endeffekt zu viele abstruse Ergebnisse an den unterschiedlichsten Stellen in meinem Programm gebracht.
Ich habe dieses Flag jetzt wieder rausgenommen, um bei verschiedenen Socket-Syscalls selbst auf EINTR zu prüfen.Da aber offensichtlich der errno nicht auf 0 (Success) gesetzt wird wenn ein Syscall erfolgreich durchläuft, ergab sich das Problem dass bspw. das select() - wenn mein Init-Script zum Beenden des Programms das Signal SIGTERM sendet - trotz Select-Timeout in einer Endlos-Schleife verharrt, bis mein Init-Script einen eigenen Timeout erreicht und SIGKILL sendet.
Ich muss also erkennen wann der Syscall wirklich fertig geworden ist und dann die Schleife ggf. verlassen.
Dazu wollte ich erst eine Variable, die den Return-Wert nimmt, auf -2 setzen und im "Schleifenfuß" auf "var != -2" prüfen, musste aber feststellen dass Syscalls (oder zumindest select() ) auch dann einen Return-Wert zurückliefern wenn sie unterbrochen werden.
Deswegen setze ich den errno jetzt immer am Beginn der Schleife manuell auf 0:
int iRet; do { errno = ESUCCESS; iRet = connect(m_fdSocket, (struct sockaddr*)&m_destAddr, sizeof(struct sockaddr_in)) ; } while( EINTR == errno ); /* ... */ do { errno = ESUCCESS; iRet = select(m_fdSocket+1, NULL, &wfds, NULL, &timeout); } while( EINTR == errno );
Das scheint mir bisher genau das Verhalten zu liefern das ich mir von meinem Programm erwartet hatte. Und damit wäre das Problem meines Threads erstmal gelöst.