Socket verschluckt Daten
-
Hallo zusammen,
ich habe angefangen den Quellcode für den Netzwerkteil von dem Buch 3D-Spieleprogrammierung von Stefan Zerbst zu übernehmen. Allerdings habe ich es nicht 1:1 übernommen, sondern etwas abgeändert. Zum einem, verwende ich keine Windows-Events, weil ich den Code ggf später einmal auf Linux übernehmen möchte.
Stattdessen habe ich FD_SETS benutzt um auf Ereignisse reagieren zu können.Das Programm läuft auf dem eigenen Rechner ganz gut und relativ schnell. Wenn ich aber jetzt den Server auf einen anderen Rechner im Netzwerk starte, dann wird ein großer Teil der Daten "verschluckt", wenn ich in einer Schleife ca. 1000 Datenpakete sende.
Wenn ich nun ein Sleep(1) in der Schleife einbaue, dann kommen mehr Pakete an, aber es dauert entsprechend lange. Ein kompletter Durchlauf ca 16 Sekunden.
Ich sitze nun 2 Tage an diesem Problem. Weder das Buch noch Google konnten mir bisher helfen. Vielleicht stelle ich auch einfach die falschen Fragen.
Ich poste hier mal den kompletten Code. Schonmal Entschuldigung, falls es zu lang sein sollte, aber ich kenne den Teil nicht, welcher verantwortlich ist für dieses Problem.
///////////////////////////////////////////////////////////////////////////// // File : CSocket.h // Brief : Testklasse fuer eine Socketimplementierung // Created : 29.12.2008 ///////////////////////////////////////////////////////////////////////////// #ifndef _CSOCKET_H_ #define _CSOCKET_H_ //Includes und libs #include <winsock2.h> #include <vector> #pragma comment (lib, "Ws2_32.lib") //Reservierte Nachrichtentypen enum packagetype_e { TYPE_NEWID, //Neuer Client bekommt eine ID TYPE_NEWCLIENT, //Broadcast an Clients, dass sich ein Client angemeldet hat TYPE_REMCLIENT, //Broadcast an Clients, dass sich ein Client abgemeldet hat TYPE_CHAR = 50 //Chat Nachricht (Testweise) }; //Datenpaket typedef struct netpackage_type { UINT nType; //Nachrichtentyp UINT nSender; //Absender (0 bedeutet Server) UINT nRecipient; //Empfaenger (0 bedeutet Server, 1 bedeutet alle Clients) UINT nDataLength; //Datenlaenge von lpData in Bytes void *lpData; //Zu sendende Daten } netpackage_t; //Clients typedef struct clients_type { UINT nID; //ID des Clients SOCKET nSocket; //Socket vom Client char szIP[256]; //IP-Adresse ULONG nConnected; //Timestamp der Verbindung } client_t; //Socket Klasse class CSocket { private: SOCKET m_nSocket; //Socket UINT m_nPort; //Port UINT m_nPackets; //Anzahl der wartenden Pakete UINT m_nRec; //Anzahl der Receives UINT m_nMaxSize; //Maximale Sendegroesse inkl. Header FD_SET m_FdSet; //Socket Array (http://www.c-worker.ch/tuts/select.php) char *m_cBuffer;//[65536]; //Empfangspuffer char *m_lpSendBuffer; //Sendepuffer //Clients std::vector<client_t> m_vClients; //Einen Client akzeptieren bool Accept(); //Daten empfangen bool Receive(const client_t *lpClient); //Schreibe den letzen WSA Error void LogLastError(const char *szFunction); public: CSocket(); ~CSocket(); //Socket Initialisierung bool CreateSocket(); //Socket an einen Port und eine Schnittstelle binden. Wird NULL uebergeben //so wird INADDR_ANY benutzt. bool BindSocket(UINT nPort, const char *szSocketAddress = NULL); //Serverfunktion aktivieren bool Listen(); //Zu einem Server verbinden bool Connect(const char *szServer, UINT nServerPort); //Daten senden. Diese Funktion wird vom Client und von dem Server benutzt. //Der Server muss zusaetzlich den Socket angeben. Clients koennen diesen //Parameter ingnorieren. bool Send(const char *szData, UINT nSize, SOCKET nReceiver = 0); bool Send(const netpackage_t *lpPackage, SOCKET nReceiver = 0); bool Disconnect(); bool IsConnected(); //Anzahl der verbundenen Clients zureuckgeben int GetConnectedClients(); //TEST bool Loop(); }; #endif //_CSOCKET_H_///////////////////////////////////////////////////////////////////////////// // File : CSocket.cpp // Brief : Testklasse fuer eine Socketimplementierung // Created : 29.12.2008 ///////////////////////////////////////////////////////////////////////////// #include "CSocket.h" #include <iostream> CSocket::CSocket() { m_nSocket = INVALID_SOCKET; m_nPort = 0; m_nPackets = 0; m_nRec = 0; m_nMaxSize = 65536; m_cBuffer = NULL; m_lpSendBuffer = new char[m_nMaxSize]; } CSocket::~CSocket() { //Socket schliessen, falls dieser noch gueltig ist if (m_nSocket != INVALID_SOCKET) Disconnect(); //Aufraeumen WSACleanup(); delete [] m_lpSendBuffer; } //Einen Client akzeptieren bool CSocket::Accept() { sockaddr_in saClientAddress; int nClientSize; client_t client; //Client akzeptieren nClientSize= sizeof(sockaddr_in); client.nSocket = accept(m_nSocket, (SOCKADDR*)&saClientAddress, &nClientSize); //Erfolgreich ? if (client.nSocket == INVALID_SOCKET) { LogLastError("Accept"); return false; } //Informationen abfragen SOCKADDR_IN sai; int sai_len = sizeof( SOCKADDR_IN ); memset( &sai, 0, sizeof( SOCKADDR_IN ) ); getpeername( client.nSocket, (SOCKADDR*)&sai, &sai_len ); strcpy(client.szIP , inet_ntoa( sai.sin_addr ) ); printf("New Client : %s\n", client.szIP); //Client hinzufuegen m_vClients.push_back(client); return true; } //Daten empfangen bool CSocket::Receive(const client_t *lpClient) { UINT nSize = 65536; //Maximale Bytes pro Durchlauf UINT nRead = 0; //Anzahl gelesene Bytes UINT nSeek = 0; //Position im Buffer UINT nLeft = 0; //Verbleibende Bytes bool bDone = false; //True, wenn fertig bool bFirst = true; //True beim dem ersten Schleifendurchlauf m_cBuffer = new char[nSize]; //Zeiger auf Netzwerkpaket netpackage_t *lpPackage = NULL; //Totale groesse des Paketes UINT nPackageSize = 0; //Groesse eines Header UINT nDefPackageSize = sizeof(netpackage_t); //Debug m_nRec++; printf("Empfange Daten (%d) von %s\n", m_nRec, lpClient->szIP); //Bei jedem Schleifendurchlauf nSize Bytes Daten lesen, bis //keine mehr vorhanden sind while (!bDone) { nRead = recv(lpClient->nSocket, &m_cBuffer[nLeft], nSize - nLeft, 0); //Client weg ? if (bFirst) { if (nRead == 0) return false; bFirst = false; } //Fehler ? if (nRead == SOCKET_ERROR) { int nError = WSAGetLastError(); //Kritischer Fehler ? if ((nError != WSAEMSGSIZE) && (nError != WSAEWOULDBLOCK)) { bDone = true; printf ("\a\a\aAchtung Komischer Fehler !\n"); return false; } } //Nun sind nRead Bytes im Buffer if (nRead <= 0) bDone = true; else { //Alte Daten im Buffer beachten nRead += nLeft; //Loopen, bis wir einen kompletten Header finden while ((nRead - nSeek) >= nDefPackageSize) // Ergaenzt : '=' { //Naechstes Datenstueck lpPackage = (netpackage_t*)&m_cBuffer[nSeek]; lpPackage->lpData = &m_cBuffer[nSeek] + nDefPackageSize; nPackageSize = nDefPackageSize + lpPackage->nDataLength; //Haben wir das ganze Datenpaket empfangen ? if ((nRead - nSeek) >= nPackageSize) { //TODO : Enqueue Message //printf("\r#%d ", m_nPackets); if (lpPackage->nType == TYPE_CHAR) { m_nPackets++; printf("ID : %d - # : %d) : %s\n", lpPackage->nSender, m_nPackets, lpPackage->lpData); std::cout << m_cBuffer << std::endl; } else { //m_nPackets--; printf("Paket #%d Fehlerhaft.\n", m_nPackets); } nSeek += nPackageSize; } else { //Das Bruchstueck an den Anfang des Buffers kopieren und weiterlesen memcpy(m_cBuffer, &m_cBuffer[nSeek], nRead - nSeek); nLeft = nRead - nSeek; break; } } //Haben wir alle Daten ? if (nRead <= nSize) //Ergaenzt : '=' bDone = true; } }//while //printf("[%d] %s\n", m_nPackets, m_cBuffer); Sleep(1); delete [] m_cBuffer; return true; } //Schreibe den letzten WSA Error void CSocket::LogLastError(const char *szFunction) { UINT nLastError = WSAGetLastError(); if (nLastError == 0) return; std::cout << "Socket Error : [" << szFunction << "] "; switch (nLastError) { case WSANOTINITIALISED: std::cout << "Successful WSAStartup not yet performed." << std::endl; break; case WSAEADDRNOTAVAIL: std::cout << "Cannot assign requested address. The requested address is not valid on this local computer." << std::endl; break; case WSAHOST_NOT_FOUND: std::cout << "Host not found." << std::endl; break; case WSAECONNREFUSED: std::cout << "Connection refused." << std::endl; break; default: std::cout << "Unknown WSA Error #" << nLastError << std::endl; break; } } //Socket erzeugen bool CSocket::CreateSocket() { WORD wVersionRequested; WSADATA wsaData; int nError; //WSA Starten wVersionRequested = MAKEWORD(2, 2); nError = WSAStartup(wVersionRequested, &wsaData); if (nError != 0) { LogLastError("CreateSocket() WSAStartup"); return false; } //Existiert bereits ein Socket ? if (m_nSocket != INVALID_SOCKET) if (Disconnect() == false) return false; //Socket erstellen m_nSocket = socket(AF_INET, SOCK_STREAM, 0); //Erfolgreich ? if (m_nSocket == INVALID_SOCKET) { LogLastError("CreateSocket() socket(AF_INET, SOCK_STREAM, 0)"); return false; } return true; } //Socket an einen Port und eine Schnittstelle binden. Wird NULL uebergeben //so wird INADDR_ANY benutzt. bool CSocket::BindSocket(UINT nPort, const char *szSocketAddress) { sockaddr_in saServerAddress; memset(&saServerAddress, 0, sizeof(sockaddr_in)); saServerAddress.sin_family = AF_INET; if (szSocketAddress != NULL) saServerAddress.sin_addr.s_addr = inet_addr(szSocketAddress); else saServerAddress.sin_addr.s_addr = INADDR_ANY;//inet_addr("127.0.0.1"); saServerAddress.sin_port = htons(nPort); //Socket binden if (bind(m_nSocket, (SOCKADDR*)&saServerAddress, sizeof(saServerAddress)) == SOCKET_ERROR) { LogLastError("BindSocket()"); Disconnect(); return false; } return true; } //Socket in den Listen-Modus setzen bool CSocket::Listen() { if (listen(m_nSocket, 64) != 0) { LogLastError("Listen()"); return false; } return true; } //Zu einem Server verbinden bool CSocket::Connect(const char *szServer, UINT nServerPort) { sockaddr_in saServerAddress; LPHOSTENT lpHost = NULL; //Es wird versucht den Server zu finden memset(&saServerAddress, 0, sizeof(sockaddr_in)); saServerAddress.sin_port = htons(nServerPort); saServerAddress.sin_family = AF_INET; saServerAddress.sin_addr.S_un.S_addr = inet_addr(szServer); //Falls ein Name und keine IP uebergeben wurde, wird versucht //diesen aufzuloesen. if (saServerAddress.sin_addr.S_un.S_addr == INADDR_NONE) { lpHost = gethostbyname(szServer); if (lpHost != NULL) saServerAddress.sin_addr.S_un.S_addr = ((LPIN_ADDR)lpHost->h_addr)->S_un.S_addr; else { LogLastError("gethostbyname()"); return false; } } //Zum Server verbinden if (connect(m_nSocket, (SOCKADDR*)&saServerAddress, sizeof(sockaddr)) == SOCKET_ERROR) { LogLastError("connect()"); Disconnect(); return false; } //TODO : Erfolgreichen Verbindungsstatus setzen return true; } //Daten senden. Diese Funktion wird vom Client und von dem Server benutzt. //Der Server muss zusaetzlich den Socket angeben. Clients koennen diesen //Parameter ingnorieren. bool CSocket::Send(const char *szData, UINT nSize, SOCKET nReceiver) { UINT nSend = 0; UINT nLeft = 0; if (nReceiver == 0) nReceiver = m_nSocket; while (nSend < nSize) { nLeft = send(nReceiver, szData + nSend, nSize - nSend, 0); //Erfolgreich ? if (nLeft == SOCKET_ERROR) { LogLastError("send()"); return false; } if (nLeft == nSize) break; printf("nLeft = %d\n", nLeft); nSend += nLeft; } return true; } bool CSocket::Send(const netpackage_t *lpPackage, SOCKET nReceiver) { UINT nSizeTotal = sizeof(netpackage_t) + lpPackage->nDataLength; //char *lpBuffer = new char[m_nMaxSize]; //TODO : Eventuell als Member ? //Daten-Serialisierung memcpy(m_lpSendBuffer, lpPackage, sizeof(netpackage_t)); memcpy(m_lpSendBuffer + sizeof(netpackage_t), lpPackage->lpData, lpPackage->nDataLength); //Daten Senden Send(m_lpSendBuffer, nSizeTotal, nReceiver); return true; } //Socket herunterfahren und schliessen bool CSocket::Disconnect() { if (m_nSocket == INVALID_SOCKET) return true; //Socket runterfahren shutdown(m_nSocket, SD_BOTH); //Socket schliessen closesocket(m_nSocket); m_nSocket = INVALID_SOCKET; return true; } //Anzahl der verbundenen Clients zureuckgeben int CSocket::GetConnectedClients() { return m_vClients.size(); } //TEST bool CSocket::Loop() { int nSocketState; // Inhalt vom FD_SET leeren FD_ZERO(&m_FdSet); //Den Accept-Socket hinzufuegen FD_SET(m_nSocket, &m_FdSet); //Hier werden alle gueltigen Client Sockets dem FD_SET hinzugefuegt werden for (std::vector<client_t>::iterator it = m_vClients.begin(); it != m_vClients.end(); ++it) { //std::cout << "Client im Vector : " << it->szIP << std::endl; if (it->nSocket != INVALID_SOCKET) FD_SET(it->nSocket, &m_FdSet); } //Nun wird geschaut, welche Sockets Daten bereitstellen (oder accept() beim Server Socket) //Alle Clients, welche keine Daten haben, werden aus diesem Array entfernt. nSocketState = select(0, &m_FdSet, NULL, NULL, NULL); //Erfolgreich ? if (nSocketState == INVALID_SOCKET) { LogLastError("select()"); return false; } //Wird auf eine neue Verbindung gewartet ? if(FD_ISSET(m_nSocket, &m_FdSet)) Accept(); //Die Clients werden durchlaufen und mit den empfangsbereiten Sockets verglichen. //Bei einer uebereinstimmung werden die Daten empfangen for (std::vector<client_t>::iterator it = m_vClients.begin(); it != m_vClients.end(); ++it) if (FD_ISSET(it->nSocket, &m_FdSet)) { if (!Receive(&*(it))) { printf("Entferne Client.\n"); //TODO : Wenn false, client entfernen und socket schliessen //Test m_vClients.erase(it); break; } } return false; }///////////////////////////////////////////////////////////////////////////// // File : main.h // Brief : // Created : 29.12.2008 ///////////////////////////////////////////////////////////////////////////// #ifndef _MAIN_H_ #define _MAIN_H_ #include "CSocket.h" //Prototypen bool ConnectToServer(const char *szServer, UINT nPort); bool CreateServer(UINT nPort); #endif //_MAIN_H_///////////////////////////////////////////////////////////////////////////// // File : main.cpp // Brief : // Created : 29.12.2008 ///////////////////////////////////////////////////////////////////////////// #include "main.h" #include <iostream> using namespace std; int main(int argc, char *argv) { char cChoice = 0; cout << "Socket Testprogramm" << endl << "================================================" << endl << endl << "Moechten Sie einen Server (s) oder einen Client (c) starten ? (q) fuer Quit :" << endl << endl; do { cout << "Auswahl : "; cin >> cChoice; cin.clear(); cin.sync(); if (cChoice == 's') { CreateServer(42202); return 0; } if (cChoice == 'c') { ConnectToServer("127.0.0.1", 42202); return 0; } } while(cChoice != 'q'); return 0; } bool ConnectToServer(const char *szServer, UINT nPort) { CSocket Socket; //CreateSocket() cout << "Erstelle Socket ... "; if (Socket.CreateSocket()) cout << "Erfolgreich !" << endl; else { cout << "Fehlgeschlagen !" << endl; return false; } //Verbinde zum Server //CreateSocket() cout << "Verbinde zum Server [" << szServer << ":" << nPort << "] ... "; if (Socket.Connect(szServer, nPort)) cout << "Erfolgreich !" << endl; else { cout << "Fehlgeschlagen !" << endl; return false; } //Testpaket netpackage_t package; char szMessage[256] = "Hallo Welt, wie geht es Dir denn heute so :)"; package.lpData = new char[1024]; package.nType = TYPE_CHAR; package.nSender = 1; package.nRecipient = 0; package.nDataLength = sizeof(szMessage); memcpy(package.lpData, szMessage, sizeof(szMessage)+1); cout << "Sende Daten ... " << endl; //Testpaket senden for (int nIndex = 0; nIndex != 1000; nIndex++) { printf("\r#%d", nIndex); package.nSender = nIndex; Socket.Send(&package); Sleep(1); } delete [] package.lpData; cout << "Alle Daten gesendet !" << endl; return true; } bool CreateServer(UINT nPort) { CSocket Socket; nPort = 42202; //CreateSocket() cout << "Erstelle Socket ... "; if (Socket.CreateSocket()) cout << "Erfolgreich !" << endl; else cout << "Fehlgeschlagen !" << endl; //BindSocket() cout << "Binde Socket an Port #" << nPort << " ... "; if (Socket.BindSocket(nPort)) cout << "Erfolgreich !" << endl; else cout << "Fehlgeschlagen !" << endl; //Listen() cout << "Setze Socket in en Listen-Modus ... "; if (Socket.Listen()) cout << "Erfolgreich !" << endl; else cout << "Fehlgeschlagen !" << endl; //Loop while (true) { Socket.Loop(); //Sleep(1); } return true; }