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;
    }
    

Anmelden zum Antworten