Frage zu Tutorial von www.c-worker.ch (TCP/UDP Socket Programmierung)



  • Dann versteh ich aber in dem Beitrag (http://www.c-plusplus.net/forum/viewtopic-var-t-is-158175-and-highlight-is-host+port.html)

    diese Aussage nicht:

    // prüfen ob ein socket bereit ist, da timeout=0 kehrt die funktion
    // sofort wieder zurück nach dem aufruf.
    // achtung: das timeout auf 0 setzen oder als paremeter NULL mitgeben
    // ist NICHT das gleiche. auf 0 gesetzt kehrt sofort zurück, während
    // NULL blockt.



  • Thomas_ schrieb:

    Dann versteh ich aber in dem Beitrag (http://www.c-plusplus.net/forum/viewtopic-var-t-is-158175-and-highlight-is-host+port.html)

    diese Aussage nicht:

    // prüfen ob ein socket bereit ist, da timeout=0 kehrt die funktion
    // sofort wieder zurück nach dem aufruf.
    // achtung: das timeout auf 0 setzen oder als paremeter NULL mitgeben
    // ist NICHT das gleiche. auf 0 gesetzt kehrt sofort zurück, während
    // NULL blockt.

    Moment, da verwechselst du etwas. select hat die folgende Signatur:

    int select(int nfds, fd_set *restrict readfds,
               fd_set *restrict writefds, fd_set *restrict errorfds,
               struct timeval *restrict timeout);
    

    Uebergibst du fuer timeout "NULL" bzw. "0", dann setzt du den Zeiger auf den
    Nullpointer. In dem Artikel meinen die aber, den timeout per timeval-Parameter
    auf 0 zu setzen, d. h. es wird eine timeval-Struktur uebergeben, deren Member
    0 sind.

    gruss
    v R



  • @ virtuell Realisticer:

    OK den Unterschied verstehe ich!
    und da habe ich wirklich was verwechselt/falsch verstanden.

    Ich habe mich nun für eine Lösung mit Threads entschieden und würde gerne eure Meinung dazu hören. Wodurch könnten komplikationen entstehen? Bei mir traten bislang keine Fehler auf ...

    Hier der Code:

    bool CloseSocket(SOCKET &Socket)
    {
     return closesocket(Socket) && WSACleanup();
    }
    
    SOCKET CreateSocket(char *ServerName, u_short Port)
    {
       WSADATA wsa;
       SOCKET Socket;
       SOCKADDR_IN addr;
       long rc = WSAStartup(MAKEWORD(2,0), &wsa);
    
       if (rc != NULL)
       {
          CloseSocket(Socket);
          return 0;
       }
    
       Socket = socket(AF_INET, SOCK_STREAM, 0);
       if (Socket == INVALID_SOCKET)
       {
          CloseSocket(Socket);
          return 0;
       }
    
       memset(&addr, 0, sizeof(SOCKADDR_IN)); // zuerst alles auf 0 setzten
       addr.sin_family      = AF_INET;
       addr.sin_port        = htons(Port); // wir verwenden mal port 12345
    
       rc = getAddrFromString(ServerName, &addr);
       if(rc == SOCKET_ERROR)
       {
          CloseSocket(Socket);
          return 0;
       }
    
       rc = connect(Socket, (SOCKADDR*)&addr, sizeof(SOCKADDR));
       if (rc == SOCKET_ERROR)
       {
          CloseSocket(Socket);
          return 0;
       }
     return Socket;
    }
    
    DWORD WINAPI recvThread(LPVOID Data)
    {
       char buf[256];
       long rc;
       do
       {
          rc = recv((SOCKET)Data, buf, 256, 0);
          if (rc == 0) // Server hat die Verbindung getrennt
          {
             PrintToLog("Server hat die Verbindung getrennt... [code: rc]\n");
             break;
          }
          if (rc == SOCKET_ERROR) // Fehler beim Empfangen
             break;
    
          buf[rc] = '\0'; // Daten erfolgreich empfangen
    
          PrintToLog("Server: " + buf + "\n");
    
       }
       while (rc != SOCKET_ERROR);
    
     return (DWORD)Data;
    }
    
    int main(int argc, char* argv[])
    {
       HANDLE hThread;
       DWORD dwThreadID;
    
       SOCKET Socket = CreateSocket("192.168.0.19", 12345);
    
       if (Socket)
       {
          HANDLE hThread = CreateThread(NULL, 0, recvThread, (LPVOID)Socket, 0, 0);
    
             char buf[256];
             do
             {
                gets(buf);
             } while(send(Socket, buf, strlen(buf), 0) != SOCKET_ERROR);
    
             CloseSocket(Socket);
             CloseHandle(hThread);
       }
     return 0;
    }
    


  • Ein Fehler der mir schon mal aufgefallen ist: Wenn du 256 Bytes empfängst, wird in das 257ste das Null-Byte geschrieben, was dann außerhalb des Array-Bereichs ist.
    Wenn recv SOCKET_ERROR zurückgibt, könntest du WSAGetLastError noch auf WSAEWOULDBLOCK prüfen und in dem Fall ein continue oder so einbauen. Ich blick grad nicht durch ob recv nun blockiert oder nicht (ich persönlich hab bis jetzt immer nur mit select gearbeitet), aber verlieren tust du dabei nix: Wenn's blockiert, kannst du diesen Fehlercode gar nicht kriegen und wenn's nicht blockiert hast du's korrekt behandelt.

    Da du ja augenscheinlich in C++ arbeitest (in C gabs doch keine Referenzen oder?), kannst du deine Variablen deklarieren wo du willst. In recvThread kannst du die rc-Variable z.B. direkt bei der Zuweisung von recv hinstecken ( long rc = rc = recv(...); . Du hast davon keinen "direkten" Vorteil, ist aber besser lesbar.

    Ach, und in der main-Funktion hast du zwei mal die Variable hThread deklariert.

    In CreateSocket brauchst/solltest du nicht CloseSocket aufrufen, bevor dieser überhaupt gesetzt ist 😉

    Um dich nicht um WSAStartup und Cleanup kümmern zu müssen, könntest du sowas in der Art verwenden:

    namespace // anonymer Namensraum
    {
        class WSAStartAndCleanWatcher
        {
            public:
                WSAStartAndCleanWatcher()  { /*hier das Startup rein*/ }
                ~WSAStartAndCleanWatcher() { /*hier das Cleanup rein*/ }
        };
        WSAStartAndCleanWatcher watcher;
    }
    

    Oder halt doch nicht in einen anonymen Namespace und eine Instanz davon am Anfang der main deklarieren.

    Vielleicht solltest du auch noch was einbauen, dass du deinen Thread beenden kannst - jetzt wird er ja nur beendet, falls inkorrekte Daten kommen oder was mit dem Socket nicht stimmt. Und in main könntest du dann halt warten (WaitForSingleObject), bis der Thread fertig ist.

    So, das war erstmal alles was mir aufgefallen ist 🙂



  • Ein Fehler der mir schon mal aufgefallen ist: Wenn du 256 Bytes empfängst, wird in das 257ste das Null-Byte geschrieben, was dann außerhalb des Array-Bereichs ist.

    Ein wirklich guter Tip, der übrigends auch im Tutorial falsch ist!

    Ich blick grad nicht durch ob recv nun blockiert oder nicht

    recv blockiert! (hoffe ich erzähl nichts falsches ...)
    habe das geändert aber wann wird denn die Bedingung true?

    if (WSAGetLastError() == WSAEWOULDBLOCK)
       continue;
    

    Da du ja augenscheinlich in C++ arbeitest (in C gabs doch keine Referenzen oder?), kannst du deine Variablen deklarieren wo du willst. In recvThread kannst du die rc-Variable z.B. direkt bei der Zuweisung von recv hinstecken (long rc = rc = recv(...);. Du hast davon keinen "direkten" Vorteil, ist aber besser lesbar.

    Laut Tutorial soll das wohl reines C gewesen sein ... ich habe den Code aber meinen Bedürfnissen angepasst und daher weiß ich nicht, in wie weit das noch C ist ... ich programmiere aber mit dem Borland C++ Builder in der Konsolenanwendung.

    Aber: Ich versteh nicht, warum die zwei sachen gleich sein sollen?

    long rc;
       do
       {
       }
       while (rc != ...);
    
    do
       {
          long rc;
       }
       while (rc != ...);
    

    wird bei der 2. Variante nicht immer wieder (bei jeden do-while "loop") neuer Speicher für rc angefordert und freigegeben? bei der ersten Variante ist das doch nicht so ... und Spoeicher reservieren und freigeben kostet doch Rechenzeit!?? oder nicht? Somit müsste das doch einen - wenngleich unwesentlichen - Unterschied machen!??

    Ach, und in der main-Funktion hast du zwei mal die Variable hThread deklariert.
    

    Hab ich garnicht gesehen ... Danke ... auch die Variable DWORD dwThreadID benutze ich garnicht ...

    In CreateSocket brauchst/solltest du nicht CloseSocket aufrufen, bevor dieser überhaupt gesetzt ist 😉

    Ich verstehe, dass CloseSocket gar keinen Sinn ... Danke ... habs abgeändert!

    long rc = WSAStartup(MAKEWORD(2,0), &wsa);
    
       if (rc != NULL)
       {
          CloseSocket(Socket);
          return 0;
       }
    

    Und überall sonst habe ich es durch WSACleanup(); ersetzt ... denn WSAStartup wurde ja erfolgreich ausgeführt und muss ja wieder beendet werden.

    [cpp]namespace // anonymer Namensraum
    {
        class WSAStartAndCleanWatcher
        {
            public:
                WSAStartAndCleanWatcher()  { /*hier das Startup rein*/ }
                ~WSAStartAndCleanWatcher() { /*hier das Cleanup rein*/ }
        };
        WSAStartAndCleanWatcher watcher;
    } [/cpp]
    

    Das verstehe ich nicht ganz ... welche Funktion hat WSAStartAndCleanWatcher watcher; ? Und was ist namespace ? Damit habe ich noch nie gearbeitet ...

    Vielleicht solltest du auch noch was einbauen, dass du deinen Thread beenden kannst

    Geht sowas auch (nicht auf die "harte" Tour) ohne eine Globale Variable >ENDE<?

    WaitForSingleObject(...);
    

    Habe ich auch eingebaut 🙂

    Vielen Dank!

    Nochmal die Frage: Dadurch, dass ich mit einem Thread arbeite entstehen keine Komplikationen innerhalb der Funktion recvThread? Oder doch? ... ich habe mal gelesen, dass es Probleme gibt, wenn 2 Threads auf den gleichen Speicher zugreifen/ihn verändern/löschen ... und das recv nicht critical selection fähig ist ...

    LG



  • Thomas_ schrieb:

    Ich blick grad nicht durch ob recv nun blockiert oder nicht

    recv blockiert! (hoffe ich erzähl nichts falsches ...)
    habe das geändert aber wann wird denn die Bedingung true?

    if (WSAGetLastError() == WSAEWOULDBLOCK)
       continue;
    

    Da du ja augenscheinlich in C++ arbeitest (in C gabs doch keine Referenzen oder?), kannst du deine Variablen deklarieren wo du willst. In recvThread kannst du die rc-Variable z.B. direkt bei der Zuweisung von recv hinstecken (long rc = rc = recv(...);. Du hast davon keinen "direkten" Vorteil, ist aber besser lesbar.

    Laut Tutorial soll das wohl reines C gewesen sein ... ich habe den Code aber meinen Bedürfnissen angepasst und daher weiß ich nicht, in wie weit das noch C ist ... ich programmiere aber mit dem Borland C++ Builder in der Konsolenanwendung.

    Aber: Ich versteh nicht, warum die zwei sachen gleich sein sollen?

    long rc;
       do
       {
       }
       while (rc != ...);
    
    do
       {
          long rc;
       }
       while (rc != ...);
    

    wird bei der 2. Variante nicht immer wieder (bei jeden do-while "loop") neuer Speicher für rc angefordert und freigegeben? bei der ersten Variante ist das doch nicht so ... und Spoeicher reservieren und freigeben kostet doch Rechenzeit!?? oder nicht? Somit müsste das doch einen - wenngleich unwesentlichen - Unterschied machen!??

    Da die lokalen Variablen auf dem Stack abgelegt werden, muss da kein Speicher angefordert werden, also nix Rechenzeit-Verlust 😉 Das Ding ist nur (war mir gestern nicht aufgefallen), dass die rc-Variable dann nicht in der while-Bedingung genutzt werden kann.
    Ich glaube ich würde es ungefähr so machen:

    char buf[256];
    while ( !quit )
    {
        long rc = recv((SOCKET)Data, buf, 255, 0); // Das letzte Byte soll ja für das Null-Byte reserviert sein
        if (rc == 0) // Server hat die Verbindung getrennt
        {
            PrintToLog("Server hat die Verbindung getrennt... [code: rc]\n");
            break;
        }
        if ( rc == SOCKET_ERROR ) // Fehler beim Empfangen
        {
            if ( WsaGetLastError() == WSAEWOULDBLOCK ) // Nur gerade keine Daten da..
                continue;                              // .. also weitermachen
             break; // Sonst scheint es ein Fehler zu sein
        }
    
        buf[rc] = '\0'; // Daten erfolgreich empfangen
    
        PrintToLog("Server: " + buf + "\n");
    }
    

    Die Variable quit wird dabei von außen gesetzt, also von dem anderen Thread, im Fall dass der Thread sich beenden soll.
    Es muss nicht unbedingt eine globale Variable quit sein (sollte es auch nicht), du könntest es z.B. auch so machen:

    struct ThreadData
    {
        SOCKET sock;
        bool*  quit;
    };
    
    int MeineThreadFunc( void* ptr )
    {
        ThreadData* pData = static_cast<ThreadData*>(ptr);
    
        while ( ! (*pData->quit) )
        {
            //...
        }
    }
    
    // Thread starten
    ThreadData data;
    data.sock = my_socket;
    data.quit = false;
    ThreadStarten( thread_param = &data ); // Pseudo-Code.. :)
    
    // bla bla
    
    // Thread soll sich beenden
    thread_quit = true;
    WaitForSingleObject( hThreadHandle, INFINITE );
    

    Irgendwie so halt, gibt da viele Möglichkeiten..

    WSAStartup & Cleanup:
    So wie ich das kenne, ruft man, wenn man die socket-Funktionalitäten braucht, am Anfang des Programms WSAStartup auf und ganz am Ende des Programms WSACleanup. Das Namespace-Dings was ich da gezeigt hab deklariert eine globale Variable von der Klasse. Die wird vor dem Eintritt in die main-Funktion erstellt, also wird da der Konstruktor aufgerufen und WSAStartup ausgeführt. Bei Programmende wird diese globale Variable wieder destruiert und damit wird WSACleanup aufgerufen. Alternativ kannst du eine Instanz dieser Klasse in der main-Funktion deklarieren oder einfach manuell am Anfang WSAStartup und am Ende WSACleanup aufrufen.
    Der anonyme Namespace dient dazu, dass man weder die globale Variable noch die Klasse ansprechen oder benutzen kann. Hat den Vorteil, dass es quasi eine "unsichtbare" globale Variable ist, der Benutzer muss ja auch nix davon wissen, außer dass automatisch WSAStartup und Cleanup aufgerufen wird.

    Thomas_ schrieb:

    Nochmal die Frage: Dadurch, dass ich mit einem Thread arbeite entstehen keine Komplikationen innerhalb der Funktion recvThread? Oder doch? ... ich habe mal gelesen, dass es Probleme gibt, wenn 2 Threads auf den gleichen Speicher zugreifen/ihn verändern/löschen ... und das recv nicht critical selection fähig ist ...

    Weiß ich ehrlich gesagt nicht genau. Von deinem Code jetzt müsste alles multithread-fähig sein, wie es _in_ der recv-Methode aussieht weiß ich jetzt aber nicht.
    Das Sauberste wäre meiner Meinung nach, einen eigenen Socket zum empfangen zu erstellen, der natürlich auf dem selben Port lauscht. Also einen Socket zum Senden (den du ja in main benutzt) und einen zum Empfangen, so können die sich nicht in die Quere kommen. Dass man nur mit einem Socket arbeitet der sendet _und_ empfängt, hab ich so noch nie gesehen, vielleicht weiß Scorcher was dazu 🙂



  • Der Test auf WSAEWOULDBLOCK ist doch quatsch, er setzt den Socket doch nirgendwo in den Non-Blocking Modus.



  • o.O schrieb:

    Der Test auf WSAEWOULDBLOCK ist doch quatsch, er setzt den Socket doch nirgendwo in den Non-Blocking Modus.

    Das stimmt, allerdings schadet er auch nicht - sollte er mal non-blocking arbeiten, würde er sonst vergessen, das einzubauen. Und da die Überprüfung von WSAGetLastError sowieso nur zustande kommt, wenn es einen Fehler gibt, gibt es auch keinen Verlust bei der Laufzeit. Ich jedenfalls finde den Einbezug von WSAEWOULDBLOCK wesentlich flexibler und irgendwie "allgemeiner". Ist im Endeffekt sowieso _seine_ Entscheidung und ich glaub ich schrieb auch schon, dass es im Fall der blockierenden Sockets nichts bringt.



  • Aber so wie du die WSAEWOULDBLOCK Situation behandelst wäre es ja eh "falsch". Es würde ja direkt wieder recv aufgerufen werden. Wenn man mit Non-Blocking Sockets arbeitet ruft man ja normalerweise recv erst wieder auf wenn select sagt das Daten da sind.



  • Hallo,

    Badestrand schrieb:

    o.O schrieb:

    Der Test auf WSAEWOULDBLOCK ist doch quatsch, er setzt den Socket doch nirgendwo in den Non-Blocking Modus.

    Das stimmt, allerdings schadet er auch nicht - sollte er mal non-blocking arbeiten, würde er sonst vergessen, das einzubauen. Und da die Überprüfung von WSAGetLastError sowieso nur zustande kommt, wenn es einen Fehler gibt, gibt es auch keinen Verlust bei der Laufzeit. Ich jedenfalls finde den Einbezug von WSAEWOULDBLOCK wesentlich flexibler und irgendwie "allgemeiner". Ist im Endeffekt sowieso _seine_ Entscheidung und ich glaub ich schrieb auch schon, dass es im Fall der blockierenden Sockets nichts bringt.

    das ist natuerlich Ansichtssache, aber ich bin der Meinung, dass unnoetiger
    Code nichts in den Sourcen zu tun hat.

    gruss
    v R



  • Dieser Thread wurde von Moderator/in rüdiger aus dem Forum Rund um die Programmierung in das Forum WinAPI verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.



  • Ich hab jetzt nicht alles gelesen, doch wenn du nicht auf Threads oder dergleichen zurückgreifen möchtest, kannst du ja, Windowsprogrammierung vorrausgesetzt, WSAAsyncSelect verwenden.
    Dann kümmert sich Windows um die "nichtblockerei" und du bekommst immer eine Nachricht Geschickt wenn was passiert.

    Jedoch müsstest du dich dann noch informieren wie Windows das genau macht.
    Ich selbst habe mal gelesen, dass Windows dafür selbstständig eigene Threads anlegt, jedoch habe ich woanders mal gehört, dass WSAAsyncSelect mit nonblocking Sockets arbeitet und das wolltest du ja nicht.


Anmelden zum Antworten