Gleicher Code, unterschiedliches Ergebnis



  • Und zwar hänge ich gerade vor VS und habe das Problem, dass der Code in meinem "Temp" Projekt ohne Fehler läuft. Kopiere ich diesen in ein anderes (frisch erstelltes) Projekt, bekomme ich die folgenden Fehler ausgespuckt:

    Fehler	C2672	"std::invoke": keine übereinstimmende überladene Funktion gefunden
    Fehler	C2893	Funktionsvorlage "unknown-type std::invoke(_Callable &&,_Types &&...) noexcept(<expr>)" konnte nicht spezialisiert werden
    

    Hier erstmal mein Code dazu:

    #include <iostream>
    #include <string>
    #include <thread>
    #include <chrono>
    #include <vector>
    #include <boost/asio.hpp>
    
    
    void test(size_t id, boost::asio::ip::tcp::socket &sock) {
    
    
    }
    
    int main() {
    
    	std::vector<std::thread> connections;
    
    
    	boost::asio::io_service io_service;
    	boost::asio::ip::tcp::acceptor acceptor(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 18));
    
    	while (true) {
    		size_t userCounter = 0;
    
    		boost::asio::ip::tcp::socket sock(io_service);
    		acceptor.accept(sock);
    
    		connections.push_back(std::thread(test, userCounter, std::move(sock)));
    		connections[connections.size() - 1].detach();
    
    		userCounter++;
    	}
    }
    

    Boost habe ich selbstverständlich bei den Include-Verzeichnissen hinzugefügt und bei den Linkern eingetragen.

    Nun ist natürlich die Frage, warum er mir die Fehler ausspuckt und nicht kompiliert. Was denkt ihr dazu?



  • Die Signatur von test passt nicht. Sie solte so aussehen:

    void test(size_t id, boost::asio::ip::tcp::socket sock) {}

    Das macht auch mehr Sinn, denn nach dem annehmen der Verbindung möchtest Du das Socket an test übergeben, welches dann auf einem separaten Thread die Kommunikation erledigt. Die wegen dem Move zurückbleibende Socket-Hülle ist gemäss Boost.Asio wieder bereit, um eine neue Verbindung anzunehmen.



  • Vielen Dank für die Antwort! Ja, da hast du Recht. Das macht Sinn. Zwar werde ich nie verstehen warum es in dem anderen Projekt compiled (auch wenn ich die Referenz lösche, compile und diese dannach wieder davor schreibe und compile) und in diesem nicht, aber egal.

    Ich habe im Übrigen weiter dran gebastelt. Fällt dir (oder jemand anderem) noch eine saubere Variante ein, wie ich hier eine Nachricht broadcasten kann? Meine Lösung ist wirklich sehr sehr unschön. Zwar werde ich alles noch in eine Klasse verschieben und die globalen Variablen ordentlich umsetzen, jedoch sind die while-Schleifen mit den sleep-timern meiner Meinung nach dirty code. Das geht zu 100% besser.

    Hier mein Code:

    #include <iostream>
    #include <string>
    #include <thread>
    #include <chrono>
    #include <vector>
    #include <boost/asio.hpp>
    
    
    
    std::string msg = "";
    size_t count = 0;
    size_t userCounter = 0;
    
    
    
    void conThread(size_t id, boost::asio::ip::tcp::socket sock) {
    
    	try {
    		while (true) {
    			if (id == count) {
    				std::string tmp;
    				tmp.clear();
    				std::array<char, 1024> buffer;
    				buffer.fill(0);
    				size_t len = sock.read_some(boost::asio::buffer(buffer, sizeof(buffer)));
    				for (size_t i = 0; i < len; i++) {
    					tmp += buffer[i];
    				}
    
    				msg = tmp;
    				std::this_thread::sleep_for(std::chrono::seconds(1));
    				count++;
    				if (count > (userCounter - 1)) { count = 0; }
    			}
    			else {
    
    
    				while (id != count) {
    
    					std::string backMsg = msg;
    					while ((backMsg == msg) && (id != count)) {
    
    						std::this_thread::sleep_for(std::chrono::milliseconds(100));
    					}
    
    					if (id != count) {
    						boost::asio::write(sock, boost::asio::buffer(msg, msg.length()));
    						backMsg = msg;
    					}
    				}
    			}
    		}
    	}
    	catch (std::exception &myExc) {
    		std::cout << myExc.what() << std::endl;
    	}
    }
    
    
    int main() {
    
    	try {
    		std::vector<std::thread> connections;
    
    		boost::asio::io_service io_service;
    		boost::asio::ip::tcp::acceptor acceptor(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 18));
    
    
    		while (true) {
    			boost::asio::ip::tcp::socket sock(io_service);
    			acceptor.accept(sock);
    
    			connections.push_back(std::thread(conThread, userCounter, std::move(sock)));
    			connections[connections.size() - 1].detach();
    
    			userCounter++;
    		}
    	}
    	catch (std::exception &myEx) {
    		std::cout << myEx.what() << std::endl;
    	}
    }
    


  • Guten Morgen!

    Ich habe es nun selbst geschafft, auf dem Weg, den ich für wesentlich sauberer halte. Sicher kann man hier und da Dinge hübscher machen, aber ich glaube so als Basis und für einen Anfänger ist es schon ok.

    #include <iostream>
    #include <string>
    #include <thread>
    #include <chrono>
    #include <vector>
    #include <boost/asio.hpp>
    
    
    std::vector<boost::asio::ip::tcp::socket> sockets;
    
    
    void conThread(size_t id) {
    
    	try {
    		while (true) {
    
    			std::string tmp;
    			tmp.clear();
    			std::array<char, 1024> buffer;
    			buffer.fill(0);
    			size_t len = sockets[id].read_some(boost::asio::buffer(buffer, sizeof(buffer)));
    			for (size_t i = 0; i < len; i++) {
    				tmp += buffer[i];
    			}
    
    			for (std::vector<boost::asio::ip::tcp::socket>::iterator it = sockets.begin(); it != sockets.end(); it++) {
    				boost::asio::io_service io_service;
    				boost::asio::ip::tcp::socket tempSock(io_service);
    				tempSock = std::move(*it);
    				boost::asio::write(tempSock, boost::asio::buffer(tmp, tmp.length()));
    				*it = std::move(tempSock);
    			}
    		}
    	}
    	catch (std::exception &myExc) {
    		std::cout << myExc.what() << std::endl;
    	}
    }
    
    
    int main() {
    
    	try {
    		std::vector<std::thread> connections;
    		size_t userCounter = 0;
    
    		boost::asio::io_service io_service;
    		boost::asio::ip::tcp::acceptor acceptor(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 21));
    		
    
    		while (true) {
    			boost::asio::ip::tcp::socket sock(io_service);
    			acceptor.accept(sock);
    
    			sockets.push_back(std::move(sock));
    			connections.push_back(std::thread(conThread, userCounter));
    			connections[connections.size() - 1].detach();
    
    			userCounter++;
    			std::cout << "Player " << userCounter << " is joined!" << std::endl;
    		}
    	}
    	catch (std::exception &myEx) {
    		std::cout << myEx.what() << std::endl;
    	}
    }
    

    Da ich ich nur als Hobby programmiere und dadurch kein weiteres Wissen zu der Thematik habe, möchte ich euch noch eine abschließende Frage stellen: Wie nennt man denn eigentlich, das Prinzip, was ich hier zusammengeschustert habe? Kann man es als "Multithreaded Synchronous Server" bezeichnen?



  • Weiß keiner die Antwort darauf oder gibt es keinen Name dafür?



  • BIst du sicher, das es wirklich kompiliert hat? Hast du im obj oder der exe kontrolliert, ob der Code enthalten war? In der std ist vieles mit Templates gelöst, da kann man sich nie sicher sein das etwas kompiliert ist, nur weil der Kompiler durchgelaufen ist.



  • Haha, das mein ich doch garnicht 🙂
    Mir ging es jetzt eher darum, wie man das Konstrukt nennt was ich da gebaut habe. Ist es ein MultiThreaded Synchronous Server? Oder etwas anderes?



  • Ok, dann halt nicht.



  • Hast du vielleicht eine Antwort auf meine Frage?



  • Zu deiner Namenswahl. Ich persönlich finde das irgendwie etwas hochtrabend. Du hast ein kleines Stückchen Code, das TCP/IP Verbindungen herstellt. Ein Server, der das in einem Prozess macht und immer nur eine Verbindung nach einander abarbeitet gibt es nicht => Alles was irgendwie ein Server darstellt ist Multithreaded.
    Und was genau soll synchron sein? Die Verbindungen werden parallel abgarbeitet, je nach dem wie sie zur stande kommen. Das ist eher die Definition von Asynchron.



  • Vielen Dank Schlangenmesch. Genau deshalb habe ich gefragt. Ich habe es nämlich so verstanden, dass asynchron bedeutet, die Verbindungen werden nacheinander abgearbeitet "in einem Prozess" und synchron, wenn sie wie im Beispiel über Threads gleichzeitig am Leben erhalten werden :). Wenn ich es richtig verstanden habe gibt es also auch garkein synchron.



  • @schlangenmensch sagte in Gleicher Code, unterschiedliches Ergebnis:

    Ein Server, der das in einem Prozess macht und immer nur eine Verbindung nach einander abarbeitet gibt es nicht

    Klar gibt's sowas. Sollte es vielleicht nicht geben, aber gibt's genug.

    => Alles was irgendwie ein Server darstellt ist Multithreaded.

    Das ist Bullshit 1. Güte. Es gibt genug Server die forken oder non-blocking IO verwenden statt mit mehreren Threads zu arbeiten.

    Und was genau soll synchron sein?

    Synchron vom Socket lesen/reinschreiben ist synchron. Asynchron wäre asynchron.

    Die Verbindungen werden parallel abgarbeitet, je nach dem wie sie zur stande kommen. Das ist eher die Definition von Asynchron.

    Blah.



  • Ok sry, aber dann hab ichs doch nicht verstanden. Kann jemand nochmal mit eigenen Worten und vielleicht einem Beispiel erklären wo der Unterschied zwischen synchron und asynchron bei Netzwerken ist?



  • Angenommen du willst Daten in den Socket schreiben. Dazu rufst du irgend eine Funktion auf. Synchron würde typischerweise heissen die Funktion die du aufrufst kommt erst zurück wenn der IO Vorgang abgeschlossen ist. (Die Daten müssen noch nicht über's Kabel raus sein, aber er ist abgeschlossen in dem Sinn dass das aufrufende Programm sich nicht weiter darum kümmern muss, seine Puffer freigeben kann etc.)

    Asynchron würde heissen die Funktion kommt bereits zurück bevor der Vorgang abgeschlossen ist (typischerweise sofort), und der Vorgang läuft im Hintergrund weiter. D.h. die Anwendung muss warten bis sie irgendwie die Information erhält dass der Vorgang abgeschlossen ist, und erst dann kann sie den Puffer mit den Daten freigeben oder überschreiben. Wie die Anwendung an diese Information kommt ist unterschiedlich je nach Technologie. Üblich sind Callbacks, gibt aber auch Systeme die Messages schicken bzw. auch die Möglichkeit dass Synchronisierungsobjekte wie Events oder Condition-Variablen verwendet werden.

    Ob man Threads verwendet ist dabei egal, synchron und asynchron beziehen sich immer auf einen Aufruf einer IO Funktion, und einen Aufruf kann immer nur ein Thread machen, und aus dessen Sicht ist der Aufruf dann eben synchron oder asynchron - egal wie viele Threads gleichzeitig andere IO Aufrufe machen.

    Ob man andere Verfahren wie non-blocking IO verwendet ist dabei auch egal. Bei non-blocking IO wird ja nur verhindert dass eine IO Funktion blockiert, aber wenn die Funktion zurückkommt ist der Aufruf auch immer abgeschlossen. Wenn der Aufruf nicht direkt erledigt werden kann dann schlägt die Funktion halt einfach fehl, aber abgeschlossen ist die Sache damit genau so. (Theoretisch könnte man schon non-blocking und asynchron kombinieren, aber es macht keinen Sinn, da asynchron und non-blocking das selbe Problem lösen, nämlich dass IO Aufrufe blockieren. Wenn man es über den einen Mechanismus löst, macht es wenig Sinn den anderen Mechanismus auch noch zu bemühen. Und weil es so wenig Sinn macht bin ich mir nichtmal sicher ob überhaupt alle IO Implementierungen diese Kombination unterstützen.)



  • Mir ging es jetzt eher darum, wie man das Konstrukt nennt was ich da gebaut habe. Ist es ein MultiThreaded Synchronous Server? Oder etwas anderes?

    Vorausgesetzt du verwendest in den Threads dann auch synchronen IO...
    Kannst du so nennen wenn du magst. Was ich für genau diese Art Server öfter als Bezeichnung/Beschreibung gehört/gelesen habe ist "one thread per client". Jemand der sich mit Server-Programmierung auskennt sollte dann mMn. sofort wissen was gemeint ist, ohne darüber nachdenken zu müssen was jetzt ein "multithreaded synchronous server" sein könnte.



  • @hustbaer sagte in Gleicher Code, unterschiedliches Ergebnis:

    Das ist Bullshit 1. Güte. Es gibt genug Server die forken oder non-blocking IO verwenden statt mit mehreren Threads zu arbeiten.

    Zumindest unter Linux macht es für den Kernel (fast) keinen Unterschied ob es sich um ein Prozess oder ein Thread handelt...

    Synchron vom Socket lesen/reinschreiben ist synchron. Asynchron wäre asynchron.

    Blah... ob ich (A)synchronität auf IO beziehe oder auf eine Funktion die eine Verbindung asynchron abarbeitet macht für die Wortwahl von (A)synchronität genau keinen Unterschied.

    Die Beschreibung "One thread per client" ist aber natürlich passend.



  • Ok vielen dank. Also ist mein Beispiel zwar multithreaded, aber synchron. Denn jeder Thread wartet immer beim read bis etwas ankommt und so lange ist der thread blocked.

    Asynchronität lohnt sich also zum Beispiel beim streamen oder VoiceChats. Dort braucht man ja keine Rückmeldung und die Gegenstelle muss auch nicht auf die Informationen reagieren.

    Synchronität wäre zum Beispiel sinnvoll für eine Telnet Verbindung. Wenn man zum Beispiel möchte, dass auf einem Rootserver etwas ausgeführt oder abgefragt wird.

    So habe ich es zumindest jetzt verstanden.



  • @schlangenmensch sagte in Gleicher Code, unterschiedliches Ergebnis:

    @hustbaer sagte in Gleicher Code, unterschiedliches Ergebnis:

    Das ist Bullshit 1. Güte. Es gibt genug Server die forken oder non-blocking IO verwenden statt mit mehreren Threads zu arbeiten.

    Zumindest unter Linux macht es für den Kernel (fast) keinen Unterschied ob es sich um ein Prozess oder ein Thread handelt...

    Hm. Sieht fast so aus als ob du in dem Punkt Recht hättest. Ich verband den Begriff Multithreading klar mit mehreren Threads im selben Prozess. Andere (und nicht nur du) sehen das aber anscheinend ganz anders 🙂

    Was non-blocking IO angeht bleibe ich aber bei meiner Meinung. Das hat weder was mit Multithreading zu tun noch ist es etwas worauf ich das Wort "asynchron" anwenden würde.

    Synchron vom Socket lesen/reinschreiben ist synchron. Asynchron wäre asynchron.

    Blah... ob ich (A)synchronität auf IO beziehe oder auf eine Funktion die eine Verbindung asynchron abarbeitet macht für die Wortwahl von (A)synchronität genau keinen Unterschied.

    Der Code der synchronous, non-blocking IO Server die ich bisher gesehen habe, hat nichts was ich primär als asynchron bezeichnen würde. Z.B. würde ich ein Spiel auch nicht als "asynchron" Bezeichnen, nur weil sich mehrere Sprites "gleichzeitig" über den Bildschirm bewegen, und der Game Loop immer für jedes Sprite ein kleines Stück der Animation macht und dann wieder zum nächsten wechselt. Wenn das Spiel allerdings ein Framework verwendet wo es eine "BeginMove" Funktion gibt die man aufrufen kann, und das Framework kümmert sich dann darum dass das Sprite zu wandern anfängt...
    Wenn es dann funktioniert dass das Sprite wandert, und zwar unabhängig davon wie lange man den Thread blockiert der "BeginMove" aufgerufen hat, dann würde ich das asynchron nennen.

    Genau das selbe bei Servern: non-blocking IO ist wie das Stückweise Weiterschieben mehrerer Sprites in einem Game-Loop - Stückchenweises Lesen/Schreiben von Daten auf mehreren Verbindungen in einem Server-Loop. Asynchron dagegen ist "BeginRead" bzw. "BeginMove", und dann läuft das nebenher, egal was man in dem Thread der den IO gestartet hat danach anstellt.

    Klar erreicht man damit das selbe. Die Frage ist aber wie der Code aussieht den man selbst schreibt, also der zu der Anwendung gehört. Und der sieht halt bei "one thread per client", "non-blocking" und "asynchron" jeweils massiv anders aus.

    Die Beschreibung "One thread per client" ist aber natürlich passend.

    Schön dass wir wenigstens da einer Meinung sind 🙂
    Wobei es auch suboptimal ist wie ich gerade selbst feststelle. "One thread per connection" wäre genauer. Denn was "Client" bedeutet kann man auch so oder so sehen. Könnte ja auch ein Client mehr als eine Verbindung gleichzeitig offen haben.



  • @zhavok sagte in Gleicher Code, unterschiedliches Ergebnis:

    Ok vielen dank. Also ist mein Beispiel zwar multithreaded, aber synchron. Denn jeder Thread wartet immer beim read bis etwas ankommt und so lange ist der thread blocked.

    Es ist definitiv multithreaded, definitiv blocking, und ich würde es als auch synchron bezeichnen.

    Asynchronität lohnt sich also zum Beispiel beim streamen oder VoiceChats. Dort braucht man ja keine Rückmeldung und die Gegenstelle muss auch nicht auf die Informationen reagieren.

    Asynchronität lohnt sich in vielen Fällen. z.B. wenn man extrem viele Verbindungen hat und/oder viele Verbindungen lange "idle" sind. Denn...

    • Ein Thread benötigt einiges an RAM, daher wenn man viele Verbindungen verschwendet man viel RAM.
    • Das Umschalten zwischen Threads kann etwas an Performance kosten.

    Beides sind aber Punkte die man nicht überbewerten sollte. Tausend Verbindungen sind z.B. noch lange kein Grund vom "one thread per connection" Modell wegzugehen, vorausgesetzt man hat einen "normalen" PC zur Verfügung (paar GB bis zu GB RAM, 2-4 Cores mit je 2-4 GHz). Und die Einfachkeit dieses Ansatzes ist halt kaum zu überbieten, was seine grosse Stärke ist.

    Synchronität wäre zum Beispiel sinnvoll für eine Telnet Verbindung. Wenn man zum Beispiel möchte, dass auf einem Rootserver etwas ausgeführt oder abgefragt wird.

    Synchron, blocking und mit einem Thread pro Session wird man bei Telnet nicht weit kommen. Gibt ja zwei Richtungen. D.h. man braucht entweder zwei Threads pro Session oder halt irgendwas ala asynchronem IO oder non-blocking IO.
    (Gut, auf POSIX Systemen könnte man vermutlich Signals verwenden, aber Signals sind so grausam, darüber will man eigentlich gar nicht nachdenken.)


Log in to reply