Threadproblem bei Server-Client-Programm
-
Hallo Community,
Ich schreibe grade ein kleines Netzwerktestprogramm. Dabei sollen sich mehrere Clients gleichzeitig auf einem Server anmelden können und Nachrichten an den Server senden, der die Nachricht an alle Clients weitergibt (Das hab ich aber bisher noch nicht gemacht, ist aber mein Ziel).
Um zu realisieren, dass mehrere Clients sich anmelden können, benutze ich Threads aus der boost-Library. Zur Realisierung dieser Threads habe ich eine Klasse "ServerConnection" geschrieben, welche von den thread erbt und in der Endlosschleife des Servers ein ServerConnection-Objekt erzeugt.
Ich zeig euch erst einmal den Code dazu:
/* server_main.cpp */ /* Die main-Datei des Servers, welche aufgerufen wird */ #include "ServerSocket.h" #include "ClientSocket.h" #include "ServerConnection.h" #include "SocketException.h" #include <string> #include <sstream> #include <iostream> void ServerConnection::calc() { /*std::stringstream ss; std::string data; clientSocket >> data; ss << "Empfangen: " << data << "\n"; clientSocket << ss.str(); */ std::cout << "Ich bin die Methode calc()" << std::endl; } int main ( int argc, char**argv ) { std::cout << "running....\n"; try { // 1. Create the socket // 2. Bind Socket to Port 30000 // 3. Set Socket to Listen ServerSocket server ( 30000 ); while ( true ) { ClientSocket clientSock; server.accept ( clientSock ); /* Hier wird die Connection erstellt. Diese gibt die Funktion calc() an * den Thread weiter und den akzeptierten Socket */ ServerConnection sc(clientSock, &calc); sc.join(); } } catch ( SocketException& e ) { std::cout << "Exception was caught:" << e.description() << "\nExiting.\n"; } return 0; }Die Klasse ServerConnection:
#ifndef SERVERCONNECTION_H_ #define SERVERCONNECTION_H_ #include <boost/thread/thread.hpp> #include "ClientSocket.h" class ServerConnection : public boost::thread { public: template<typename Callable> ServerConnection(const ClientSocket& csocket, const Callable func) : boost::thread(func), clientSocket(csocket) { } virtual ~ServerConnection() { } void calc(); private: ClientSocket clientSocket; }; #endif /* SERVERCONNECTION_H_ */ClientSocket sieht so aus:
/* * ClientSocket.h * * Created on: May 12, 2011 * Author: lumbeck */ #ifndef CLIENTSOCKET_H_ #define CLIENTSOCKET_H_ #include "Socket.h" class ClientSocket : public Socket { public: ClientSocket ( std::string host, int port ); ClientSocket() { } virtual ~ClientSocket() {}; const ClientSocket& operator << ( const std::string& ) const; const ClientSocket& operator >> ( std::string& ) const; }; #endif /* CLIENTSOCKET_H_ */Und hier auch wieder die zugehörige cpp-Datei
// Implementation of the ClientSocket class #include "ClientSocket.h" #include "SocketException.h" ClientSocket::ClientSocket ( std::string host, int port ) { if ( ! Socket::create() ) { throw SocketException ( "Could not create client socket." ); } if ( ! Socket::connect ( host, port ) ) { throw SocketException ( "Could not bind to port." ); } } const ClientSocket& ClientSocket::operator << ( const std::string& s ) const { if ( ! Socket::send ( s ) ) { throw SocketException ( "Could not write to socket." ); } return *this; } const ClientSocket& ClientSocket::operator >> ( std::string& s ) const { if ( ! Socket::recv ( s ) ) { throw SocketException ( "Could not read from socket." ); } return *this; }ServerSocket sieht so aus:
#ifndef SERVERSOCKET_H_ #define SERVERSOCKET_H_ #include "Socket.h" class ServerSocket : public Socket { public: ServerSocket ( int port ); ServerSocket (){}; virtual ~ServerSocket(); const ServerSocket& operator << ( const std::string& ); const ServerSocket& operator >> ( std::string& ); void accept ( Socket& ); }; #endif /* SERVERSOCKET_H_ *//* * ServerSocket.cpp */ // Implementation of the ServerSocket class #include "ServerSocket.h" #include "SocketException.h" #include <sstream> ServerSocket::ServerSocket ( int port ) { if ( ! Socket::create() ) { throw SocketException ( "Could not create server socket." ); } if ( ! Socket::bind ( port ) ) { throw SocketException ( "Could not bind to port." ); } if ( ! Socket::listen() ) { throw SocketException ( "Could not listen to socket." ); } } ServerSocket::~ServerSocket() { } const ServerSocket& ServerSocket::operator << ( const std::string& s ) { if ( ! Socket::send ( s) ) { throw SocketException ( "Could not write to socket." ); } return *this; } const ServerSocket& ServerSocket::operator >> ( std::string& s ) { if ( ! Socket::recv ( s ) ) { throw SocketException ( "Could not read from socket." ); } return *this; } void ServerSocket::accept ( Socket& sock ) { if ( ! Socket::accept ( sock ) ) { throw SocketException ( "Could not accept socket." ); } }Ohne das Threading funktioniert es mit dem Echo-Server (also Client schickt Nachricht und bekommt sie sofort zurück).
Hier ist aber folgendes Problem beim Kompilieren des Servers:
g++ -o server Socket.cpp network.cpp server_main.cpp ServerSocket.cpp ServerConnection.cpp /tmp/ccD7KxdR.o: In function `main': server_main.cpp:(.text+0x16e): undefined reference to `ServerConnection::ServerConnection<void (ServerConnection::*)()>(ClientSocket const&,void (ServerConnection::*)())' server_main.cpp:(.text+0x17e): undefined reference to `boost::thread::join()' /tmp/ccD7KxdR.o: In function `ServerConnection::~ServerConnection()': server_main.cpp:(.text._ZN16ServerConnectionD1Ev[ServerConnection::~ServerConnection()]+0x38): undefined reference to `boost::thread::~thread()' server_main.cpp:(.text._ZN16ServerConnectionD1Ev[ServerConnection::~ServerConnection()]+0x63): undefined reference to `boost::thread::~thread()' /tmp/ccD7KxdR.o: In function `ServerConnection::~ServerConnection()': server_main.cpp:(.text._ZN16ServerConnectionD0Ev[ServerConnection::~ServerConnection()]+0x38): undefined reference to `boost::thread::~thread()' server_main.cpp:(.text._ZN16ServerConnectionD0Ev[ServerConnection::~ServerConnection()]+0x63): undefined reference to `boost::thread::~thread()' collect2: ld returned 1 exit status make: *** [server] Error 1Ich möchte die Methode calc() der ServerConnection an die Connection weitergeben. Warum klappt das nicht? Ich hoffe, mein Problem ist verständlich...
-
Ohne den Code zu lesen: Du hast nicht gegen die Booost Threads Bibliothek gelinkt. Da sollte noch so etwas wie -lboost_thread an den Compileraufruf dran.
-
Ahrg,
hatte vorher ein kleines Testprogramm geschrieben und in der make-Datei nur die Library beim Testprogramm angegeben.Jetzt kommt noch folgender Fehler:
g++ -lboost_thread -o server Socket.cpp network.cpp server_main.cpp ServerSocket.cpp ServerConnection.cpp -L/usr/lib64/ /tmp/ccD4gzLZ.o: In function `main': server_main.cpp:(.text+0x16e): undefined reference to `ServerConnection::ServerConnection<void (ServerConnection::*)()>(ClientSocket const&,void (ServerConnection::*)())' collect2: ld returned 1 exit status make: *** [server] Error 1
-
Templatedefinitionen musst (beziehungsweise solltest, denn das ist die einfachste Methode) du in den zugehörigen Header packen, sonst werden sie bei Bedarf nicht instanziert. Steht hier in den FAQ glaube ich an fünfter Stelle.
-
Das heisst, der Kontruktor sollte in der Header von ServerConnection schon definiert werden?
Oder wie hab ich das vorzustellen?
Habe die Templatedefinition aus der thread-Klasse von boost, deshalb weiss ich es nicht besser, habe bisher nicht mit templates aktiv gearbeitet.
(ich schau mal im FAQ)
Ah. Also muss ich entweder direkt im Header definieren oder eine .impl-Datei schreiben, wo die Templates definiert werden...
-
Fabulus schrieb:
Das heisst, der Kontruktor sollte in der Header von ServerConnection schon definiert werden?
Ja.
-
Okay, habe ich gemacht. Das klappt ja soweit schonmal, jetzt kommt aber ein nächstes Kompilierproblem bzgl der threads:
g++ -lboost_thread -o server Socket.cpp network.cpp server_main.cpp ServerSocket.cpp -L/usr/lib64/ /usr/include/boost/thread/detail/thread.hpp: In member function 'void boost::detail::thread_data<F>::run() [with F = void (ServerConnection::*)()]': server_main.cpp:55: instantiated from here /usr/include/boost/thread/detail/thread.hpp:56: error: must use '.*' or '->*' to call pointer-to-member function in '((boost::detail::thread_data<void (ServerConnection::*)()>*)this)->boost::detail::thread_data<void (ServerConnection::*)()>::f (...)' make: *** [server] Error 1Das versteh ich ja noch weniger

-
Zeig mal die server_main.cpp, wie sie jetzt ist.
-
/* server_main.cpp */ /* Die main-Datei des Servers, welche aufgerufen wird */ #include "ServerSocket.h" #include "ClientSocket.h" #include "ServerConnection.h" #include "SocketException.h" #include <string> #include <sstream> #include <iostream> void ServerConnection::calc() { /*std::stringstream ss; std::string data; clientSocket >> data; ss << "Empfangen: " << data << "\n"; clientSocket << ss.str(); */ std::cout << "Ich bin die Methode calc()" << std::endl; } int main ( int argc, char**argv ) { std::cout << "running....\n"; try { // 1. Create the socket // 2. Bind Socket to Port // 3. Set Socket to Listen ServerSocket server ( 30000 ); while ( true ) { ClientSocket clientSock; server.accept ( clientSock ); /* Hier wird die Connection erstellt. Diese gibt die Funktion calc() an * den Thread weiter und den akzeptierten Socket */ ServerConnection sc(clientSock, &ServerConnection::calc); sc.join(); } } catch ( SocketException& e ) { std::cout << "Exception was caught:" << e.description() << "\nExiting.\n"; } return 0; }
-
Verstehe. Du übergibst ihm mit &calc einen Pointer auf eine Memberfunktion. Die kann der Thread natürlich nicht ohne weiteres aufrufen, da er schließlich noch ein konkretes Objekt braucht.
Das musst du entweder mit boost::bind daran binden oder das insgesamt anders lösen.
-
Ah, weil calc() ja keine statische Methode ist, müsste ich quasi die Methode eines Objektes vom Typ ServerConnection angeben?
Das heißt, wenn ich diese Methode statisch mache, würde das vorerst funktionieren.Wie funktioniert das denn mit bind? Auf der Boost-Documentation-Seite finde ich da keine Hilfe zu.
-
Das er hier die Definition einer Template-Memberfunktion in die cpp gepackt hat spielt hier keine Rolle?
-
Habe es schon geändert, gibt jetzt nur noch den Konstruktor in ServerConnection.h, die .cpp-Datei gibt es nicht mehr

-
Fabulus schrieb:
Wie funktioniert das denn mit bind?
So:
http://www.boost.org/doc/libs/1_46_1/libs/bind/bind.html#with_member_pointers
-
Blicke ich noch nicht so durch

Mit dem Beispiel aus der Doku wird doch nur die Funktion aufgerufen (So wie es daneben im Kommentar steht)
Aber ich habe doch gar keine Argumente, die an calc übergeben werden müssen.
Und ich habe auch kein Objekt, welches ich angeben könnte (im Beispiel ist es das x).Ich weiß nicht, wie ich den bind-Befehl mit meinem Code kombinieren soll.
Soll ich beim Erstellen der ServerConnection-Instanz das bind() in den Argumentaufruf setzen wie hier:ServerConnection sc(clientSock, bind(&ServerConnection::calc, /* Was soll hier jetzt hin? */)); sc.join();
-
Fabulus schrieb:
Aber ich habe doch gar keine Argumente, die an calc übergeben werden müssen.
Und ich habe auch kein Objekt, welches ich angeben könnte (im Beispiel ist es das x).
Wieso machst du es dann überhaupt so kompliziert, wenn du das gar nicht möchtest? Überdenke dein Design!
-
Mein Plan ist folgender gewesen:
Der Server ist die ganze Zeit online und akzeptiert in einer while-true-Schleife alle eingehenden Verbindungen. Damit aber gleichzeitig mehrere Clients sich anmelden können, soll eine neue ServerConnection erstellt werden, welche quasi ein Thread ist, in dem dann die Berechnung bzw. die Stringumformungen gemacht werden.
Als Argumente für die Serverconnection gebe ich den ClientSocket an und die Methode, die im Thread ausgeführt werden soll.Ist das Prinzip verständlich?
Es soll quasi bei einer neuen Client-Verbindung ein neuer Thread gestartet werden.
Edit:
Wenn ich eine globale Methode erstelle, klappt dies soweit, habe dann aber keinen Zugriff mehr auf den clientSocket, den ich bei der ServerConnection erstelle, deshalb würde ich lieber ne Methode von ServerConnection übergeben.
-
Wenn du die calc-Methode von ServerConnection verwenden willst, dann brauchst du sie doch eigentlich nicht im Konstruktor von ServerConnection zu übergeben. Es würde doch reichen sie in der Initialisierungsliste des Konstruktors an thread zu übergeben. Irgendwie so
ServerConnection(const ClientSocket& csocket) : boost::thread(boost::bind(&ServerConnection::calc, this)), clientSocket(csocket) { }
-
Ah, wunderbar, das funktioniert! Hätte ich auch selber drauf kommen können. Dann spar ich mir auch das Template

Danke sehr

Wenn es noch was bzgl. dieses Themas gibt, melde ich mich wieder.
EDIT:
Ich möchte jetzt zusätzlich noch eine Clientconnection erstellen. Eine Instanz dieser wird zu Beginn des Clients erstellt und der Thread gestartet. Das einzige, was die Methode, die der Thread aufruft, macht, ist in einer Endlosschleife Nachrichten entgegenzunehmen und auszugeben.
Das ganze wollte ich jetzt folgendermaßen lösen:
// client_main.cpp #include "ClientSocket.h" #include "SocketException.h" #include "ClientConnection.h" #include <iostream> #include <string> int main ( int argc, char** argv ) { try { ClientSocket client_socket ( "ikp696", 30000 ); std::string reply; std::string entry; std::string exitmsg="exit"; ClientConnection cc(client_socket); cc.join(); std::cout << "Test"; while(true) { std::cout << "Enter message: "; getline(std::cin, entry); try { // Sende Nachricht an den Sever client_socket << entry; if(entry == exitmsg) { client_socket.close(); break; } } catch ( SocketException& ) { std::cout << "Socket Exception!"; } } } catch ( SocketException& e ) { std::cout << "Exception was caught:" << e.description() << "\n"; } return 0; }#ifndef CLIENTCONNECTION_H_ #define CLIENTCONNECTION_H_ #include <boost/thread/thread.hpp> #include <boost/bind.hpp> #include "ClientSocket.h" class ClientConnection : public boost::thread { public: ClientConnection(const ClientSocket& csocket) : boost::thread(boost::bind(&ClientConnection::Run, this)), clientSocket(csocket) { } virtual ~ClientConnection() { } void Run(); private: ClientSocket clientSocket; }; #endif /* CLIENTCONNECTION_H_ *///ClientConnection.cpp #include "ClientConnection.h" #include <string> void ClientConnection::Run() { // Empfange Daten vom Server und speichere sie in reply std::string reply=""; clientSocket >> reply; std::cout << reply << "\"\n";; }Jetzt hab ich das Problem, dass in der Client_main-Datei die Nachricht "Test" nicht ausgegeben wird, weil anscheinend der Thread blockiert oder so.
Sinn und Zweck der ganzen Sache ist, dass ich gleichzeitig Nachrichten senden und empfangen kann. In Java habe ich es genau so hinbekommen, nur in C++ noch nicht. Kann da jemand noch mal schauen?
-
Indem du auf einem Thread die join - Methode aufrufst, wartest du auf Beendigung des Threads. Wenn der Thread nicht beendet wird, passiert also auch nichts.