mehrere winsock recv gleichzeitig abhandeln?



  • Die Profis machen das wohl überwiegend mit select.

    Mit Threads kann man das prinzipiell auch lösen, jedoch muss man bei Threads höllisch aufpassen dass das Programm in einem definierten Zustand bleibt. (Thread synchronisation etc.) daher nutze ich sie selber sehr ungern und greife lieber auf select zurück. Select kann gerade bei Anfängern etwas ungewöhnlich und kompliziert sein (habs selber erst nicht geschnallt) daher:

    Eine grobe Theorie wie select überhaupt funktioniert:

    Wie du bereits richtig gesagt hast ist recv() eine blockierende Funktion.
    Das geniale an select ist nun, dass du dort mehrere sockets in eine art liste reingibst, die select() überwachen soll.
    Wenn du nun select() aufrufst schaut es nach, ob auf einem der sockets die in der liste stehen etwas passiert ist. (z.b. ob Daten anliegen, oder sich jemand zu diesem socket verbinden will)

    select() teilt dir dann mit auf welchen dieser sockets etwas passiert ist, so das du dich 100%tig darauf verlassen kannst, das recv() oder accept() nicht blocken werden.

    Select() ist ebenfalls ein blocking call, der solange wartet bis auf einem der sockets etwas passiert. Du kannst jedoch einen timeout festlegen, so dass select() immer nur eine bestimmte Zeit wartet. Das ist sehr nützlich wenn man komplett auf threads verzichtet und nebenbei programminterne abläufe verarbeiten will.

    Ich hoffe ich konnte dir helfen. 😉



  • Achja, bevor ichs vergesse:

    Möglichweise kennst du die Seite schon, aber hier ist ein gutes Tutorial was select angeht:
    http://www.c-worker.ch/tuts/select.php

    Wenn du das verstanden hast, dürfte dir select keine Probleme mehr bereiten 😉



  • ThePro schrieb:

    Die Profis machen das wohl überwiegend mit select.

    Sehr gewagte Aussage... es gibt nicht nur select für das Event Dispatching.
    Simon

    Edit
    Meinte natürlich das Event Demultiplexing.



  • Schon, aber es ist wohl die portabelste.
    Für jedes Betriebssystem gibt es noch spezialisierte wrapper mit denen sich u.a. besser arbeiten lässt als mit select. Es kommt eben darauf an ob man überhaupt portabel sein will oder was man generell vor hat.

    Select ist halt ne altbewährte standardmethode die schon wunderbar klappte als es sowas wie Threads noch gar nicht gab. 😉



  • ThePro schrieb:

    Die Profis machen das wohl überwiegend mit select.

    Nein.

    ThePro schrieb:

    Für jedes Betriebssystem gibt es noch spezialisierte wrapper mit denen sich u.a. besser arbeiten lässt als mit select.

    Das sind keine Wrapper. Wenn es Wrapper wären, könnte man ja auch einfach einen portablen Wrapper selbst schreiben.



  • unter windows ist select aber die aller langsamste methode



  • @thepro:
    danke für deine antwort, habe - würd ich sagen - realtiv gute grundkentnisse in c++ ansich, winapi ist ziemlich neu für mich.
    so hab ich das mit select noch garnicht gesehen, das wäre dann ja eig ne ziemlich
    gute lösung für mein problem.
    jetzt weiß ich wenigstens auf welchen dampfer ich muss 🙂 - danke dir.

    hustbaer schrieb:

    ThePro schrieb:

    Die Profis machen das wohl überwiegend mit select.

    Nein.

    Wenn du eine Aussage schon definitiv verneinst, dann würde mich interessieren
    was du denn dann für richtig hältst.

    Wäre dieser Ablauf so einigermaßen korrekt,
    oder habe ich etwas wichtiges vergessen?

    `

    Thread 1:

    Schleife starten

    __Prüfen ob eine Verbindung am ListenSocket vorliegt

    ____Wenn Ja: ins SET eintragen (Mutex)

    Schleife beenden

    Thread 2:

    Schleife starten

    __Prüfen ob Daten an einem SET-Eintrag anliegen (Mutex)

    ____Wenn Ja: Daten empfangen

    Schleife beenden

    `

    Danke an alle, Doll



  • Der erste Thread ist überflüssig.
    Die Verbindungen kannst du im selben Thread annehmen, in dem du auch die Daten empfängst.

    Hier siehst du wie es Beispielhaft gelöst wurde:
    http://www.c-worker.ch/tuts/select.php#server

    Nachschauen, ob eine neue Verbindung angenommen werden kann.

    // acceptSocket is im fd_set? => verbindung annehmen (sofern es platz hat)
        if(FD_ISSET(acceptSocket,&fdSet)) {
          // einen freien platz für den neuen client suchen, und die verbingung annehmen
          for(i=0;i<MAX_CLIENTS;i++)
          {
            if(clients[i]==INVALID_SOCKET)
            {
              clients[i]=accept(acceptSocket,NULL,NULL);
              printf("Neuen Client angenommen (%d)\n",i);
              break;
            }
          }
        }
    

    Nachschauen ob Daten empfangen werden können

    // prüfen wleche client sockets im fd_set sind
        for(i=0;i<MAX_CLIENTS;i++)
        {
          if(clients[i]==INVALID_SOCKET)
          {
            continue; // ungültiger socket, d.h. kein verbunder client an dieser position im array
          }
          if(FD_ISSET(clients[i],&fdSet))
          {
            rc=recv(clients[i],buf,256,0);
            // prüfen ob die verbindung geschlossen wurde oder ein fehler auftrat
            if(rc==0 || rc==SOCKET_ERROR)
            {
              printf("Client %d hat die Verbindung geschlossen\n",i);
              closesocket(clients[i]); // socket schliessen         
              clients[i]=INVALID_SOCKET; // seinen platz wieder freigeben
            }
            else
            {
              buf[rc]='\0';
              // daten ausgeben und eine antwort senden
              printf("Client %d hat folgendes gesandt: %s\n",i,buf);
              // antwort senden
              sprintf(buf2,"Du mich auch %s\n",buf);
              send(clients[i],buf2,(int)strlen(buf2),0);
            }
          }
        }
    


  • doll schrieb:

    hustbaer schrieb:

    ThePro schrieb:

    Die Profis machen das wohl überwiegend mit select.

    Nein.

    Wenn du eine Aussage schon definitiv verneinst, dann würde mich interessieren
    was du denn dann für richtig hältst.

    Man verwendet üblicherweise andere de-multiplexer, wie z.B. IO Completion Ports, epoll oder kqueue.



  • hustbaer schrieb:

    doll schrieb:

    hustbaer schrieb:

    ThePro schrieb:

    Die Profis machen das wohl überwiegend mit select.

    Nein.

    Wenn du eine Aussage schon definitiv verneinst, dann würde mich interessieren
    was du denn dann für richtig hältst.

    Man verwendet üblicherweise andere de-multiplexer, wie z.B. IO Completion Ports, epoll oder kqueue.

    Gibt es diese Funktionen unter Windows überhaupt?



  • ThePro schrieb:

    Der erste Thread ist überflüssig.
    Die Verbindungen kannst du im selben Thread annehmen, in dem du auch die Daten empfängst.

    Okay, angenommen folgendes ergibt sich:

    Eine Verbindung wurde in das Set eingetragen und mit select wird
    gerade (wie in dem Beispiel) darauf gewartet dass dieser wieder lesbar wird.

    In der gleichen Zeit will der Benutzer eine neue Verbindung aufbauen.

    Dann hängt das Programm doch immer noch bei Select (solang ich keinen Timeout setze - was ja bei C-Worker auch nicht der Fall ist), und kann die neue Verbindung nicht aufbauen solange der 'alte' Socket nicht noch einmal lesbar wurde (weil der neue ja noch garnicht eingetragen ist).

    also:
    - Socket1 wird ins Set eingetragen
    - Mit Select wird darauf gewartet dass Daten am Socket1 kommen
    - Es kommen Daten am Socket1
    - Keine neuen Verbindungen, also wieder zu Select (blockiert, bis Socket1 lesbar, da ja nur Socket1 bisher im Set)
    - Jetzt versucht der Benutzer eine neue Verb. aufzubauen

    -> klappt erst, sobald select nicht mehr blockiert,
    --> und das blockiert erst nicht mehr wenn etwas am Socket1 kommt



  • Gibt es diese Funktionen unter Windows überhaupt?

    Welche?



  • Du kannst einem der drei Sets einen 'Fake'-Socket hinzufügen.
    Wenn du diesen aus einem anderen Thread mit closesocket schließt kehrt select zurück.

    Klingt zwar unsauber, aber anders wird es wohl nicht gehen.



  • MrNoname schrieb:

    hustbaer schrieb:

    doll schrieb:

    hustbaer schrieb:

    ThePro schrieb:

    Die Profis machen das wohl überwiegend mit select.

    Nein.

    Wenn du eine Aussage schon definitiv verneinst, dann würde mich interessieren
    was du denn dann für richtig hältst.

    Man verwendet üblicherweise andere de-multiplexer, wie z.B. IO Completion Ports, epoll oder kqueue.

    Gibt es diese Funktionen unter Windows überhaupt?

    IO Completion Ports ja, die anderen nein. Ich habe auch nicht behauptet dass diese Interfaces "portabel" wären. Man kann allerdings Wrapper um diese de-multiplexer bauen, so dass man auf allen Systemen das selbe Wrapper-Interface verwenden kann.



  • doll schrieb:

    ThePro schrieb:

    Der erste Thread ist überflüssig.
    Die Verbindungen kannst du im selben Thread annehmen, in dem du auch die Daten empfängst.

    Okay, angenommen folgendes ergibt sich:

    Eine Verbindung wurde in das Set eingetragen und mit select wird
    gerade (wie in dem Beispiel) darauf gewartet dass dieser wieder lesbar wird.

    In der gleichen Zeit will der Benutzer eine neue Verbindung aufbauen.

    Dann hängt das Programm doch immer noch bei Select (solang ich keinen Timeout setze - was ja bei C-Worker auch nicht der Fall ist), und kann die neue Verbindung nicht aufbauen solange der 'alte' Socket nicht noch einmal lesbar wurde (weil der neue ja noch garnicht eingetragen ist).
    (...)

    Der "neue" ist noch nicht eingetragen, aber der Socket über den du Verbindungen annehmen möchtest, sollte eingetragen sein.
    D.h. in deinem Beispiel hast du 1x den "listen socket" und 1x den "socket for connection 1" im FD_SET.

    select() kommt dann zurück, sobald entweder der "socket for connection 1" lesbar/schreibbar wird, ODER eine neue Verbindung am "listen socket" eingeht.
    Lies dir die Doku zu select() durch, da sollte genau drin stehen, welche Bedingungen/Ereignisse für welches der drei Sets "gelten", so dass select() zurückkommt.

    p.S.: damit das gut funktioniert, sollte man den "listen socket" auch im non-blocking Modus betreiben (die "connection" Sockets sowieso). Sonst kann es einem passieren, dass listen() meldet es wäre eine Verbindung da, nur bis man accept() aufruft, ist die Gegenstelle futsch (antwortet nichtmehr oder hat schon ein RST geschickt). Dann würde accept() nämlich blockieren, und das wäre weniger gut. Im non-blocking Mode bekommt man in dem Fall von accept() einfach einen EWOULDBLOCK, und alles ist gut.



  • falls du "C++ von A bis Z" noch zur hand hast: darin wird (wenn ich mich noch recht erinnere) ausführlich auf select eingegangen und an einem tcp-client-server beispiel demonstriert...



  • hustbaer schrieb:

    select() kommt dann zurück, sobald entweder der "socket for connection 1" lesbar/schreibbar wird, ODER eine neue Verbindung am "listen socket" eingeht.

    super, jetzt macht das endlich sinn 😃



  • Ahh sorry ich hatte nur das "Jetzt versucht der Benutzer eine neue Verb. aufzubauen" gelesen und daraus geschlossen das du einen Client programmierst wo der Benutzer dynamisch Verbindungen herstellen kann.

    Für einen Server macht mein Vorschlag mit dem Fake-Socket dann keinen Sinn.



  • ------ schrieb:

    Ahh sorry ich hatte nur das "Jetzt versucht der Benutzer eine neue Verb. aufzubauen" gelesen und daraus geschlossen das du einen Client programmierst wo der Benutzer dynamisch Verbindungen herstellen kann.

    Für einen Server macht mein Vorschlag mit dem Fake-Socket dann keinen Sinn.

    danke trotzdem, für deine mühe! und aller anderen natürlich 😉


Anmelden zum Antworten