Allgemeine Fragen zu Winsock und Verständnisprobleme mit select()
-
Hallo,
ich programmiere nun schon seit längerem mit Sockets und habe ein paar allgemeine Fragen zu Winsock sowie Verständnisfragen zum Umgang mit der Funktion select(). Das Thema Sockets habe ich mir mit den Tutorials von c-worker.ch angelernt. Bisher habe ich ein paar Codeteile, die mir sinnlos vorkamen aus dem select()-Tutorial einfach übernommen, da meine Programme ohne diese nicht liefen. Nun würde ich von euch gerne Wissen, was diese Codeteile bewirken.
Hier erstmal der vollständige Code:
#pragma comment( lib, "ws2_32.lib" ) #include <windows.h> #include <winsock2.h> // bei manchan compilern muss man nur windows.h includieren (zB VC++) #include <stdio.h> #define MAX_CLIENTS 10 int startWinsock(void) { WSADATA wsa; return WSAStartup(MAKEWORD(2,0),&wsa); } int main() { long rc; SOCKET acceptSocket; //SOCKET connectedSocket; SOCKADDR_IN addr; char buf[256]; char buf2[300]; // zusätzliche Variabeln FD_SET fdSet; SOCKET clients[MAX_CLIENTS]; int i; // Winsock starten rc=startWinsock(); if(rc!=0) { printf("Fehler: startWinsock, fehler code: %d\n",rc); return 1; } else { printf("Winsock gestartet!\n"); } // Socket erstellen acceptSocket=socket(AF_INET,SOCK_STREAM,0); if(acceptSocket==INVALID_SOCKET) { printf("Fehler: Der Socket konnte nicht erstellt werden, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("Socket erstellt!\n"); } // Socket binden memset(&addr,0,sizeof(SOCKADDR_IN)); addr.sin_family=AF_INET; addr.sin_port=htons(12345); addr.sin_addr.s_addr=INADDR_ANY; // gewisse compiler brauchen hier ADDR_ANY rc=bind(acceptSocket,(SOCKADDR*)&addr,sizeof(SOCKADDR_IN)); if(rc==SOCKET_ERROR) { printf("Fehler: bind, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("Socket an port 12345 gebunden\n"); } // In den listen Modus rc=listen(acceptSocket,10); if(rc==SOCKET_ERROR) { printf("Fehler: listen, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("acceptSocket ist im listen Modus....\n"); } for(i=0;i<MAX_CLIENTS;i++) { clients[i]=INVALID_SOCKET; } while(1) { FD_ZERO(&fdSet); // Inhalt leeren FD_SET(acceptSocket,&fdSet); // Den Socket der verbindungen annimmt hinzufügen // alle gültigen client sockets hinzufügen (nur die die nicht INVALID_SOCKET sind) for(i=0;i<MAX_CLIENTS;i++) { if(clients[i]!=INVALID_SOCKET) { FD_SET(clients[i],&fdSet); } } rc=select(0,&fdSet,NULL,NULL,NULL); // nicht vergessen den ersten parameter bei anderen betriebssystem anzugeben if(rc==SOCKET_ERROR) { printf("Fehler: select, fehler code: %s\n",WSAGetLastError()); return 1; } // acceptSocket is im fd_set? => verbindung annehmen (sofern es platz hat) if(FD_ISSET(acceptSocket,&fdSet)) { // einen freien platz für den neuen client suchen, und die verbingung annehmen for(i=0;i<MAX_CLIENTS;i++) { if(clients[i]==INVALID_SOCKET) { clients[i]=accept(acceptSocket,NULL,NULL); printf("Neuen Client angenommen (%d)\n",i); break; } } } // prüfen wlecher client sockets im fd_set sind for(i=0;i<MAX_CLIENTS;i++) { if(clients[i]==INVALID_SOCKET) { continue; // ungültiger socket, d.h. kein verbunder client an dieser position im array } if(FD_ISSET(clients[i],&fdSet)) { rc=recv(clients[i],buf,256,0); // prüfen ob die verbindung geschlossen wurde oder ein fehler auftrat if(rc==0 || rc==SOCKET_ERROR) { printf("Client %d hat die Verbindung geschlossen\n",i); closesocket(clients[i]); // socket schliessen clients[i]=INVALID_SOCKET; // seinen platz wieder freigeben } else { buf[rc]='\0'; // daten ausgeben und eine antwort senden printf("Client %d hat folgendes gesandt: %s\n",i,buf); // antwort senden sprintf(buf2,"Du mich auch %s\n",buf); send(clients[i],buf2,(int)strlen(buf2),0); } } } } }1. Erstes Verständnisproblem:
Zuerst bekommen alle Verbindungs-Sockets den Wert INVALID_SOCKET:
for(i=0;i<MAX_CLIENTS;i++) { clients[i]=INVALID_SOCKET; }Zeile 80 bis 83
Und danach werden alle Verbindungs-Sockets, die nicht den Wert INVALID_SOCKET besitzen in das FD_SET aufgenommen.
// alle gültigen client sockets hinzufügen (nur die die nicht INVALID_SOCKET sind) for(i=0;i<MAX_CLIENTS;i++) { if(clients[i]!=INVALID_SOCKET) { FD_SET(clients[i],&fdSet); } }Zeile 90 bis 97
Und hier ist mein Verständnisproblem! Es dürften doch keine Verbindungs-Sockets in das FD_SET aufgenommen werden da sie alle den Wert INVALID_SOCKET haben, oder sehe ich das falsch?
2. Zweites Verständnisproblem:
Wieso wird in Zeile 131 auf einen Socket Error geprüft und wenn dieser vorhanden ist, angezeigt das ein Client die Verbindung getrennt hat? recv gibt doch den Rückgabewert 0 zurück, wenn ein Client die Verbindung trennt. Wenn man aber nur auf rc==0 prüft, wird die zuletzt vom Client gesendete Nachricht auf dem Server immer wiederholt neu ausgegeben, warum?
3. Drittes Verständnisproblem:
Wieso wird in Zeile 135 das Socket auf INVALID_SOCKET gesetzt, macht das die Funktion closesocket, die eine Zeile davor aufgerufen wurde nicht schon?
Weitere Fragen:
4. Wenn man nun mit closesocket alle Sockets schließt sowie ein WSACleanup nach der Schleife programmiert, wird dieses doch nie ausgeführt, oder wird dieses ausgeführt, wenn man auf Schließen klickt? Soweit ich weiß werden nur Destruktoren aufgerufen, wenn man auf Schließen klickt oder die exit bzw. OnExit Funktionen benutzt.
5. closesocket macht doch genau das gegenteil von der socket Funktion, oder?
6. Wenn ich beispielsweise ein Socket allokiere, darauf die socket funktion anwende und dann die closesocket Funktion benutze, habe ich so gesehen doch ein neu allokiertes Socket oder nicht?
7. In wie weit ist Winsock vom Betriebssystem abhängig? Es benötigt auf jeden Fall das jeweilige Protokoll. Gibt es weitere Abhängigkeiten?
8. Sendet man Binärdaten auch über send und recv? Wenn ja, werden die dann mittels fstream eingelesen und geschrieben, oder wie läuft das in der Praxis?
9. Wurden Programme wie Windows Live Messenger, ICQ oder Teamspeak auch über Winsock mit diesen Funktionen geschrieben?
10. Gibt es auch eine Winsock-Klasse, so das man objektorientiert arbeiten kann?
Mit freundlichen Grüßen,
DarkBug
-
Erste Frage:
Zeile 111:
if(clients[i]==INVALID_SOCKET) { clients[i]=accept(acceptSocket,NULL,NULL);die while schleife enthält 2 teile, einmal das überprüfen ob ein neuer
client da is, und zweitens, das überprüfen, ob ein client was gesendet hat.wenn im ersten teil ein neuer client angekommen ist, wird dieser in das
array eingetragen, und der eintrag ist != INVALID_SOCKETZweite Frage:
0 = ordentlich beendet, andere seite hat ein closesocket() gemacht
SOCKET_ERROR = nicht ordentlich beendet, verbindung weg, whateverwenn du nur auf 0 überprüfst, fällt SOCKET_ERROR in den else-zweig -> ausgabe
Dritte Frage:
du übergibst den socket byvalue und nicht byref, die funktion könnte
den socket garnicht ändern. natürlich ist er nach dem close ungültig,
ist zu vergleichen mit dem "pointer = 0" nach einem delete.4:
wenn du auf schließen klickst, wird dein programm (ich nehme mal an console)
brutal beendet, außerdem verlässt du die schleife doch garnicht.5:
jup, macht den socket ungültig und schließt die verbindung
6:
??
7:
die funktionen recv, send, accept, socket, ... sind normale socket-funktionen,
die es auf anderen platformen auch gibt. unter linux isses glaubich close()
statt closesocket (weils wie eine datei behandelt wird). WSAStartup und alle
WSA funktionen sind windows-spezifisch.8:
ja.
meist sendest und empfängst du einen block fester größe, der dann so wie er is
in die datei wandert. send und recv hören beim nullbyte nicht auf! über die
länge sagst du wieviel gesendet werden soll, der inhalt ist dabei egal.9:
ob sie ein framework dafür nutzen, weiß ich nicht, jedoch läuft alles auf
diese grundfunktionen hinaus. sieht man auch wenn man die teile disassembliert.
du könntest dir auch n icq-client mit winsock schreiben (wenn du das protokoll
kennst)10:
da gibt es einige, der einzige der mir um diese zeit spontan einfält, ist
RakNet. deren API ist recht highlevel.
-
1. Erstmal zum Sinn: Es wird damit die Struktur fd_set gefüllt, welche später an select übergeben wird. Dadurch kann festgestellt werden, ob ein Client etwas geschickt hat oder eine neue Verbindung ansteht.
Der Status der Clients ändert sich natürlich dann, wenn eine Verbindung angenommen wird. Diese wird dann im nächsten Schleifendurchlauf hinzugefügt und es wird überprüft, ob Nachrichten ankommen. Mindestens ein Socket ist aber immer im fd_set, nämlich das Socket, welches auf Verbindungen wartet.
4. Es gehört zum guten Stil, dass man die Routinen zur Freigabe trotzdem implementiert. Im übrigen wäre es auch sinnvoll, wenn die Schleife eine Abbruchbedingung erhalten würde, die auch mal erreicht wird. Eine Variable bool running; wäre da angemessen.
5. Ja, könnte man so sehen.
6. Du meinst du legst mit socket() ein neues Socket an und schließt dieses anschließend mit closesocket()? In dem Fall hättest du den gleichen Zustand wie vorher => Kein gültiges Socket.
7. Winsock ist nur für Windows verfügbar, wie der Name erahnen lässt: WINdows Sockets. Trotzdem hält sich die API an die Berkeley Sockets (POSIX), so dass du den Code nur (relativ) geringfügig ändern musst, wenn du auf einem anderen System kompilieren willst.
8. Ja, du kannst alles über die Leitung schicken. Du musst das dann in ein char * casten, aber das macht zunächst keine Probleme. Es wird nur kompliziert, wenn die Betriebssysteme eine andere Endianess haben, etc.
9. Im Endeffekt geht alles auf Winsock zurück, ja. Dem zugrunde liegen aber teilweise andere Programmiersprachen, wie Delphi, die aber im Endeffekt auch nur die Winsock-Funktionen ansteuern.
10. Nein, Winsock ist insofern nur zu vorhanden. Du kannst aber Plattformunabhängige APIs benutzen, die alles für dich machen, so das du gar kein Stück Code ändern musst. Suchwörter wären ASIO oder ähnliches.
-
Vielen Dank für eure Antworten.
1. Ok, beantwortet. Nachdem ich mir nochmal den Beitrag in der MSDN durchgelesen habe, habe ich erstmal verstanden was gemeint ist mit: "select verändert das FD_SET". Wenn z.B. Daten von einem Client gesendet werden, der verbunden ist, sich aber in diesem Schleifendurchlauf kein neuer Client versucht zu verbinden, wirft select den acceptSocket aus dem FD_SET, damit er bei accept nicht hängen bleibt.
2-9. Beantwortet.
10. Ich weiß nicht ob ihr mich so ganz verstanden habt. Ich meine eine programmierte Klasse im Sinne von OOP. Ich habe mir in etwa soetwas vorgestellt:
socket = new SOCKET(AF_INET,SOCK_STREAM,0); socket->connect((SOCKADDR*)&addr,sizeof(SOCKADDR)); socket->send(buf,strlen(buf),0);11. Es ist nicht möglich über TCP 0 Zeichen zu senden, oder?
Mit freundlichen Grüßen,
DarkBug
-
Sorry für den Push, aber könnte mir jemand bitte noch die letzten beiden Fragen beantworten?
-
wenn du sowas willst, das is in 5min zusammengebastelt
class Socket { public: Socket(int af, int typ, int proto = 0) { if (!count++) { WSADATA wsa; WSAStartup(MAKEWORD(2,0), &wsa); } s = socket(af, typ, proto); } ~Socket() { closesocket(s); if (!--count) WSACleanup(); } int Connect(const sockaddr_in *addr) { if (!addr) return -1; return connect(s, addr, sizeof(*addr)); } int Send(const void *data, unsigned size, int flags) { if (!data || !size) return -1; return send(s, static_cast<const char *>(data), size, flags); } private: SOCKET s; static unsigned count; }; unsigned Socket::count = 0;ganz easy

- nein, afaik geht das nicht, aber da TCP streamorientiert ist, warum sollte
man das tun wollen?
- nein, afaik geht das nicht, aber da TCP streamorientiert ist, warum sollte
-
DarkBug schrieb:
11. Es ist nicht möglich über TCP 0 Zeichen zu senden, oder?
msdn: send() schrieb:
Calling send with a len parameter of zero is permissible and will be treated by implementations as successful. In such cases, send will return zero as a valid value. For message-oriented sockets, a zero-length transport datagram is sent.
...ist aber ziemlich sinnlos

-
@zap:
10. An eine eigene Klasse zu bauen hatte ich auch schon gedacht, jedoch ist man dann immer von dieser abhängig. Wenn es keine offizielle Klasse aus den Standardlibrarys gibt, werde ich weiterhin nicht objektorientiert mit Sockets programmieren.11. War nur neugierig ^^.
@geeky: Danke xD.