Multithreadingfähigen Server schreiben
-
Hallo, ich schreibe gerade unter anderem zu Übungszwecken einen multithreadingfähigen Server unter Windows. Leider klappt das nicht so wie ich will. Durch ein paar Debugausgaben habe ich rausbekommen, dass sich ClienThreads dasselbe Objekt teilen (gleicher Speicherbereich). Das will ich natürlich vermeiden, aber ich finde den Fehler einfach nicht. Die wichtigen Methoden und Funktionen sind myServer::AcceptNewSocket(), ClientRoutine(LPVOID) und myServer::CloseConnection(int).
Mein Compiler ist minGW mit Code::Blocks.
Ich wäre euch wirklich dankbar wenn ihr den Fehler findet...#include <iostream> #include <winsock2.h> #include <windows.h> #include <vector> //#pragma comment(lib, "libws2_32.lib" ) <--nützt nichts using namespace std; ///Prototypen void printDebug(const char*,int); void printDebug(const char*, int, int); class myClient { public: SOCKET ClientSocket; char recvBuffer[257]; myClient() { memset(recvBuffer, 0, 257); } }; class myServer { public: myServer(); int OnExecute(); private: friend DWORD WINAPI ClientRoutine(LPVOID); //====>wichtiger Kram CRITICAL_SECTION ServerCritical; unsigned int MAX_NUMBER_CLIENTS; unsigned int numberOfClients; int ServerPort; SOCKET acceptSocket; vector<HANDLE> aThreadsofClients; vector<myClient> aClients; //====>unwichtiger Kram timeval TIMEOUT_0; char recvBufferServer[257]; int rc_class; //rückgabewert SOCKADDR_IN addrsocket; FD_SET fd; void parseClientData(unsigned int indexOfClient); void ClientRecv(int indexOfClient); void CloseConnection(unsigned int indexOfSocket); void AcceptNewSocket(); int StartupWinsock(); void CleanupWinsock(); inline void EnterCritical(); inline void LeaveCritical(); void printDebug(const char *msg, int zeilennummer); void printDebug(const char *msg, int zahl, int zeilennummer); WSADATA wsaD; }; struct ClientArgs { myServer* thisServer; myClient* thisClient; }; myServer::myServer() { //==>timeout definieren TIMEOUT_0.tv_sec = 0; TIMEOUT_0.tv_usec = 0; //kritischen Bereich definieren InitializeCriticalSection(&ServerCritical); //Arrays nullen memset(recvBufferServer, 0, sizeof(recvBufferServer)); MAX_NUMBER_CLIENTS = 64; ServerPort = 1337; //Port festlegen numberOfClients = 0; ///======================>Start Winsock StartupWinsock(); if ((acceptSocket = socket(AF_INET, SOCK_STREAM, 0))==INVALID_SOCKET) { printDebug("ERROR - acceptSocket konnte nicht erstellt werden. Fehler Nr.", WSAGetLastError(), __LINE__); } ///===========>Socket binden memset(&addrsocket, 0, sizeof(SOCKADDR_IN)); //alles nullen addrsocket.sin_port = htons(ServerPort); addrsocket.sin_family = AF_INET; addrsocket.sin_addr.s_addr = INADDR_ANY; if ((bind(acceptSocket, (SOCKADDR*)&addrsocket, sizeof(SOCKADDR_IN)))==SOCKET_ERROR) { printDebug("ERROR - Binden des Sockets Fehler Nr.", WSAGetLastError(), __LINE__); } if ((listen(acceptSocket, MAX_NUMBER_CLIENTS))==SOCKET_ERROR) { printDebug("ERROR at listen Fehler Nr.", WSAGetLastError(), __LINE__); } else { printDebug("[SUCCES] acceptSocket wurde erfolgreich gebunden! Maximale Verbindungen:", MAX_NUMBER_CLIENTS, __LINE__); printDebug("Port Nr.", ServerPort, __LINE__); } } int myServer::OnExecute() { unsigned int rc_Threads = WAIT_TIMEOUT; while (1) { ///=============>Verbindungen annehmen AcceptNewSocket(); //acceptSocket überprüfen ///==============>Verbindungen trennen rc_Threads = WaitForMultipleObjects(aThreadsofClients.size(), &aThreadsofClients[0], false, 0); if (rc_Threads != WAIT_TIMEOUT && rc_Threads != WAIT_FAILED) { printDebug("Client wird getrennt Nr. ", rc_Threads, __LINE__); CloseConnection(rc_Threads); } } return 0; } void myServer::parseClientData(unsigned int indexOfClient) { cout << aClients.at(indexOfClient).recvBuffer << endl; //BufferOverflow oder irgendwas anderes return; } void myServer::CloseConnection(unsigned int indexOfSocket) { EnterCritical(); CloseHandle(aThreadsofClients[indexOfSocket]); aThreadsofClients.erase(aThreadsofClients.begin()+indexOfSocket); //====================================> closesocket(aClients[indexOfSocket].ClientSocket); aClients.erase(aClients.begin()+indexOfSocket); //ClientObject aus dem Vector löschen numberOfClients--; LeaveCritical(); return; } DWORD WINAPI ClientRoutine(LPVOID myClientArgs) { ClientArgs* thisClientArg = (ClientArgs*)myClientArgs; myServer* thisServer = thisClientArg->thisServer; myClient* thisClient = thisClientArg->thisClient; delete myClientArgs; //ansonsten overflow ///=================================================> thisServer->printDebug("Client ClientAddr", (int)thisClient, __LINE__); //hier sieht man nach ein paar Verbidnungsannahmen, dass das ClientObjekt immer gleich ist //thisServer->printDebug("Client SOCKET: ", (int)thisClient->ClientSocket, __LINE__); //thisServer->printDebug("ClientThread Nr. ", GetCurrentThreadId(), __LINE__); recv(thisClient->ClientSocket, thisClient->recvBuffer, 256, 0); //irgendwas empfangen //============================>Cient wird beendet return 0; } void myServer::AcceptNewSocket() { ClientArgs *newClientArgs; FD_ZERO(&fd); FD_SET(acceptSocket, &fd); if (select(0, &fd, 0, 0, &TIMEOUT_0)==SOCKET_ERROR) //kontrolliert kurz den acceptSocket { printDebug("ERROR - Select Fehler", WSAGetLastError(), __LINE__); } if (FD_ISSET(acceptSocket, &fd)) { EnterCritical(); newClientArgs = new ClientArgs; newClientArgs->thisServer = this; //===============>ClientObjket erstellen und initialisieren aClients.resize(numberOfClients+1); newClientArgs->thisClient = &aClients[numberOfClients]; //den Pointer auf das ClientObjekt in eine Struktur füllen (wird CreateThread übergeben) aClients.at(numberOfClients).ClientSocket = accept(acceptSocket, NULL, NULL); //Verbindung annehmen und den Socket in dem Objekt abspeichern //==========>Thread erstellen aThreadsofClients.resize(numberOfClients+1); aThreadsofClients[numberOfClients] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ClientRoutine, newClientArgs, NULL, NULL); numberOfClients++; //=================> LeaveCritical(); printDebug("Neue Verbindung angenommen! Client Nummer:", numberOfClients, __LINE__); } return; } void myServer::printDebug(const char *msg, int zahl, int zeilennummer) { EnterCritical(); cout << "[DEBUG] in line " << zeilennummer << ": " << msg << " " << zahl << endl; LeaveCritical(); } void myServer::printDebug(const char *msg, int zeilennummer) { EnterCritical(); cout << "[DEBUG] in line " << zeilennummer << ": " << msg << endl; LeaveCritical(); } int myServer::StartupWinsock() { return WSAStartup(MAKEWORD(2,0), &wsaD); } void myServer::CleanupWinsock() { return; } inline void myServer::EnterCritical() { EnterCriticalSection(&ServerCritical); } inline void myServer::LeaveCritical() { LeaveCriticalSection(&ServerCritical); } int main() { cout << "Starting Server..." << endl; myServer obj; obj.OnExecute(); return 0; }
lg
-
Ich habe jetzt herausgefunden, dass es funktioniert, wenn man etwas Zeit zwischen den verschiedenen Verbindungen verstreicht (ich teste meinen Server mit einem AutoIt-Skript und Telnet).
Anscheinend kann es vorkommen, dass mehrere Threads ausversehen die selben Argumente kriegen, da die Zeile
newClientArgs = new ClientArgs;
(z. 196)
öfters mal den selben Speicherbereich anfordert. Gut debuggen lässt sich das mit dem GDB-Debugger auch nicht wegen den Threads...Kann mir jemand sagen, wie die Argumente sicherer übergebe? Momentan wird den Clients bei sehr schnell aufeinander folgenden Verbindungsaufbau dasselbe Objekt zugeordnet.
-
zeit--; schrieb:
Ich habe jetzt herausgefunden, dass es funktioniert, wenn man etwas Zeit zwischen den verschiedenen Verbindungen verstreicht (ich teste meinen Server mit einem AutoIt-Skript und Telnet).
Anscheinend kann es vorkommen, dass mehrere Threads ausversehen die selben Argumente kriegen, da die Zeile
newClientArgs = new ClientArgs;
(z. 196)
öfters mal den selben Speicherbereich anfordert.Ja klar kann das passieren, nämlich wenn der Speicherbereich knapp davor freigegeben wurde.
Das ist ganz normal, und überhaupt kein Problem.
Zu einem Problem würde es nur dann, wann man den Zeigerwert für irgendwas verwendet, nachdem das Objekt bereits wieder freigegeben wurde. Eben WEIL es ganz normal ist dass neue Objekte den Speicher von zuvor zerstörten Objekten wiederverwenden.
-
zeit--; schrieb:
Kann mir jemand sagen, wie die Argumente sicherer übergebe? Momentan wird den Clients bei sehr schnell aufeinander folgenden Verbindungsaufbau dasselbe Objekt zugeordnet.
Mach aus:
DWORD WINAPI ClientRoutine(LPVOID myClientArgs) { ClientArgs* thisClientArg = (ClientArgs*)myClientArgs; myServer* thisServer = thisClientArg->thisServer; myClient* thisClient = thisClientArg->thisClient; delete myClientArgs; //ansonsten overflow
folgendes:
DWORD WINAPI ClientRoutine(LPVOID myClientArgs) { ClientArgs thisClientArg = *((ClientArgs*)myClientArgs); myServer* thisServer = thisClientArg.thisServer; myClient* thisClient = thisClientArg.thisClient; delete myClientArgs; //ansonsten overflow
damit Deine ClientArgumente nicht freigegeben werden, bzw. erst freigegeben werden, wenn die Threadfunktion verlassen wird.
-
@Belli
Macht in dem von ihm gezeigten Code keinen Unterschied.