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.

    http://ideone.com/L7Y0c

    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.


Anmelden zum Antworten