[C/C++/Winsock] Wie einen "disconnect" realisieren?

jbJOGI

Cadet 4th Year
Registriert
März 2004
Beiträge
105
hi,

ich schreibe mir im Moment ein Programm zur Steuerung meines Servers. Am Ende soll das Programm den Status des Server Anzeigen, diesen starten können und die Möglichkeit bieten einen "Poll" zu starten, ob der Server herunter gefahren werden kann, oder einer der Clients Enspruch erhebt.

Nun soll das Client-Tool ja möglichst immer laufen. Soll dann später mal als Tray-Icon umgesetzt werden. Vorher sollte aber der Rest schon gehen! :)

Ich habe mir eine Klasse geschrieben, die mit Hilfe der Winsock-Funktionalitäten ein einfaches "Interface" zum weiteren Programmieren bietet. Damit kann ich den Socket registrieren, Verbindung aufbauen, Daten senden und empfangen, plus ein paar für dieses Problem unwichtige Dinge mehr.

Mein Problem setzt nun an folgender Stelle an:
Das Client-Programm läuft auf meinem Rechner und verliert die Verbindung zum Server, ob nun durch ein shutdown des Servers oder Routerausfall oder sonstiges ist ja egal. Nun wird ein Thread gestartet, welcher alle paar Sekunden versucht die Verbindung wieder aufzubauen.
Die möchte ich gerne über ein erneutes connect(...) machen, da ich ja sonst nichts senden/empfangen kann. Ich bekomme dann aber den Fehlercode 10056 zurück (http://support.ipswitch.com/kb/WSK-19980714-EM10.htm).

Hier wird beschrieben, dass man auf einem Socket, der bereits verbunden war kein erneutes connect ausführen kann, bevor er nicht "disconnected" wurde.

--by calling connect() with a AF_INET NULL destination address: INADDR_ANY (0.0.0.0), and port 0--

ich hab das folgendermaßen versucht:

Code:
// disconnect
// um connect erneut aufrufen zu können muss erst ein "disconnect" gemacht werden
struct sockaddr_in emptyServer;
unsigned long ulEmptyTargetIP;
memset( &emptyServer, 0, sizeof (emptyServer));

if ( ( ulEmptyTargetIP = inet_addr( "0.0.0.0" )) != INADDR_NONE ) {
        memcpy( (char *) &emptyServer.sin_addr, &ulEmptyTargetIP, sizeof(ulEmptyTargetIP));
}

emptyServer.sin_family = AF_INET;
emptyServer.sin_port = htons( 0 );

connect( clientSocket, (struct sockaddr*)&emptyServer, sizeof(emptyServer) );
Eben wie ich die ursprüngliche Verbindung aufgebaut habe, aber mit den genannten Werten. Ich bekomme hier aber ebenfalls wieder den gleichen Fehler zurück.

Bei Google und in den Tutorials, die mir bisher halfen, konnte ich leider auch nichts dazu finden. aber irgendwie muss das doch gehen. Man kann doch nicht bei jedem Disconnect einen neue Socket auf machen.

Kann mir hier jemand helfen? Es wäre schade, wenn ich schon wieder so ewig an einem "kleinen Problem" hänge würde.
 
schon mal closesocket(socketname) probiert?
also
closesocket ausfürhren, neue socket erstellen mit socket restellen und dann mit connect aufmachen.
 
hi,
danke für die antwort.
mit closesocket() geht das schon, aber das ist nicht ganz das, was ich eigentlich suche.

es kann doch nicht sein, dass es nur einweg-sockets gibt? das wäre ja unnötig umständlich. gibt es keine andere lösung?
 
Code:
ULONG lValue = 1;

setsockopt( mSocket, SOL_SOCKET, SO_REUSEADDR, (const char *) &lValue, sizeof( int ) );

MfG

Arnd
 
danke für die antwort. hab mal in der msdn-lib nachgeschaut, da kann man ja einiges setzen.

allerdings funktioniert das dennoch nicht. vermutlich wende ich es falsch an. aber ist auch egal. ich habe das nun so implementiert, dass der socket einfach vorher geschlossen wird, bevor ein reconnect geschieht.

trotzdem danke.
 
Hallo jbJOGI,

das vorher schliessen ist nicht idiotensicher. Bei schnell aufeinanderfolgenden reconnects kann das dann trotzdem wieder schiefgehen. Zumindest habe ich das so in Erinnerung.
Das mit dem Socket schliessen habe ich früher auch mal so gemacht.

Bei mir funktioniert die Lösung mit setsockopt einwandfrei.

Poste doch mal die betreffenden Codezeilen komplett.

Das obige sin_port = htons(0) erscheint mir z.B. etwas suspekt.

MfG

Arnd
 
Zuletzt bearbeitet:
Ein Socket, auf dem einmal connect angewendet wurde, ist danach unbrauchbar. D.h. Socket ggf. schließen und mit der WinAPI Funktion socket einen neuen anfordern. Neue connects auf dem alten Socket werden nicht funktionieren.
Die setsockopt Sache ist für Server-Sockets gedacht.
 
ja, ich habe gestern beim nachschlagen der setsockopt auch in nem beispiel gesehen, dass die das für nen listen-server benutzten. bei mir hats ja dann auch nicht getan, was ich darauf geschoben hab. :)

den noch hier mal der code der betreffenden stellen:


hier der Code, der den ersten Socket öffnet. Wie gesagt ist das ganze in ne klasse verpackt und das hier eine der methoden. danach folgen noch mehtoden um den connect vorzubereiten und den tatsächlichen connect auszuführen.
hierzu muss ich noch sagen, dass das meiste aus nem tutorial kommt, mit dem ich eben das erste mal sockets programmiert habe und das hier ist das erste richtige programm, was dies auch verwendet. vorher gabs nur kleine testserver.
Code:
bool Socket::openSocket(int iPort){
	
	this->iPort	= iPort;

	SOCKET temp_sock;

        /* Initialisiere TCP für Windows ("winsock") */
        WORD wVersionRequested;
        WSADATA wsaData;
        wVersionRequested = MAKEWORD (1, 1);
        if (WSAStartup (wVersionRequested, &wsaData) != 0)
		if( bDEBUG )
			MessageBox(NULL, "Fehler beim Initialisieren von Winsock.", "Error", MB_OK | MB_ICONERROR);
        
        temp_sock = socket( AF_INET, SOCK_STREAM, 0 );

        if (temp_sock < 0){
		if( bDEBUG )
			MessageBox(NULL, "Fehler beim Anlegen eines Sockets", "Error", MB_OK | MB_ICONERROR);
		return false;
        }else{
        clientSocket = temp_sock;

		

		return true;
        }

}


und hier die methode zum erneuten aufbau einer verbindung zum server, nachdem die erste beendet wurde. problem ist bei dieser lösung nun, dass es alle paar sekunden (wird wohl bei 5 bleiben) ausgeführt wird. das heißt alle 5 sekunden wird der bestehende socket geschlossen, ein neuer geöffnet, ein connect ausgeführt und bei nem fehler das ganze in 5 sek nochmal wiederholt, bis eine verbindung steht.
Code:
bool Socket::reopenConnection(){

	char msg[RCVBUFSIZE] = {'\0'};

	// Socket schließen
	closesocket(clientSocket);
	
	// Socket wieder neu öffnen
	if( openSocket(iPort) ){


		/* Baue die Verbindung zum Server auf */
		if( connect( clientSocket, (struct sockaddr*)&server, sizeof(server) ) >= 0 ){
			// Verbindung geöffnet
			// Thread starten, der auf Eingangssignale horcht und Server regelmäßig pingt

			if( callThreadMonitorSocket() ){
				if( registerClient() )
					return true;
			}

		}

                // Fehler mit Code ausgeben
		if( bDEBUG ){
			itoa(WSAGetLastError(), msg, 10);
			strcat( msg, ": Fehler bei reconnect");
			MessageBox(NULL, msg, "Error", MB_OK | MB_ICONERROR);
		}
	
	}
	return false;
}
 
Hallo jbJOGI,

ein paar Sachen hast Du weggelassen. Z.B wie die Variable server gefüllt wird.
So kann das Programm nicht laufen.

Da du ja Threads erwähnst. Ich würde die ganze Sache komplett in einem Thread ablaufen lassen und auch den socket darin anlegen.
Der socket wird auf nonblocking gesetzt und versucht nach dem anlegen zu connecten. Da er nicht blockierend ist wird EWOULDBLOCK zurückkommen.

D.h. im folgenden wird dann in isWriteable() darauf gewartet das der socket connected. Schafft er dies nicht innerhalb des Timeouts fängt die Sache wieder von vorne an.
Lies Dir mal die Doku zum select() durch.

Der Vorteil dieses Ansatzes ist das der Code zum einen nicht blockiert und zum anderen keine CPU Last erzeugt, da die meiste Zeit im select() gewartet wird.

Im folgenden ein bisschen Code:

Verbindung aufbauen:
Code:
mSAddr.sin_family            = AF_INET;
mSAddr.sin_port              = htonl( lPortNummer );
mSAddr.sin_addr.s_addr = lAddr;

mSocket = socket( AF_INET, SOCK_STREAM, 0 );
if( mSocket != INVALID_SOCKET )
{
	ULONG lValue = 1;

	if( setsockopt( mSocket, SOL_SOCKET, SO_REUSEADDR, (const char *) &lValue, sizeof( int ) ) != 0 )
	{
		addWSAError( "setsockopt" );
	}
	if( ioctlsocket ( mSocket, FIONBIO, &lValue ) != 0 )
	{
		addWSAError( "ioctlsocket" );
	}

	lValue = 0;
	ULONG lState = 0;

	::connect( mSocket, (PSOCKADDR) &mSAddr, sizeof mSAddr );
	lState = WSAGetLastError(); 
	switch( lState )
	{
		case 0:
			if( ioctlsocket ( mSocket, FIONBIO, &lValue ) != 0 )
			{
				addWSAError( "ioctlsocket" );
			}
			mState   = eConnected;
			addWSAError( "Verbindung hergestellt" );
		break;
		case WSAEWOULDBLOCK:
			mState   = eConnecting;
			addWSAError( "Verbindung wird aufgebaut" );
		break;
		default:
			shutdownClient();
		break;
	}
}

Warten auf eine erfolgreiche Verbindung:

Code:
BOOL CPropSocket::isWriteable()
{
	BOOL lRet = FALSE;

	if( mSocket != INVALID_SOCKET )
	{
		LONG            lStat = 0;
		fd_set			lWrite;
		struct timeval	lTimeOut;

		lTimeOut.tv_sec  = 5;
		lTimeOut.tv_usec = 0;

		FD_ZERO( &lWrite );
		FD_SET( mSocket, &lWrite );

		lStat = select( 1, 0, &lWrite, 0, &lTimeOut );
		if( lStat > 0 )
		{
			lRet = TRUE;
		}
	}
	return lRet;
}

Code:
void CPropSocket::shutdownClient()
{
	if( mSocket != INVALID_SOCKET )
	{
		shutdown( mSocket, 2 );
		closesocket( mSocket );
		mSocket = INVALID_SOCKET;
		mState  = eIdle;
	}
	addWSAError( "Verbindung geschlossen." );
}

MfG

Arnd
 
Zuletzt bearbeitet:
ok, ich glaube ich verstehe deinen anstatz. und select selbst habe ich auch verstanden und benutze es auch schon recht oft im bisherigen programm, sonst fängt die cpu ja an zu dampfen! :)

was mir in deinem beispiel nicht ganz klar wird, ist folgendes:

die funktion isWriteable() wartet 5 sekunden darauf, dass der connect erfolrgreich ist, bzw. prüft ob der socket bereits schreibbar ist. aber das setzt ja vorraus, dass dein connect-call "parallel" zum aufruf von isWriteable() geschieht. und genau das versteh ich nicht ganz wo das bei dir geschieht.

ich habe mir auch überlegt diesen reconnect mit select zu machen, wusste aber nicht wie, denn ich kann ja auf einem socket kein schreibzugriff erwarten, der nicht connected ist/wird.

zwischen den aufrufen von reopenConnection() habe ich übrigens auch ne wartezeit drin, allerdings über Sleep, da sonst einfach die CPU last abrtig hoch wird.

vielleicht kannst du mir nochmal schnell erläutern, was du meintest. danke für die bisherigen antworten. :)
 
Der connect und der select Aufruf laufen nicht paralell ab, sondern sequentiell.
Das select vollendet den vom connect angefangenen Verbindungsaufbau.

Sequenz:

- connect(), da der socket nicht blockierend ist, kommt das connect sofort zurück mit EWOULDBLOCK

- warte auf fertigstellen der Verbindung mit Timeout im select
Siehe doku zum select, ist eine Verbindung connected wenn sie writeable ist.

Das ioctlsocket mit FIONBIO ist wichtig da der socket sonst blockierend ist und damit selber auf eine Verbindung wartet.
Da das Verbindungstimeout aber intern geregelt ist, ist es besser dies selber in die Hand zu nehmen. Auf diese Weise hat man selber unter Kontrolle wann was passiert.

Stell Dir nur mal vor das Programm soll beendet werden, während der Socket aber noch im connect hängt. Entweder hängt das Programm solange bis der connect austimed, oder man bricht ihn hart ab und verursacht damit Konsistenzprobleme im tcp/ip Stack. Wenn man das regelmässig macht ist der Zeitpunkt an dem man den Rechner neu starten muss nicht mehr weit.

Allgemeine Struktur des connect Threads:

Code:
void threadProc()
{

    mState = eIdle;
    while( !appIsClosing )
    {
        if( mState == eIdle )
        {
            switch( connectFunc() )
            {
                case 0:
                    mState = eConnected;
                break;
                case EWOULDBLOCK:
                    mState = eConnecting;
                    if( waitForConnectReady() )
                    {
                        mState = eConnected;
                    }
                break;
                default:
                    handleError();
                    mState = eIdle;
                break;
            }
        } else
        {
            if( readDataFromSocketUsingSelect() <= 0)
            {
                // Wenn Verbindung verloren/geschlossen wurde
                mState = eIdle;
            }
        }      
    }
}

Wichtig ist nur das THread die meiste Zeit in einem select Aufruf hängt, wegen der CPU Last. Sleeps gehen auch sind aber nicht besonders "System" freundlich. Besser ist da ein WaitForSingleObject.

Wenn über den socket Daten verschickt werden sollen, kann es Probleme geben wenn Du aus einem anderen Threadkontext Methoden aufrufst, bzw. Daten übergibst. Bzw. wenn Du Daten empfängst müssen diese ja auch an Deine Applikation übergeben werden und/oder in einer Listbox angezeigt werden.

In dem Fall kann man mit Events und WaitForSingleObject arbeiten und sich selber Nachrichten schicken.

MfG

Arnd
 
Zuletzt bearbeitet:
aha, nun verstehe ich wie du das meinst. habe das mit dem sofortigen return von connect nicht verstanden gehabt. :)

das sieht so echt gut aus, muss ich mal testen.

vielen dank!
 
Zurück
Oben