Forum: PC-Programmierung Hilfe mit Socket Programmierung (C++) und boost asio Bibliotheken


von X. A. (wilhem)


Lesenswert?

Hallo zusammen,
Für mein Projekt an der Uni wird eine Verbindung mit einem externen 
Server hergestellt (über Internet). Daten (aus einer Sensorik) werden 
von einem Rechner (ein eingebettetes System) zu einem Server übertragen 
(und dies geschieht alle 10 Sekunden). Das Client-Programm habe ich 
selber in C++ realisiert. Da es sehr wichtig ist, dass auch andere Daten 
neben der Kommunikation Rechner-Server verarbeitet werden, musste ich 
eine asynchrone Verbindung herstellen. Daher habe ich die boost asio 
Bibliotheken in meinem Programm eingebunden (insbesondere der Link hier: 
http://theboostcpplibraries.com/boost.asio-network-programming war sehr 
hilfreich).

Wichtig ist auch: Der Rechner sendet Daten zu dem Server und dann wartet 
auf eine Rückmeldung von ihm. Bekommt der Rechner keine Rückmeldung 
innerhalb einer festgelegten Frist, werden die gleichen Daten nochmal 
geschickt.

*Problem*: In meiner _main_-Funktion rufe ich die Verbindung mit dem 
Server einmal alle 10 Sekunden:
1
/* main.cpp */
2
3
  std::chrono::high_resolution_clock::time_point current_time = std::chrono::high_resolution_clock::now();
4
  std::chrono::high_resolution_clock::time_point previous_time = current_time;
5
6
while(1) {
7
current_time = std::chrono::high_resolution_clock::now();
8
9
/* Das ist der schnelle Loop */
10
if( std::chrono::duration_cast<std::chrono::seconds>( current_time - previous_time ) > std::chrono::seconds(10) ) {
11
12
previous_time = current_time;
13
14
/* Die Verbindung mit dem Server wird hergestellt */
15
client.run();
16
17
}
18
19
}

wobei der Aufruf der Methode der Klasse "client" wie folgende aussieht:
1
/* client.cpp */
2
3
void Client::run( void )
4
{
5
6
boost::asio::ip::tcp::resolver::query local_query( host_server_, port_ );
7
8
// HIER WERDEN SENSOREN EINGELESEN
9
    
10
/* Eine neue Serververbindung wird nun hergestellt */
11
resolver_.async_resolve( local_query, boost::bind( &Client::handleResolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator ) );
12
    
13
try {
14
  size_t service_status = io_service_.run();
15
16
  std::cout << "IO Service status: " << service_status << std::endl << std::endl;
17
18
  } catch ( std::exception &ec ) {
19
20
  std::cerr << ec.what() << std::endl;
21
  }
22
23
  io_service_.reset();
24
25
}
26
}

Alles funktioniert wunderbar: ABER wenn ich das Zeitintervall von 10 
Sekunden auf 100 Millisekunden reduziere, dann habe ich den Eindruck, 
dass das Objekt resolver:
1
boost::asio::ip::tcp::resolver::query local_query( host_server_, port_ ); 
2
....
3
resolver_.async_resolve( local_query, boost::bind( &Client::handleResolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator ) );
 viel länger braucht und zu einer Verzögerung der Kommunikation führt.
Mir ist es noch nicht klar, wie eine Internet Verbindung mit einem 
solchen Objekt hergestellt wird, daher wollte ich vor allem die folgende 
Fragen:

1) soll ein solches Objekt bei jeder neuen Verbindung kreiert werden? 
Also alle X Sekunden, oder reicht es vollkommen aus, wenn ich das Objekt 
mit Hilfe von Smart-Zeigern in meiner Klasse deklarieren und dann der 
Funktion übergeben?
Beispiel:
1
/* client.h */
2
3
boost::shared_ptr<boost::asio::ip::tcp::resolver::query> local_query_( new boost::asio::ip::tcp::resolver::query( host_server_, port_ ));
1
/* client.cpp */
2
3
void Client::run( void )
4
{
5
6
// HIER WERDEN SENSOREN EINGELESEN
7
    
8
/* Eine neue Serververbindung wird nun hergestellt */
9
resolver_.async_resolve( &local_query_, boost::bind( &Client::handleResolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator ) );
10
    
11
try {
12
  size_t service_status = io_service_.run();
13
14
  std::cout << "IO Service status: " << service_status << std::endl << std::endl;
15
16
  } catch ( std::exception &ec ) {
17
18
  std::cerr << ec.what() << std::endl;
19
  }
20
21
  io_service_.reset();
22
23
}
24
}

Was ist erreichen will ist, dass ich das Objekt nur einmal erstellen 
muss.

Ich hoffe dass meine Frage verständlich ist.
Ich möchte einfach nicht, dass die Internet-Verbindung mein ganzes 
Programm abbremst, daher ist es mir wichtig, zu wissen, ob irgendwie 
diese Verzögerungen vermieden werden können.

: Bearbeitet durch User
von X. A. (wilhem)


Lesenswert?

In dem obigen Link ist dieser Satz zu lesen:

In main(), boost::asio::ip::tcp::resolver::query is instantiated to 
create  an object q. q represents a query for the name resolver, an I/O 
object of  type boost::asio::ip::tcp::resolver. By passing q to 
async_resolve(), an  asynchronous operation is started to resolve the 
name. Example 32.5  resolves the name theboostcpplibraries.com. After 
the asynchronous  operation has been started, run() is called on the I/O 
service object to pass control to the operating system.

Daher meine Frage: Darf dieses instantiierte Objekt einmal kreiert 
werden oder muss ich bei jeder neuen Verbindung mit dem Server kreieren? 
(Also jedes mal dass der Rechner eine Kommunikation starten will)

Selbstverständlich bleibt der Name des Servers immer gleich.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

> *Problem*: In meiner _main_-Funktion rufe ich die Verbindung mit dem
> Server einmal alle 10 Sekunden:

Dein Problem ist, dass Du den code nicht vernünftig formatiert hast und 
des damit potentiellen Helfern sehr schwer machst, Dir zu helfen!

Ansonsten looped dein Code busy um die 10 Sekunden verstreichen zu 
lassen. Das ist absolutes no go! Du verbrennst damit CPU-Zeit und ein 
ggf. vorhandener thread scheduler kann auch nicht erkennen, dass das 
Blödsinn ist und ggf. einen anderen thread die CPU geben.

Boost.Asio hat auch Timer. Wenn Du 10 Sekunden warten musst, dann setze 
einen timer auf und starte im callback des timers den Verbindungsaufbau 
zum server. Am Ende der Verbindung setzt Du dann wieder den Timer auf.

Die einzige Haupt-Schleife im Programm sollte nichts anderes machen, als 
boost::Agio::io_service::run() aufzurufen.

HTH
Torsten

: Bearbeitet durch User
von X. A. (wilhem)


Lesenswert?

Torsten!!!
Vielen Dank! Dein Tipp war super. Nachdem ich mir die boost asio 
Bibliotheken angeschaut habe 
(http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/tutorial/tuttimer4.html), 
konnte ich innerhalb wenigen Stunden mein Programm von Null umschreiben. 
Nun funktioniert alles prima!!!

Eine Sache lässt mich noch ein bisschen nachdenken. Das von mir 
gepostete Beispiel, war ein sogenannte Minimal-Beispiel. Ich habe alles 
sehr stark vereinfacht. In dem original Progamm habe ich 2 Loops. Die 
beiden laufen mit zwei verschiedenen Takten.

In der Boost-Webseite ist ein (echt minimal-)Beispiel zu lesen. Die 
beiden Loops zum Laufen zu bringen ist echt einfach.
Aber ich muss Daten zwischen den Beiden Loops austauschen können. Was 
soll ich tun? mutex in dem main-Loop und jedem Loop ein eigenes thread 
zuordnen?

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Du kannst mal gucken, ob es für Deine Anwendung passen würde, wenn Du 
die Datenänderung dadurch bewirkst, dass Du kleine Funktionen auf den 
io_service post()-est, die dann die zu transportierenden Daten ändern.

Ansonsten klingt es nach keiner schlechten Idee, jeder loop einen thread 
zuzuordnen.

mfg Torsten

von X. A. (wilhem)


Lesenswert?

Hi,
ich glaube ich habe es nicht so richtig verstanden, das mit der Methode 
post().

Vielleicht soll ich meine Struktur und mein Design nochmal umdenken, 
würde ich mich sehr freuen, wenn Du (oder Ihr) bei diesem Problem helfen 
kannst.

Mit Hilfe des folgenden Tutorials 
(http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/tutorial/tuttimer4.html) 
war es gar nicht schwer, zwei verschiedene Klassen (Foo und Bar) zu 
erzeugen und deren Methode mit einem vordefinierten Zeitintervall 
systematisch aufzurufen.

/* Klasse: Foo */
1
using namespace boost::asio
2
using namespace boost::posix_time
3
4
class Foo {
5
...
6
  deadline_timer timeout_foo_;
7
...
8
9
  Foo::Foo( io_service &io): timeout_foo_(io,seconds( 10 ) )
10
  {
11
    timeout_foo_.async_wait( boost::bind( &Foo::run, this ) );  
12
  }
13
14
  Foo::run(void)
15
  {
16
    std::cout << "Running Foo forever" << std::endl;
17
18
    timeout_foo_.expires_at( timeout_foo_.expires_at() + seconds( 10 ) );
19
    timeout_foo_.async_wait( boost::bind( &Foo::run, this ) );
20
  }


/* Klasse: Bar */
1
class Bar {
2
...
3
  deadline_timer timeout_bar_;
4
...
5
6
  Bar::Bar( io_service &io): timeout_bar_(io, seconds( 2 ) )
7
  {
8
    timeout_bar_.async_wait( boost::bind( &Bar::loop, this ) );  
9
  }
10
11
  Bar::loop(void)
12
  {
13
    std::cout << "Running Bar forever" << std::endl;
14
15
    timeout_bar_.expires_at( timeout_bar_.expires_at() + seconds( 2 ) );
16
    timeout_bar_.async_wait( boost::bind( &Bar::loop, this ) );
17
  }

und in der main-Datei habe ich nun folgendes:
1
/* main.cpp */
2
3
io_service io_service;
4
5
while(1) {
6
7
  try {
8
9
     io_service.run();
10
11
  } catch (std::exception& e) {
12
13
    std::cerr << "Exception: " << e.what() << std::endl;
14
  }
15
}


Das obige ultra-mega-minimal-Beispiel funkioniert einwandfrei und ist es 
auch viel eleganter als das von mir gepostete Programm.

Das Problem nun ist, dass ich in der Klasse Foo einige Methode der 
Klasse Bar aufrufen muss. Es kommt somit zu einem Austausch von Daten 
zwischen den beiden Klassen. Derzeit ist es unidirektional, doch werde 
ich zeitnah auf die Möglichkeit zugreifen müssen, Daten in beide 
Richtungen auszutauschen. Ich stehe somit vor einem Huhn-Ei Problem.

Mir fehlen nun nur zwei mögliche Lösungen ein:

 *Lösung A)* In diesem Fall wird ein Istanz von Bar der Klasse Foo 
als Referenz übergeben. Also:
1
/* Klasse: Foo */
2
using namespace boost::asio
3
using namespace boost::posix_time
4
5
class Foo {
6
...
7
  deadline_timer timeout_foo_;
8
  Bar bar_;
9
  int zahl_;
10
...
11
12
  Foo::Foo( io_service &io, Bar &bar ): timeout_foo_(io,seconds( 10 ) ), bar_(bar)
13
  {
14
    timeout_foo_.async_wait( boost::bind( &Foo::run, this ) );  
15
  }
16
17
  Foo::run(void)
18
  {
19
    std::cout << "Running Foo forever" << std::endl;
20
21
    zahl_ = bar_->methode_in_bar(); // <= HERE wird eine Methode aufgerufen, die Daten ausgibt!!!!!!
22
23
    timeout_foo_.expires_at( timeout_foo_.expires_at() + seconds( 10 ) );
24
    timeout_foo_.async_wait( boost::bind( &Foo::run, this ) );
25
  }

und in dem main loop folgt dann:
1
io_service io_service;
2
3
Bar bar(io_service);
4
Foo foo(io_service, bar);
5
6
while(1) {
7
8
  try {
9
10
     io_service.run();
11
12
  } catch (std::exception& e) {
13
14
    std::cerr << "Exception: " << e.what() << std::endl;
15
  }
16
}

 *Lösung b)*
Beide Loops werden einzeln in einem eigenen Thread gestartet. Der 
Austauch von Daten erfolgt durch den Einsatz von Mutex ...

Mich würde brennend interessieren, wie ich mein Code gestalten soll.

Danke!!!

: Bearbeitet durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Dave A. schrieb:
> ich glaube ich habe es nicht so richtig verstanden, das mit der Methode
> post().

Siehe Unten.

> Das Problem nun ist, dass ich in der Klasse Foo einige Methode der
> Klasse Bar aufrufen muss. Es kommt somit zu einem Austausch von Daten
> zwischen den beiden Klassen. Derzeit ist es unidirektional, doch werde
> ich zeitnah auf die Möglichkeit zugreifen müssen, Daten in beide
> Richtungen auszutauschen. Ich stehe somit vor einem Huhn-Ei Problem.

hast Du schon mal darüber nachgedacht, dass es evtl. besser wäre, wenn 
Du nur eine Klasse mit zwei Timern hättest?

> Mir fehlen nun nur zwei mögliche Lösungen ein:
>
>  *Lösung A)* In diesem Fall wird ein Istanz von Bar der Klasse *Foo*
> als Referenz übergeben. Also:

Wenn das geht und Du sicher stellen kannst, dass das referenzierte 
Objekt auch so lange lebt, wie das referenzierende Objekt, ist das eine 
gute Lösung.


>  *Lösung b)*
> Beide Loops werden einzeln in einem eigenen Thread gestartet. Der
> Austauch von Daten erfolgt durch den Einsatz von Mutex ...

Mehr als ein thread macht bei so einem asynchronen design nur Sinn, wenn 
Du wirklich mehr als eine CPU auslasten kannst. Du kannst prinzipiell 
die run function des io_service mit einem thread betreiben, aber auch 
mit mehreren threads. Erst im zweiten Fall, must Du überhaupt damit 
rechnen, dass irgend welche callbacks von verschiedenen threads 
aufgerufen werden.

Wenn die Ein-Klassen-Lösung zu unübersichtlich wird, und Du vermutest, 
dass Du mehr als eine CPU auslasten wirst (bzw. längere Perioden hast in 
denen die CPU genutzt wird) dann könntest Du maximale Flexibilität 
erreichen, in dem Du den Objekten Referenzen Ihrer Kommunikationspartner 
mitgibst und diese Funktionen auf Ihren Kommunikationspartnern nicht 
direkt aufrufen, sondern den Funktionsaufruf via io_service::post() 
asynchron dem io_service zur Ausführung übergibst.

io_service funktioniert dann einfach wie eine Queue, irgend wann wird 
der übergebene Handler indirekt von io_service::run ausgeführt.

HTH
Torsten
>
> Mich würde brennend interessieren, wie ich mein Code gestalten soll.
>
> Danke!!!

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

ach und was ich vergessen hatte: Bei asynchronen designs ist es extrem 
hilfreich, dass ganze als Automat zu betrachten und zu analysieren, 
welche Zustände und welche Übergänge hat. Ein rein Objektorientiertes 
Design führt meist nur zu Knoten im Kopf ;-)

von X. A. (wilhem)


Lesenswert?

Hi Torsten,
vielen Dank für Deine Hilfe.
Ich habe meinen Code in den letzten Tagen nochmal überarbeitet. So...mit 
Deiner Hilfe und dem folgenden Tutorial: 
http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/tutorial/tuttimer5.html 
habe ich nun 2 Threads. Eigentlich ist es so wie Du es beschrieben hast. 
Ich habe eine Klasse, welche 2 Objekte aus 2 verschiedenen Klassen 
erzeugt. Diese enthalten jeweils einen Timer, welche asynchron arbeitet.

Im Grunde habe ich zwei Prozesse, welche von einander entkoppelt laufen 
müssen (und mit verschiedenen Takten). Zwischen den zwei Prozessen 
müssen Daten ausgetauscht werden (nach Eintreffen bestimmter 
Bedingungen).

Mit dem Code bin nun relativ zufrieden, dennoch bin ich etwas irritiert, 
weil ich der Meinung bin, dass das Ganze einfacherer gestaltet werden 
könnte.
So...ich muss einen Schritt zurückgehen und feststellen, dass das echte 
Problem in dem OOP-Design von Code liegt. Boost, Threads und alles drum 
und dran ist relativ einfach und schnell zu implementieren. Doch was 
einem (mir) fehlt ist ein gutes Design. Vom Anfang an. Im Internet bin 
auf das Prinzip S.O.L.I.D. gestoßen, sieht und klingt es aber zu 
abstract, um dessen Richtlinien einsetzen zu können.

Ich habe Deinen Lebenslauf angeschaut und wow... Du hast echt sehr viel 
Erfahrung. Könntest Du mir ein Buch oder irgendwas empfehlen, um das 
OOP-Design in Zukunft besser zu gestalten?

Vielen Dank nochmal.
Gruß
D.

: Bearbeitet durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Dave A. schrieb:

> Könntest Du mir ein Buch oder irgendwas empfehlen, um das
> OOP-Design in Zukunft besser zu gestalten?

Der Klassiker, wenn es um OO Designs geht ist "Design Patterns. Elements 
of Reusable Object-Oriented Software.". Zur OO Analyse fällt mir leider 
nichts ein.

mfg Torsten

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.