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); } // mainProgrammieren 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 mitWSAGetLastError ();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
10038Socket 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 Malsend ();, 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 wiederSchönen Abend noch
-
Vielleicht solltest du den Code selbst paar mal durchgehen bis es klingelt, bevor du hier postest...