Frage zu Socket-Programmierung und select()



  • Hallo
    Ich habe ein Problem. Es geht um folgendes:
    Ich soll im Rahmen meines Studiums eine Client-Server-Anwendung in C/C++ schreiben. Es soll möglich sein mehrere Clients gleichzeitig mittels select() zu bedienen.
    Mein Problem ist nun, dass ich einen Client connecte. Von diesem schicke ich einen Text an den Server und der wiederum an den Client als Bestätigung. Meine Server-Schleife wird dabei 2x durchlaufen. Der Puffer für die empfangene Nachricht vom Client enthält auch nach dem leeren des Puffers die geschickte Nachricht. Beim 3. Durchlauf allerdings kommt es zu einem Fehler aber ich weiß nicht warum.
    Wenn einer eine Ahnung hat woran es liegt wäre mir sehr geholfen.
    Danke schonmal

    // RN_P_Beispielserver.cpp : Definiert den Einstiegspunkt für die Konsolenanwendung.
    //
    #include "stdafx.h"
    #include <stdio.h>
    //#include <winsock.h>
    #include <stdlib.h>
    #include <io.h>
    #include <conio.h>
    
    #ifdef _WIN32
    #include <winsock.h>
    #include <io.h>
    #endif
    
    #define PROTOPORT		5000    // Default Portnummer
    #define QLEN			6       // Länge der Backlog-Warteschlange
    #define AF_INET_LEN		4		// Länge der IPv4-Adresse in Byte
    #define RECVBUF_SIZE	100	// Größe des Empfangspuffers
    #define SENDBUF_SIZE	100	// Größe des Sendepuffers
    #define MAXCLIENT		5
    
    int main(int argc, char* argv[])
    {
    //---------------------------------------------------------------------------------
    // Program:   RN_P_Beispielserver.cpp
    //
    // Funktion:  Erzeugt einen Socket und führt die folgenden Aktionen zyklisch aus:
    //            (1) Wartet auf eingehende Verbindungswünsche von Clients
    //            (2) Führe passiven TCP-Verbindungsaufbau durch
    //	          (3) Sende eine Nachricht an den Client
    //            (4) Schließe die TCP-Verbindung
    //            (5) Gehe zurück zu Schritt (1)
    //			   
    // Aufruf:    RN_P_Beispielserver [<Portnummer>]
    //            <Portnummer>: Portnummer unter welcher der Server seinen 
    //							Dienst anbietet		
    //
    // Bemerkung: Die Eingabe der Portnummer ist optional. Falls kein Port spezifiziert
    //			  ist, wird der durch PROTOPORT definierte Default-Wert verwendet!    
    //
    // Dr. W. Schmitt, 06.05.08
    //---------------------------------------------------------------------------------
    
    	const char *Datum = "06.05.08";
        int		Fehlerkode;		// Hilfsvariable zum Speichern eines Fehlerkodes
    	protoent *ptrp;			// Speichert den Namen des Transp.Protok.--> winsock.h
    	sockaddr_in serveradress;		// Speichert die Serveradresse --> winsock.h
        sockaddr_in clientadress;		// Speichert die Clientadresse --> winsock.h
        SOCKET  fd,		// Socketdeskriptor --> Annahme ank. Verbindungen
    			sd_client;      // Socketdeskriptor --> Aktive Clientverbindungen
        unsigned short	port;	// Portnummer des Servers
        int     struct_size;    // Größe einer Datenstruktur
        int		string_len;		// Länge einer Zeichenkette
    	int	n;				// Anzahl empfangener Bytes
    	char	RecvBuf[RECVBUF_SIZE];	// Puffer für Empfangsdaten
    	char    SendBuf[SENDBUF_SIZE];  // Puffer für Sendedaten
    	//int     visits = 0;		// Zählt die Anzahl erfolgreicher Clientverbindungen
    	WSADATA	wsaData;		// Speichert Winsock-Initialisierungsinfo
        WORD wVersionRequested;	// Speichert die gewünschte Version der Winsock-DLL
    
    	// Variablen für Select()
    	int clcnt = 0;		// Aktuelle Anzahl von Verbindungen
    	SOCKET	clients[MAXCLIENT];		// Socketdescriptor: Client-Server Datenaustausch
    	fd_set ReadSet;			// Enthält Sockets deren Lesestatus überwacht
    							// werden soll
    	long rc;
    	TIMEVAL time;
    
    	int i;
    
    	wVersionRequested = MAKEWORD(2, 2);
    	// Initialisierung der Winsock-DLL	
    	Fehlerkode = WSAStartup(wVersionRequested, &wsaData);
    	if (Fehlerkode != NO_ERROR)
    	{
    		fprintf_s(stdout,"Initialisierung der Winsock-DLL fehlgeschlagen: %i\n",
    					Fehlerkode);
    	} 
    
    	//  Bildschirmausgabe einer Begrüßungsnachricht     	
    	fprintf_s(stdout, "Server gestartet\nWarte auf Clients\n");
    
    	memset(&serveradress, 0, sizeof(serveradress));       // Initialisiere sad mit 0
    	serveradress.sin_family = AF_INET;           // Setze "Internetadressierung"
        serveradress.sin_addr.s_addr = INADDR_ANY;   //
    
    	// Auswerten der Kommandozeilenparameter
    
    	// Falls <Portnummer> vorhanden
    	if (argc > 1) 
    	{	
    		// dann konvertiere nach int				
    		port = atoi(argv[1]);
    	}		
        else 
    	{
    		// sonst verwende Default-Wert
    		port = PROTOPORT;
    	}			
    
        if (port > 0)
    	{
            serveradress.sin_port = htons(port); // Host --> Netzwerk-Byteordnung
    	}
        else 
    	{                          // Drucke Fehlermeldung; Programmende
             fprintf(stdout,"Ungültige Portnummer %s\n",argv[1]);
    		 WSACleanup();
    		 exit(EXIT_FAILURE);
    	}
    
        if ((ptrp = getprotobyname("tcp")) == NULL)
    	{ // "tcp" --> Protokollcode
            fprintf(stdout, "Abbildung von \"tcp\" auf Protokollnummer nicht möglich!");
    		WSACleanup();
    		exit(EXIT_FAILURE);
    	}
    
    	// Erzeuge Socket
        fd = socket(PF_INET, SOCK_STREAM, ptrp->p_proto);
        if (fd == INVALID_SOCKET)
    	{
    		fprintf_s(stdout, "Fehler bei Generierung von Socket: %ld\n", WSAGetLastError());
    		WSACleanup();
    		exit(EXIT_FAILURE);
    	}
    
    	// Initialisiere Socket mit (Rechneradress, Portnummer) des Servers
        if (bind(fd, (struct sockaddr*)&serveradress, sizeof(serveradress)) == SOCKET_ERROR)
    	{
            fprintf(stdout,"bind-Fehler: %ld\n", WSAGetLastError());
    		WSACleanup();
    		exit(EXIT_FAILURE);
    	}
    
    	// Lege Länge der Backlog-Warteschlange fest
    	if (listen(fd, QLEN) == SOCKET_ERROR)
    	{
            fprintf(stdout, "listen-Fehler: %ld\n", WSAGetLastError());
    		WSACleanup();
    		exit(EXIT_FAILURE);
    	}
    
    	for( i = 0 ; i < MAXCLIENT; i++ )
    
    	{
    		clients[i] = INVALID_SOCKET;
    	}
    
    	// ReadSet mit NULL initialisieren:
    	FD_ZERO(&ReadSet);
    	//Hauptschleife MIT Select()
    
    	while(1) 
    	{
    		time.tv_sec = 2;            // Abfragedauer für select in Sekunden
    		time.tv_usec = 0;           // Abfragedauer für select in Mikrosekunden
    
    		// Socket-Deskriptoren in ReadSet einfügen:
    		FD_SET(fd, &ReadSet);
    
    		// alle gültigen Sockets in ReadSet aufnehmen
    		for ( i = 0; i < MAXCLIENT; i++ ) 
    		{
    			if( clients[i] != INVALID_SOCKET )
    			{
    				FD_SET(clients[i], &ReadSet);
    			}
    		}
    		rc = select( 0, &ReadSet, NULL, NULL, &time );
    		if( rc == SOCKET_ERROR )
    		{
    			fprintf( stdout, "Fehler: select, fehler code: %s\n ", WSAGetLastError() );
    			return 1;
    		}
    		if (FD_ISSET(fd, &ReadSet)) 
    		{
    			//freien Platz für neuen Client suchen und
    			//die Verbindung annehmen
    			for( i = 0; i < MAXCLIENT; i++ )
    			{
    				if( clients[i] == INVALID_SOCKET )
    				{
    					//Falls Client nicht akzeptiert wird, gib einen Fehler aus und breche ab
    					clients[i] = accept( fd, NULL, NULL );
    					//clients[i] = accept( fd, (struct sockaddr*)&clientadress, &struct_size );
    
    					//Meldung am Server ausgeben, wenn Client connected
    					fprintf( stdout, "Client %s connected\n", i );
    
    					//Meldung am Client ausgeben, wenn ein Client connected
    					sprintf_s( SendBuf, "Client %s hat connected\n" ,i);
    					break;
    				}
    			}
    		}
    		//prüfen welche ClientSockets in fd_set sind
    		for( i = 0; i < MAXCLIENT; i++ ) 
    		{
    			if( clients[i] == INVALID_SOCKET )
    			{
    				continue;
    				fprintf( stdout, "Fehler: select, fehler code: %s\n ", WSAGetLastError() );
    			}
    			if( FD_ISSET( clients[i], &ReadSet ) )
    			{
    				n = recv( clients[i], RecvBuf, RECVBUF_SIZE, 0 );
    				/*
    					Eingabe im Client von hallo
    					Im 1. Durchgang ist dann n=5
    					Im 2. Durchgang ist n immer noch 5
    					Nach dem 2. Durchlauf wird n -1!!!
    					Keine Ahnung warum :(
    				*/
    				//wurde die Verbindung des Clients geschlossen?
    				if( n == 0  || n == SOCKET_ERROR )
    				{
    					fprintf( stdout, "Fehler: select, fehler code: %s\n ", WSAGetLastError() );
    					fprintf( stdout, "Client %d hat die Verbindung geschlossen\n", i);
    					//den socket schließen
    					closesocket( clients[i] );
    					clients[i] = INVALID_SOCKET;
    					//WSACleanup();
    				}
    				else
    				{
    					//Empfangene Nachricht ausgeben
    					RecvBuf[n] = '\0';
    					fprintf( stdout, "Client %d hat geschrieben: %s\n", i, RecvBuf );
    					sprintf_s( SendBuf, "Client %d schrieb: %s\n", i, RecvBuf );
    
    					for( i = 0; i < MAXCLIENT; i++ )
    					{
    						//send(clients[i], SendBuf, (int)strlen( SendBuf ), 0 );
    						sprintf_s( SendBuf, "Client %d schrieb: %s\n", i, RecvBuf );
    						send(clients[i], SendBuf, (int)strlen( SendBuf ), 0 );
    					}
    				}
    			}
    			//RECVBUF leeren?!
    			int rbuf = sizeof(RecvBuf);
    			for(i = 0; i < rbuf ; i++)
    			{
    				RecvBuf[i] = 0;
    			}
    		}
    	}//Hauptschleife MIT Select()
    	WSACleanup();	
    	exit(EXIT_SUCCESS);
    } // main
    

    Programmieren tue ich unter Windows XP und Visual Studio 2008



  • Erstmal wäre es nett zu erfahren, welcher Fehler auftritt...

    Du musst FD_ZERO in die Haupt-Schleife verschieben, so dass der Befehl am Anfang eines jeden Schleifendurchlauf ausgeführt wird, da sonst nach select () noch Sockets im fd_set bleiben, welche du dann beim nächsten Schleifendurchlauf ein zweites Mal einsetzt.

    Außerdem könntest du statt:

    fd = socket(PF_INET, SOCK_STREAM, ptrp->p_proto);
    

    (und dem Zeug darüber) einfach den letzten Parameter durch 0 ersetzen (wird nicht benötigt).



  • Ich bekomme diese Fehlermeldung:

    Unbehandelte Ausnahme bei 0x1029984f (msvcr90d.dll) in RN_Server.exe: 0xC0000005: Zugriffsverletzung beim Lesen an Position 0x00002745.
    


  • Dann lass mal den Debugger warm laufen und guck, an welcher Stelle es hackt.



  • Hab ich schon. Guck mal oben im Code, hab da einen Kommentar drin. An der Stelle wo n -1 wird bricht das Programm ab und der Fehler kommt



  • Den Grund (ich nehme mal an, dass das der Grund ist) dafür habe ich dir bereits in meinem ersten Post genannt. Oder hast du das schon ausprobiert?



  • Ja, ich hab das FD_ZERO schon getestet. Der Fehler bleibt leider der gleiche



  • Dann bau mal ein printf ("%s %d\n", strerror (errno), errno); mit vorherigem #include <errno.h> ein und schau nach, was der Fehler bedeutet bedeutet. (Oder wird das unter Windows mit WSAGetLastError (); gemacht?)



  • printf ("%s %d\n", strerror (errno), WSAGetLastError());
    

    Da gibt er folgendes aus:

    No Error 10038
    


  • http://msdn.microsoft.com/en-us/library/ms740668(VS.85).aspx schrieb:

    WSAENOTSOCK
    10038

    Socket operation on nonsocket.
    An operation was attempted on something that is not a socket. Either the socket handle parameter did not reference a valid socket, or for select, a member of an fd_set was not valid.

    Dann hat der Client irgendwie die Verbindung abgebrochen oder der Socket ist im Server put gegangen. Kann jetzt auf den ersten Blick den Grund auch nicht erkennen...



  • Ja genau. Hilft es vl wenn ich den Code vom Client auch noch poste?

    // RN_P_Beispielclient.cpp : Definiert den Einstiegspunkt für die Konsolenanwendung.
    //
    #include "stdafx.h"
    #include <winsock.h>
    #include <iostream>
    #include <conio.h>
    #include <io.h>
    
    #define PROTOPORT		5000		// Default Portnummer
    #define AF_INET_LEN		4			// Länge IPv4-Adresse in Byte
    
    using namespace std;
    
    int main(int argc, char* argv[])
    {
    //--------------------------------------------------------------------------------------
    // Program:   RN_P_Beispielclient.cpp
    //
    // Funktion:  Baut eine TCP-Verbindung zu einem Server auf, gibt dessen Nachricht
    //			  auf dem Bildschirm aus und bestätigt die Verbindung.
    //
    // Aufruf:    RN_P_Beispielclient [<Rechnername>][<Portnummer>]
    //            <Rechnername>: Rechnername des Servers
    //            <Portnummer>: Portnummer unter welcher der Server seinen 
    //							Dienst anbietet		
    //
    // Bemerkung: Die Eingabe der Parameter ist optional. Falls kein Rechnername
    //            spezifiziert ist, verwendet der Client "localhost" (127.0.0.1);
    //			  falls kein Port spezifiziert ist, wird der durch PROTOPORT definierte
    //            Default-Wert verwendet!
    //
    // Dr. W. Schmitt, 02.05.08
    //--------------------------------------------------------------------------------------
    	const char *Datum = "02.05.08";
    	int				Fehlerkode;			// Hilfsvariable zum Speichern eines Fehlerkodes
    	char localhost[] = "localhost";		// Default Hostname 
        hostent			*ptrh;				// Speichert den Namen des Clientrechners
        protoent		*ptrp;				// Speichert den Namen des Transp.Protok.
        sockaddr_in		serveradress;				// Speichert die Serveradresse
        SOCKET			sock;					// Socket Descriptor
    	unsigned short	port;				// Portnummer des Servers
        char			*host;				// Host Name static_cast<void*>
        unsigned int	n;					// Anzahl empfangener Bytes
        char			RecvBuf[100];		// Puffer für Empfangsdaten
        char			SendBuf[100];		// Puffer für Sendedaten
    	char *SendeText = "TCP-Verbindungsaufbau erfolgreich! \n";
    	WSADATA			wsaData;			// Speichert Winsock-Initialisierungsinfo
        WORD wVersionRequested;				// Speichert die gewünschte Version der Winsock-
    										// DLL
    	wVersionRequested = MAKEWORD(2, 2);
    // Initialisierung der Winsock-DLL	
    	Fehlerkode = WSAStartup(wVersionRequested, &wsaData);
    	if (Fehlerkode != NO_ERROR)
    	{
    		fprintf_s(stdout,"Initialisierung der Winsock-DLL fehlgeschlagen: %i\n",
    					Fehlerkode);
    	}
    
    //  Bildschirmausgabe einer Begrüßungsnachricht        
    	fprintf_s(stdout, "Hallo Client!\n");
    
        memset(&serveradress, 0, sizeof(serveradress));       // Initialisiere sad mit 0
        serveradress.sin_family = AF_INET;           // Setze "Internetadressierung"
    
    // Auswerten der Kommandozeilenparameter      
    	if (argc > 1) 
    	{						
    		// Falls <Rechnername> vorhanden
    		// dann in host speichern
    		host = argv[1];
    	}				
    	else 
    	{
    		// sonst verwende "localhost"
    		host = localhost;
    	}				
    
    	ptrh = gethostbyname(host);	        // <Rechnername> --> IP-Adresse
    
    	if (ptrh == NULL) 
    	{
            fprintf_s(stdout,"ungültige Rechneradresse: %s\n", host);
    		WSACleanup();
    		exit(EXIT_FAILURE);
    	}
    
        if (argc > 2) 
    	{                     
    		// Falls <Portnummer> vorhanden
    		// dann konvertiere nach int
    		port = atoi(argv[2]);
    	}			
        else
    	{
    		// sonst verwende Default-Wert 
    		port = PROTOPORT;
    	}				   
    
        if (port > 0)            
    		serveradress.sin_port = htons(port);		// Host --> Netzwerk-Byteordnung
    	else 
    	{								// Drucke Fehlermeldung; Programmende
    		fprintf_s(stdout,"Ungültige Portnummer %s\n",argv[2]);
    		WSACleanup();
    		exit(EXIT_FAILURE);
    	}
    
    	if (memcpy_s(&serveradress.sin_addr, AF_INET_LEN, ptrh->h_addr, ptrh->h_length))
    	{
    		fprintf_s(stdout,"Fehler beim Kopieren der Rechneradresse");
    		WSACleanup();
    		exit(EXIT_FAILURE);
    	}
    
        if ((ptrp = getprotobyname("tcp")) == NULL) 
    	{ // "tcp" --> Protokollcode
            fprintf_s(stdout, "Abbildung von \"tcp\" auf Protokollnummer nicht möglich!");
    		WSACleanup();
    		exit(EXIT_FAILURE);
    	}
    
    	// Erzeuge Socket
    	sock = socket(PF_INET, SOCK_STREAM, ptrp->p_proto); 
        if (sock == INVALID_SOCKET) {
    		fprintf_s(stdout, "Fehler bei Generierung von Socket: %ld\n", WSAGetLastError());
    		WSACleanup();
    		exit(EXIT_FAILURE);
    	}
    
    	// Aktiver Verbindungsaufbau zum Server
    	if (connect(sock, (struct sockaddr *)&serveradress, sizeof(serveradress)) == SOCKET_ERROR)
    	{
    		fprintf_s(stdout,"Fehler beim TCP-Verbindungsaufbau: %ld\n", WSAGetLastError());
    		WSACleanup();
    		exit(EXIT_FAILURE);
    	}
    
    	//solange du verbunden bist
    	while(connect)
    	{
    		fprintf(stdout, "Bitte Nachricht eingeben:\n");
    		//Lese Text von der Konsole ein und schreibe in SendBuf
    		cin.getline(SendBuf, 1000);
    		send(sock, SendBuf,strlen(SendBuf),0);
    
    		// Empfangene Daten lesen und auf dem Bildschirm schreiben
    		// Daten aus TCP-Empfangspuffer lesen und in RecvBuf schreiben    
    		n = recv(sock, RecvBuf, static_cast<int>(strlen(RecvBuf)), 0); 
    		RecvBuf[n] = '\0';					// String mit '\0' abschließen
    		if (fprintf_s(stdout, RecvBuf, n) < 0)
    		{
    			fprintf_s(stdout, "Schreib-/Lesefehler!\n");
    		}
    		//Falls senden fehlgeschlagen ist: Fehlermeldung und beenden mit Tastendruck
    		if (send(sock, SendBuf, static_cast<int>(strlen(SendBuf)), 0) <0)
    		{
    			fprintf_s( stdout, "Fehler beim Senden!\n" );
    			fprintf_s( stdout, "Zum beenden bitte Taste druecken" );
    			closesocket( sock );
    			WSACleanup();
    			while( !_kbhit() );
    				exit( EXIT_SUCCESS );
    		}
    		// Socket schließen und von der Winsock-DLL reservierten Ressourcen freigeben 
    		closesocket(sock);
    		WSACleanup();
    	}//Hauptschleife
    } // main
    


  • Ähm ja... Deine Schleifen-Konditionsvariable heißt connect, was in diesem Fall keine Variable sondern eine Funktion ist, auf die der Zeiger immer ungleich 0 ist, was bedeutet, dass die Schleife für immer läuft.
    Am Ende der Schleife schließt Du den Socket und räumst WSA auf, wobei du beim nächsten Schleifendurchlauf wieder mit Socket-Funktionen arbeitest?!
    cin.getline (); gibst du als Buffer-Größen-Parameter 1000 mit, obwohl der Buffer nur 100 Bytes groß ist. Außerdem verwendest du zwei Mal send (); , wobei du nur beim zweiten Mal prüfst, ob ein Fehler aufgetreten ist...

    Mein Tipp: Schmeiß den Code weg und fang noch mal ordentlich an. Ein gutes Tutorial findest du hier.



  • Ah ok. Die Differenz 100 zu 1000 war nur Testweise, habs vergessen zu ändern.
    Ansonsten Danke für den Tipp. Werd das dann morgen mal in Angriff nehmen.
    Falls es nicht klappt, meld ich mich hier wieder

    Schönen Abend noch



  • Vielleicht solltest du den Code selbst paar mal durchgehen bis es klingelt, bevor du hier postest...


Anmelden zum Antworten