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