Mehrere TCP/IP Verbindungen



  • Wären 2 Threads pro Client nicht angebrachter, da TCP Sockets full duplex sind?



  • hustbaer schrieb:

    Mein Vorschlag wäre pro Verbindung einen Thread zu nehmen, und dann alles mit synchronem IO zu machen.
    Super einfach und wer behaupten möchte dass es von der Performance her schlecht ist, der soll es mir erstmal beweisen 😉

    Danke, da ich bisher wenig mit Threads gearbeitet habe. Kannst du es mir bitte etwas genauer erläutern?

    Gruß



  • socket = socket()
    bind(socket)
    listen(socket)
    while(true) {
       new_socket = accept(socket)
       new_thread(new_socket);
    }
    
    void new_thread(socket) {
       recv(socket);
       send(socket);
       ...
       closesocket(socket);
    }
    


  • Danke für die Antwort.

    Achso logich der Thread bleibt beim ersten resv hängen aber dafür wird dann der nachfolgende Thread ausgeführt.

    kapiert!



  • ^ Oder ?

    Also wenn anschließend ein zweiter Thread kommen würde. Würde dann der erste bei resv einen blocking call erhalten und der nächste würde ausgeführt werden?

    Danke Gruß



  • Der Hauptthread empfängt neue Clients und deligiert sie in neue Threads, die dann blocken/whatever machen. Er selber blockiert nur während er auf neue Clients wartet, sonst nicht. Falls doch, dann würden halt Clients nicht mehr akzeptiert werden und früher oder später abgewiesen.



  • hustbaer schrieb:

    Ich möchte nicht wissen wie viel Geld vernichtet wurde durch schlechte Ratschläge wie "mach mal asynchron weil sonst is voll langsam". Und dann sitzt Otto-Normalprogrammierer da und programmiert sich nen Ast ab für den < 1 call/second Server. Was sonst in 10 Minuten erledigt gewesen wäre.

    Ach, aber "Otto" kommt von selbst auf die Idee einen neuen Thread für die Verbindung zu erstellen?
    "Otto" hat typischerweise auch keine Ahnung von Multi-Threading und produziert fröhlich Abstürze. Ich möchte nicht wissen, wie viel Geld seiner Kunden dadurch vernichtet wurde.
    Ich halte diese Otto-Normalidiotenbeispiele für schädlicher als die guten Ratschläge. Die zementieren nur die Don't-Care-Mentalität, die ohnehin schon zu präsent ist. Wenn man von seinen Mitmenschen erwartet, dass sie sich idiotisch verhalten, werden so das auch tun.
    Sinnvoller fände ich es den Otto mal grundsätzlich über Nebenläufigkeit aufzuklären. Wenn er verstanden hat, was alles möglich ist, kann er gerne einen Thread pro Client verwenden. Wenn man ihm zusäzlich erklärt hat, wie man Software-Komponenten voneinander trennt, kann er später problemlos seinen Thread-Ansatz überarbeiten, falls es mal erforderlich werden sollte.



  • Hier mal ein kleiner Echo-Server (für Linux), der pro Verbindung einen neuen Thread aufmacht:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <iostream>
    #include <thread>
    #include <vector>
    
    int main() {
        using namespace std;
        int listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(listen_socket < 0) {
            cerr << "socket() failure\n";
            return -1;
        }
    
        sockaddr_in saddr{ };
        saddr.sin_port = htons(40001);
        saddr.sin_family = AF_INET;
        saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
        if(bind(listen_socket, reinterpret_cast<sockaddr*>(&saddr), sizeof saddr) < 0) {
            cerr << "bind() failure\n";
            close(listen_socket);
            return -2;
        }
    
        if(listen(listen_socket, SOMAXCONN) < 0) {
            cerr << "listen() failure\n";
            close(listen_socket);
            return -3;
        }
    
        for(;;) {
            sockaddr_in client_addr{ };
            socklen_t addr_len = sizeof client_addr;
            int client_socket = accept(listen_socket, reinterpret_cast<sockaddr*>(&client_addr), &addr_len);
            if(client_socket < 1) {
                cerr << "accept() failure\n";
            }
            else { 
                thread([=]() {
                    vector<char> buffer(512);
                    int result = 0;
                    do {
                       result = recv(client_socket, &buffer[0], buffer.size(), 0);
                       if(result < 0) {
                          cerr << "recv() failure\n";
                          close(client_socket);
                          return;
                       }
                       else if(result != 0) {
                           if(send(client_socket, &buffer[0], result, 0) < 0) {
                              cerr << "send() failure\n";
                              close(client_socket);
                              return;
                           }
                       }
                       else {
                           close(client_socket);
                           return;
                       }
                   } while(true);
                }).detach();
            }
        }
    }
    

    Edit: Trotzdem sei dir schwer geraten, eine Library zu benutzen. Dann musst du dich auch nicht mit Plattformabhängigkeit und I/O-Strategien wie 1 Thread pro Verbindung etc. rumplagen.



  • TyRoXx schrieb:

    Ach, aber "Otto" kommt von selbst auf die Idee einen neuen Thread für die Verbindung zu erstellen?

    Jupp, klar.



  • Jodocus schrieb:

    Edit: Trotzdem sei dir schwer geraten, eine Library zu benutzen. Dann musst du dich auch nicht mit Plattformabhängigkeit und I/O-Strategien wie 1 Thread pro Verbindung etc. rumplagen.

    Jop.
    Boost.Asio ist z.B. nicht grundsätzlich böse. Man muss nur "As" Teil ignorieren 🕶



  • Jodocus schrieb:

    Hier mal ein kleiner Echo-Server (für Linux), der pro Verbindung einen neuen Thread aufmacht:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <iostream>
    #include <thread>
    #include <vector>
    
    int main() {
        using namespace std;
        int listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(listen_socket < 0) {
            cerr << "socket() failure\n";
            return -1;
        }
    
        sockaddr_in saddr{ };
        saddr.sin_port = htons(40001);
        saddr.sin_family = AF_INET;
        saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
        if(bind(listen_socket, reinterpret_cast<sockaddr*>(&saddr), sizeof saddr) < 0) {
            cerr << "bind() failure\n";
            close(listen_socket);
            return -2;
        }
        
        if(listen(listen_socket, SOMAXCONN) < 0) {
            cerr << "listen() failure\n";
            close(listen_socket);
            return -3;
        }
    
        for(;;) {
            sockaddr_in client_addr{ };
            socklen_t addr_len = sizeof client_addr;
            int client_socket = accept(listen_socket, reinterpret_cast<sockaddr*>(&client_addr), &addr_len);
            if(client_socket < 1) {
                cerr << "accept() failure\n";
            }
            else { 
                thread([=]() {
                    vector<char> buffer(512);
                    int result = 0;
                    do {
                       result = recv(client_socket, &buffer[0], buffer.size(), 0);
                       if(result < 0) {
                          cerr << "recv() failure\n";
                          close(client_socket);
                          return;
                       }
                       else if(result != 0) {
                           if(send(client_socket, &buffer[0], result, 0) < 0) {
                              cerr << "send() failure\n";
                              close(client_socket);
                              return;
                           }
                       }
                       else {
                           close(client_socket);
                           return;
                       }
                   } while(true);
                }).detach();
            }
        }
    }
    

    Edit: Trotzdem sei dir schwer geraten, eine Library zu benutzen. Dann musst du dich auch nicht mit Plattformabhängigkeit und I/O-Strategien wie 1 Thread pro Verbindung etc. rumplagen.

    Danke für die Antwort und das Beispiel. Aber ist es hier nicht so das alle Verbindungen über den gleichen Port laufen?

    Gruß



  • @volkard

    Beachte, daß http-serven trivial ist. Apache ist im Prinzip ein single-Threaded Nacheinanderausführer.

    aber auch nicht mehr wenn du 1000 Benutzer auf deiner Seite hast - irgendwann sieht das ganze - wenn man die Sockets betrachtet auch nicht mehr anders aus als ein stark frequentierter Chat-Server

    @hustbear

    heisse luft
    wo sind deine zahlen?
    wo sind die sources mit denen man die benchmarks selbst wiederholen kann?

    sind leider nur Erfahrungswerte aus meiner berufliche Praxis - habe dazu gerade keine "Studie" vorliegen, wenn du andere Erfahrungen gemacht hast würde ich mich gerne von deinen Lösungen hören - teilweise muss meine Software auch auf kleinen embedded Systemen laufen, deswegen achte ich sehr stark auf Skalierung oder auch die "Belastung" welche ganz klar von System zu System anders gelagert ist

    100 µs sind schon einmal die richtige Größenordnung. Invertiert sind das gerade einmal 10 kHz. Maximal gehen damit also 10000 Verbindungen pro Sekunde und Hardware-Thread.

    nicht zu vergessen wieviel Zeit dein Scheduler deinem Prozess überhaupt zuspricht und deine Threads sich um die Sub-Zuteilung schlagen (was mehr ist als die pure Switch-Zeit), plus die Contet-Switch-Cache-Misses und,und,und

    für seinen Chat-Server hier reicht aber wie gesagt sicherlich die Thread-Lösung



  • Brause schrieb:

    Danke für die Antwort und das Beispiel. Aber ist es hier nicht so das alle Verbindungen über den gleichen Port laufen?

    Jo, das wäre normal. Auf Port 40001 bietet dieser Server seine Dienste an.
    Die Verbindungen (bestehend aus je zwei Ports und zwei Adressen) sind dann unabhängig.



  • volkard schrieb:

    Brause schrieb:

    Danke für die Antwort und das Beispiel. Aber ist es hier nicht so das alle Verbindungen über den gleichen Port laufen?

    Jo, das wäre normal. Auf Port 40001 bietet dieser Server seine Dienste an.
    Die Verbindungen (bestehend aus je zwei Ports und zwei Adressen) sind dann unabhängig.

    Danke für die Antwort! War keine schlaue frage klar gibt ja zweit Sockets auf Server Seite!! Hatte einen denkfehler.

    Bei mir sieht es nämlich etwas anders aus. Da die angesprochenen Verbindungen Maschinen sind die komplett autonom sind ist meine Idee das die Maschinen jeweils der Server sind und ich der Client. Also das mein Programm jeweils einen Client für jede Maschine zur Verfügung stellt. Wie würde man das am besten mit Threads realisieren?

    Danke Gruß



  • Gast3 schrieb:

    @hustbear

    heisse luft
    wo sind deine zahlen?
    wo sind die sources mit denen man die benchmarks selbst wiederholen kann?

    sind leider nur Erfahrungswerte aus meiner berufliche Praxis - habe dazu gerade keine "Studie" vorliegen, wenn du andere Erfahrungen gemacht hast würde ich mich gerne von deinen Lösungen hören

    Ja, ich hab die Erfahrung gemacht dass es so-gut-wie egal ist ob man jetzt 10 oder 1000 Threads hat - so lange nicht mehr gleichzeitig laufen wollen als man physikalische CPUs hat merkt man keinen Unterschied.

    Vielleicht hast du noch mit einem Kernel aus Grossvaters Zeiten gearbeitet wo das Suchen eines aufzuweckenden Threads noch O(N) war?



  • Ja, ich hab die Erfahrung gemacht dass es so-gut-wie egal ist ob man jetzt 10 oder 1000 Threads hat - so lange nicht mehr gleichzeitig laufen wollen als man physikalische CPUs hat merkt man keinen Unterschied.

    und was ist wenn die gleichzeitigen deine physikalische CPU-Anzahl (du meinst Hardware-Threads?) per se überschreiten?
    denn erst da wird ja die Skalierbarkeit der Software überhaupt wichtig
    und eher das Szenario das ich beschrieben habe

    dieses "so lange nicht mehr gleichzeitig laufen wollen" ist genau das Skalierungsproblem an dem Thread-per-Client Prinzip - entweder beschränkt man die Client-Anzahl und/oder man bremst die "gleichzeitige" Kommunikation der Teilnehmer - beides könnte irgendwie den Sinn eines Chat-Server beschränken
    - und ist auch stark davon abhängig wieviel Daten über den Server zwischen den Clients ausgetauscht werden, weil dann ja auch der Synchronisationsaufwand steigt

    wenn die Client-Anzahl - also die parallel zu behandelnden Aufgaben gering ist oder die Durchsatzminderung bei
    steigender Clientanzahl irgendwie egal (unbewertet) ist - und auch die Systemressourcen da mitspielen kann man Threads leicht und einfach benutzen

    eines meiner Projekte: eine Win7/Linux 3.x,x64-Software welche mit ~230 embedded-Steuerungen über TCP-IP kommuniziert, lesend/schreibend - ~3-50ms, 100-500Byte Pakete, permanent, (fast) keine Allokationen aber Synchronisation zwischen Client-Daten - die Software ist da so eine Art Chat-Server-Message-Router - Linux macht es besser (aber Kunde mag leider Windows)

    es gab aber auch schon die Frage ob man nicht auch 3000 solcher embedded Systeme über so einen Server kommunizieren zu lassen - da musst dann aber wirklich mehr CPU,RAM,Netzwerke und Linux rein

    du hast wenigstens deine Einschränkung mit dem "so lange nicht mehr gleichzeitig laufen wollen" gebracht - manch andere Post hier könnte man teilweise so verstehen als wenn man immer alles mit Threads machen könnte

    Vielleicht hast du noch mit einem Kernel aus Grossvaters Zeiten gearbeitet wo das Suchen eines aufzuweckenden Threads noch O(N) war?

    naja so alt bin ich auch wieder nicht - habe aber noch Väterchen-Win2K-Software die Flausen ausgetrieben mit Threads skalieren zu wollen - und auch noch mit 16Bit-Interrupts "Threading" betrieben 🙂

    aber ich arbeite auch in einem Bereich in dem die Systemressoucen eines Win/Linux Industrie-PCs oder embedded-System wie RAM,CPU usw. nicht wie Kamelle
    vom Wagen geworfen werden wenn die Software schreit



  • Oh Mann, Gast3, bitte laber nicht.

    Gast3 schrieb:

    du hast wenigstens deine Einschränkung mit dem "so lange nicht mehr gleichzeitig laufen wollen" gebracht - manch andere Post hier könnte man teilweise so verstehen als wenn man immer alles mit Threads machen könnte

    Ja. Die Einschränkung ist das damit nicht irgend ein Schlaumeier daherkommt und das offensichtliche bekrittelt, nämlich dass irgendwas langsamer werden muss wenn man mehr Rechenzeit verbrauchen will als die Maschine mit allen Cores in Summe hat. Was logisch ist. Die Threading-Lösung wird aber auch mit mehr Threads die laufen wollen nicht automatisch sehr ineffizient.

    Deine Behauptung von "bei mehr als 300 wirds voll langsam" kann ich überhaupt nicht nachvollziehen.



  • Gast3 schrieb:

    Vielleicht hast du noch mit einem Kernel aus Grossvaters Zeiten gearbeitet wo das Suchen eines aufzuweckenden Threads noch O(N) war?

    naja so alt bin ich auch wieder nicht - habe aber noch Väterchen-Win2K-Software die Flausen ausgetrieben mit Threads skalieren zu wollen - und auch noch mit 16Bit-Interrupts "Threading" betrieben 🙂

    so lange ist das auch nicht her



  • manni hat recht, wenn es geht dann boost::asio



  • Deine Behauptung von "bei mehr als 300 wirds voll langsam" kann ich überhaupt nicht nachvollziehen.

    dann muss ich mich korrigieren - ich meinte eher ab einer Menge X in Abhängigkeit zur Synchronisationshäufigkeit der Threads kann es passieren das die deinem Prozess zugeteilten CPU Häppchen so klein werden das die eigentliche Thread-Arbeit und das Drumherum aus dem Gleichgewicht gerät... und ich kenne auch einfach keine Systeme die stark das Thread-per-Client Prinzip verwenden - ausser einfache Threadpools die meist auch im untern 2-stelligen Bereich agieren, ich sehe einfach nur sehr selten Tools die viel asynchrone Operation tätigen - DB, Server etc. und dabei permanent mit ein paar hundert Threads rumorgeln (Asynchronität an sich sehe ich genug - eigentlich nur)

    Boost.Asio fasst meine Erfahrungen schön zusammen

    Implementation strategies such as thread-per-connection (which a synchronous-only approach would require) can degrade system performance, due to increased context switching, synchronisation and data movement among CPUs. With asynchronous operations it is possible to avoid the cost of context switching by minimising the number of operating system threads — typically a limited resource — and only activating the logical threads of control that have events to process.

    oder teilweise Infos aus: http://stackoverflow.com/questions/14317992/thread-per-connection-vs-reactor-pattern-with-a-thread-pool

    wobei du das ja auch "...so lange nicht mehr gleichzeitig laufen wollen als man physikalische CPUs hat merkt man keinen Unterschied..." so siehst

    mag einfach keine Threads=Asychron-Problem-Lösungs-Aussagen, und nachher leidet ein weiteres Programm unter Thread-Lethargie weil einer wieder mal gedacht hat damit könnte man die "Kernenergie" viel leichter nutzbar machen - was einfach so nicht stimmt


Anmelden zum Antworten