Mehrere TCP/IP Verbindungen



  • volkard schrieb:

    Sowas da http://highscalability.com/blog/2013/5/13/the-secret-to-10-million-concurrent-connections-the-kernel-i.html betrifft weniger als 1% fürchte ich beinahe.

    Und wenn es einen betrifft will man eh einen custom Treiber verwenden. 😉

    The kernel isn’t the solution. The kernel is the problem.

    Besser hätte ich es nicht schreiben können.



  • Das eigentliche Hauptargument gegen Thread-per-Connection ist/war, dass es zwar skalierbar sein kann, es aber Systeme gibt/gab, wo der Scheduler eben nicht mit O(1) läuft, sodass diese Lösung nicht skalierbar UND plattformunabhängig war/ist.

    Der Artikel ist ganz nett, sieht aber wie ein Horror-Szenario/Wunschdenken aus. Relativ betrachtet werden vielleicht 1% der Leute sich mit Millionen Verbindungen beschäftigen müssen, aber die absolute Zahl wird bestimmt ansteigen. Die sollen dann alle ihre eigenen Kernel-Mode-Treiber schreiben (á la Microsoft IIS' HTTP-Treiber) und lock-freie Algorithmen benutzen: allesamt extrem hoch spezialisierte Einzelbereiche. Kaum auszudenken, wie einem da die Bugs, ABA-Probleme, Deadlocks (und dank Treiber auch noch) Kernel-panics/Bluescreens um die Ohren fliegen werden.



  • TL;DR: Keiner von euch hat Erfahrung und ihr ratet alle rum.



  • mehr schrieb:

    TL;DR: Keiner von euch hat Erfahrung und ihr ratet alle rum.

    Machen das nicht alle Softwareentwickler die ganze Zeit? Erfahrung fällt nicht vom Himmel.



  • mehr schrieb:

    TL;DR: Keiner von euch hat Erfahrung und ihr ratet alle rum.

    Bei welchem Punkt?

    Aber das alle rumraten, das glaube ich nicht.



  • Jodocus schrieb:

    Die sollen dann alle ihre eigenen Kernel-Mode-Treiber schreiben (á la Microsoft IIS' HTTP-Treiber) und lock-freie Algorithmen benutzen: allesamt extrem hoch spezialisierte Einzelbereiche. Kaum auszudenken, wie einem da die Bugs, ABA-Probleme, Deadlocks (und dank Treiber auch noch) Kernel-panics/Bluescreens um die Ohren fliegen werden.

    Oder sie nutzen halt Projekte wie PF_RING. 🙄



  • Oder die 99.99% der Entwickler die Software schreiben wo es vollkommen egal ist halten einfach den Rand, anstatt dass sie versuchen ihre Kollegen zu missionieren.

    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.



  • 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.


Anmelden zum Antworten