Hi,
ich muss unter Linux mittels C++ (g++ Compiler) ein Konsolenprogramm
erstellen welches mit einem Messgerät kommuniziert und hab da folgendes
Problem.
Die Kommunikation erfolgt über RS232 und das senden/empfangen der Daten
klappt wunderbar nur wenn ich solche Dinge versuche gibt es Probleme.
(modul ist eine Instanz die eine Instanz SerialPort enthält welche für
die Kommunikation zuständig ist )
1
modul->sendCommand(FrequLimit);
2
sleep(1);
3
modul->sendCommand(FrequLimit);
Es gibt hier Probleme mit dem Sleep. Wenn ich debugge und in Zeile 2 ein
Breakpoint setze, dann kann ich auch fortsetzen und er sendet den
zweiten Befehl.
Kann mir jemand dabei helfen ? bzw. einen Tipp geben in welcher Richtung
das Problem liegen könnte ?
auron2008 schrieb:> Es gibt hier Probleme mit dem Sleep.
Geht's noch unpräziser? Wie soll man mit so wenig Information
herausfinden was da nicht funktioniert?
Dr. Sommer schrieb:> auron2008 schrieb:>> Es gibt hier Probleme mit dem Sleep.>> Geht's noch unpräziser? Wie soll man mit so wenig Information> herausfinden was da nicht funktioniert?
Genau da ist mein Problem, ich habe nicht mehr Informationen. Immer wenn
Sleep, usleep aufgerufen wird dann hängt das Programm dort drinne fest.
Um herauszufinden ob's an dem Sleep liegt hatte ich auch folgendes
versucht.
1
modul->sendCommand(FrequLimit);// <- Messgerät sendet eine Antwort zurück
2
3
while(1){
4
sleep(1);
5
cout<<"Test"<<endl;
6
}
Und auch hier bleibt's scheinbar an dem sleep hängen aber nur wenn ich
Daten von der RS232 Schnittstelle empfangen habe. Kann auch über ein
Terminal Daten an das Programm senden und es bleibt am Sleep hängen.
Den Empfang der Daten habe ich so gelöst :
Initialisierung im Konstruktor
1
SerialPort::SerialPort()
2
{
3
...
4
ReceiveSignal.sa_handler=Receive_Handler;
5
ReceiveSignal.sa_flags=0;
6
ReceiveSignal.sa_restorer=NULL;
7
sigaction(SIGIO,&ReceiveSignal,NULL);
8
9
Receive_Helper(this);
10
...
11
}
Da ein Signal nur auf eine static Methode gelegt werden kann habe ich
zwei kleine Hilfsfunktion genutzt.
1
voidSerialPort::Receive_Handler(intstat)
2
{
3
Receive_Helper(NULL);
4
}
5
6
void*SerialPort::Receive_Helper(void*pthis)
7
{
8
staticSerialPort*pc_this;
9
if(!pthis==0)
10
{
11
pc_this=(SerialPort*)pthis;
12
}else{
13
pc_this->ReadWithEvent();
14
}
15
}
1
voidSerialPort::ReadWithEvent()
2
{
3
unsignedcharbuf[255];
4
5
ReadPort(buf);
6
7
if(!SerialPortReceive_Handler==NULL)
8
{
9
10
(*SerialPortReceive_Handler)();//funktion im Hauptprogramm
auron2008 schrieb:> Dr. Sommer schrieb:>> auron2008 schrieb:>>> Es gibt hier Probleme mit dem Sleep.>>>> Geht's noch unpräziser? Wie soll man mit so wenig Information>> herausfinden was da nicht funktioniert?>> Genau da ist mein Problem, ich habe nicht mehr Informationen. Immer wenn> Sleep, usleep aufgerufen wird dann hängt das Programm dort drinne fest.
Und das ist keine Information? "gibts Probleme" hätte ja auch sein
können dass sleep sofort zurückkehrt oder das Programm abstürzt etc...
> Um herauszufinden ob's an dem Sleep liegt hatte ich auch folgendes> versucht.> modul->sendCommand(FrequLimit); // <- Messgerät sendet eine Antwort> zurück>> while(1) {> sleep(1);> cout << "Test" << endl;> }>> Und auch hier bleibt's scheinbar an dem sleep hängen aber nur wenn ich> Daten von der RS232 Schnittstelle empfangen habe. Kann auch über ein> Terminal Daten an das Programm senden und es bleibt am Sleep hängen.
Mit anderen Worten, es bleibt nicht am sleep hängen, sondern am
read/write ... Mach doch mal eine Debug-Ausgabe zwischen read/write
und sleep. Vermutlich hast du vergessen, deinen filedescriptor auf
non-blocking zu setzen ("fcntl (fd, F_SETFL, fcntl (fd, F_GETFL) |
O_NONBLOCK);"). IO mit Signalen ist im Allgemeinen etwas gefährlich,
versuchs besser mal mit select(), dann sparst du dir die statische
Member-Funktion. signale können schließlich immer zu beliebigen
Zeitpunkten kommen, zB vor dem 1. Aufruf von Receive_Helper oder während
ReadPort gerade läuft...
> void *SerialPort::Receive_Helper(void *pthis)> {> static SerialPort *pc_this;> if(!pthis == 0)
Was ist das denn, eine umständliche Schreibweise für "if (pthis)" ?!
Und was ist das überhaupt für eine kreative Konstruktion, warum machst
du keine globale Variable "static SerialPort* theSerialPort;", weist
diese im Konstruktor zu, und verwendest sie in der statischen Funktion?
Das wäre klarer und leichter verständlich...
> void SerialPort::ReadWithEvent()> {> unsigned char buf[255];>> ReadPort(buf);>> if(!SerialPortReceive_Handler == NULL)> {>> (*SerialPortReceive_Handler)(); //funktion im Hauptprogramm> }> }
Protip: http://en.cppreference.com/w/cpp/utility/functional/function> int SerialPort::ReadPort(unsigned char* _result)
Eigene bezeichner die mit "_" anfangen sind gefährlich. In dem Fall ists
zwar erlaubt, aber es schadet nicht es ganz zu lassen. Siehe
http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier/228797#228797
.
> {> int res = 0;> unsigned char buffer[255];>> res = read(fd, buffer,255);
Auch hier, read hängt wenn O_NONBLOCK nicht gesetzt ist.
> buffer[res] = '\0';>> memccpy(_result,buffer,'\0',255);
Voll umständlich&ineffizient, warum lässt du "read" nicht direkt nach
"_result" lesen? Woher weiß ReadPort dass _result tatsächlich 255 bytes
groß ist?
> std::cout << hex << static_cast<int>(buffer[i]) << std::endl;
Der cast ist unnötig, "int { buffer[i] }" ist kürzer
Dr. Sommer schrieb:> warum machst> du keine globale Variable "static SerialPort* theSerialPort;", weist> diese im Konstruktor zu, und verwendest sie in der statischen Funktion?
Oder natürlich einfach eine statische Member-Variable.
Erstmal vielen Dank für deine ausführliche Antwort, die muss ich erstmal
sacken lassen. Habe erst angefangen mich mit C++ zu befassen (vorher
VB.NET) und probiere halt noch viel aus, deswegen der unsaubere Code.
Dr. Sommer schrieb:> Mit anderen Worten, es bleibt nicht am sleep hängen, sondern am> read/write ... Mach doch mal eine Debug-Ausgabe zwischen read/write> und sleep. Vermutlich hast du vergessen, deinen filedescriptor auf> non-blocking zu setzen ("fcntl (fd, F_SETFL, fcntl (fd, F_GETFL) |> O_NONBLOCK);").
Hat leider nicht den gewünschten Effekt gebracht. Aber ist es erstmal
nicht so wichtig, nach deinem Post hab ich scheinbar wichtigere
Baustellen die ich zuerst beheben muss. Aber die Richtung scheint zu
stimmen denn wenn ich
das read(...) aus der Methode ReadPort(...) entferne dann läuft es
wunderbar weiter.
> IO mit Signalen ist im Allgemeinen etwas gefährlich,> versuchs besser mal mit select(), dann sparst du dir die statische> Member-Funktion. signale können schließlich immer zu beliebigen> Zeitpunkten kommen, zB vor dem 1. Aufruf von Receive_Helper oder während> ReadPort gerade läuft...
Daran habe ich gar nicht gedacht, wenn ich aber in ReadPort das Signal
mittels sigaction auf ignorieren stelle und Receive_Helper gleich zu
Beginn des Konstruktor aufrufe dann dürfte doch dahingehend nichts mehr
passieren.
Da ich wie gesagt in C++ und Linux nicht gerade sehr bewandert bin habe
ich keine richtige alternative zu den Signalen gesehen (außer ein Thread
der ständig den Port abfragt). Werde mir ansehen was du mit dem select
meinst und wie man es besser machen kann. In meinen zwei Büchern hier
steht leider nicht darüber.
> Und was ist das überhaupt für eine kreative Konstruktion, warum machst> du keine globale Variable "static SerialPort* theSerialPort;", weist> diese im Konstruktor zu, und verwendest sie in der statischen Funktion?> Das wäre klarer und leichter verständlich...
Da fehlt's einfach an Erfahrung in C++ .
> Eigene bezeichner die mit "_" anfangen sind gefährlich. In dem Fall ists> zwar erlaubt, aber es schadet nicht es ganz zu lassen. Siehe> http://stackoverflow.com/questions/228783/what-are...>
Ich fand das (bis jetzt) immer ganz praktisch da ich so immer weiß was
gemeint ist und ich mir's in VB.Net so angewöhnt hatte.
>> {>> int res = 0;>> unsigned char buffer[255];>>>> res = read(fd, buffer,255);> Auch hier, read hängt wenn O_NONBLOCK nicht gesetzt ist.>> buffer[res] = '\0';>>>> memccpy(_result,buffer,'\0',255);> Voll umständlich&ineffizient, warum lässt du "read" nicht direkt nach> "_result" lesen? Woher weiß ReadPort dass _result tatsächlich 255 bytes> groß ist?
1
memccpy(_result,buffer,'\0',255);
sollte eigentlich
1
memccpy(_result,buffer,'\0',res);
sein.
Aber du hast recht, ich kann's direkt auf _result schreiben. Keine
Ahnung was mich hier geritten hat.
>> std::cout << hex << static_cast<int>(buffer[i]) << std::endl;> Der cast ist unnötig, "int { buffer[i] }" ist kürzer
Diese Ausgabe war nur zum debuggen gedacht und da hatte ich verschiedene
Schreibweisen ausprobiert und die ist es am Ende geblieben. Wird aber
wieder entfernt.
Also erstmal vielen Dank für deine hilfreichen Tipps.
> Daran habe ich gar nicht gedacht, wenn ich aber in ReadPort das Signal> mittels sigaction auf ignorieren stelle und Receive_Helper gleich zu> Beginn des Konstruktor aufrufe dann dürfte doch dahingehend nichts mehr> passieren.
Alles Frickelei :/
>> Da ich wie gesagt in C++ und Linux nicht gerade sehr bewandert bin habe> ich keine richtige alternative zu den Signalen gesehen (außer ein Thread> der ständig den Port abfragt). Werde mir ansehen was du mit dem select> meinst und wie man es besser machen kann. In meinen zwei Büchern hier> steht leider nicht darüber.
Wenn das Bücher über UNIX-Systemprogrammierung sein sollen, gehören die
dann in Ablage Rund. Aber zum Glück gibt es das Internet (Google "linux
select") und zum Glück hat Linux alle nötigen Anleitungen dabei ("man
select" eingeben). Mit select wird das ganze viel sauberer, denn du
brauchst dann bei ordentlicher Umsetzung keine statischen Funktionen
mehr - d.h. du kannst dann problemlos auch mehrere Instanzen von
SerialPort haben - und die Callbacks kommen zu definierten Zuständen.
Alternativ kannst du auch ein Framework wie Gtk+ oder Qt verwenden
welches eine solche Funktionalität schon bietet und Callbacks aufruft
wenn Daten ankommen/gesendet werden können.
> Ich fand das (bis jetzt) immer ganz praktisch da ich so immer weiß was> gemeint ist und ich mir's in VB.Net so angewöhnt hatte.
Und ohne den Unterstrich weiß man nicht dass das ein Parameter ist? o.O
Dr. Sommer schrieb:> Wenn das Bücher über UNIX-Systemprogrammierung sein sollen, gehören die> dann in Ablage Rund. Aber zum Glück gibt es das Internet (Google "linux> select") und zum Glück hat Linux alle nötigen Anleitungen dabei ("man> select" eingeben). Mit select wird das ganze viel sauberer, denn du> brauchst dann bei ordentlicher Umsetzung keine statischen Funktionen> mehr - d.h. du kannst dann problemlos auch mehrere Instanzen von> SerialPort haben - und die Callbacks kommen zu definierten Zuständen.> Alternativ kannst du auch ein Framework wie Gtk+ oder Qt verwenden> welches eine solche Funktionalität schon bietet und Callbacks aufruft> wenn Daten ankommen/gesendet werden können.
Das klingt ja sehr interessant. Is eigentlich das was ich wollte und saß
an meiner Lösung mit den statischen Funktionen ne halbe Ewigkeit. Werd
wohl nochmal in de Bibo gehen und mir direkt Systemprogrammierung
suchen. Über google und co zu suchen hab ich mir teilweise abgewöhnt
weil's in Copy and Paste ausartet und der Lerneffekt gegen null geht.
>> Ich fand das (bis jetzt) immer ganz praktisch da ich so immer weiß was>> gemeint ist und ich mir's in VB.Net so angewöhnt hatte.> Und ohne den Unterstrich weiß man nicht dass das ein Parameter ist? o.O
So war das nicht gemeint. Als Bsp. ich habe eine Klasse Kreis
1
classKreis{
2
private:intradius;
3
publicsetRadius(int_radius);
4
}
Ich finde das damit die Lesbarkeit erhöht wird. Aber wenn's kein guter
Programmierstil ist dann lege ich's beiseite. Andere müssen mit meinem
Quelltext am Ende klar kommen.
auron2008 schrieb:> Über google und co zu suchen hab ich mir teilweise abgewöhnt> weil's in Copy and Paste ausartet und der Lerneffekt gegen null geht.
Man muss nur einen Bogen um Foren-Threads machen und direkt die
jeweilige Doku ansehen...
auron2008 schrieb:> Ich finde das damit die Lesbarkeit erhöht wird.
Man kann auch einfach
Dr. Sommer schrieb:> IO mit Signalen ist im Allgemeinen etwas gefährlich,> versuchs besser mal mit select(), ... signale können> schließlich immer zu beliebigen Zeitpunkten kommen
Recht hat er, obwohl ich die Formulierung für leicht untertrieben, ja
gerade zu verharmlosend halte. Nimm auf jeden Fallselect. Sobald Du
mit Signalen programmierst holst Du Dir Nebenläufigkeit in Dein Programm
und damit einen ganzen Schwung Probleme, die Du als weniger erfahrener
Programmierer nicht wirklich haben willst. Selbst elementare Annahmen
über die Ausführung von Programmen können zusammenbrechen, weshalb Du
kaum ein Buch über Programmiersprachen finden wirst, das darauf eingeht.
Du wirst auf Literatur zu Multithreading oder Kernelprogrammierung
zurückgreifen müssen. Google mal nach Begriffen wie atomic oder racecondition um einen Eindruck zu bekommen. Mit select kannst Du all
diese Probleme einfach vermeiden.
A. H. schrieb:
Sobald Du
> mit Signalen programmierst holst Du Dir Nebenläufigkeit in Dein Programm> und damit einen ganzen Schwung Probleme, die Du als weniger erfahrener> Programmierer nicht wirklich haben willst.
Und genau das hatte ich vor. Mein Plan sah folgendermaßen aus.
Ich habe ein Messgerät welches mit der Klasse Mess beschrieben wird. In
dieser gibt einen Member SerialPort welcher die ganze Kommunikation
regelt. Dabei soll in regelmäßigen Abständen (ca aller 2 Sekunden)
geprüft werden ob das Gerät noch vorhanden ist. Ein Echo Befehl hin der
wieder zurückgeschickt werden soll.
Nebenbei sollte es möglich sein Befehle an das Gerät zu senden und
wieder zu empfangen. Auf die empfangene Antwort muss dann reagiert
werden. Unter NET ist sowas kein Problem, einfach für jede Aufgabe eine
Klasse mit Events und fertig.
Genau das wollte ich mit den Signalen auch erreichen. Daten werden an
das Messgerät geschickt und dieses antwortet. Die Klasse Mess bekommt
über den SerialPort eine Information das da was ist. Ein weiterer Member
Response kümmert sich um die Auswertung und löst ein Event aus welches
entsprechend reagiert.
Mit select habe ich im Moment zwar kein Problem, aber ich bin gezwungen
eine Schleife zu betreiben die nichts anderes macht außer alle Methoden
nacheinander aufzurufen. Sowas wollte ich verhindern.
1
WerteAntwortaus()
2
{
3
erzeugeneuenBefehl;
4
}
5
while(1)
6
{
7
sendBefehl(Befehl);
8
Antwort=warteAufAntwort();
9
WerteAntwortaus(Antwort);
10
}
Und soweit ich das jetzt verstanden habe macht man mit select nichts
anderes. Man wartet bis etwas ankommt oder geht nach einem timeout
weiter im Programm. Auf der anderen Seite wird mit select vieles
vereinfacht.
Grundsätzlich ist Deine Idee ja nicht schlecht, aber das Grundproblem
bleibt: Mit Signalen holst Du Dir Nebenläufigkeit und die damit
verbundenen potentiellen Probleme möchtest Du mit ziemlicher Sicherheit
unter allen Umständen vermeiden.
Auch für select schreibst Du eine Art Handler, die die eigentliche
Arbeit erledigen. Ein durch select gerufener Handler wird vollständig
abgearbeitet, bevor der nächste anlaufen kann. Bei Signalhandlern ist
das nicht so. Die können sich und das Hauptprogramm an beliebiger Stelle
unterbrechen. Das kann zu schwer durchschaubaren Fehlern führen, die
sich obendrein nur sehr schwer bis gar nicht reproduzieren lassen.
Solche Situationen zu erkennen – oder noch besser: von Anfang an zu
vermeiden – setzt einiges an Erfahrung voraus.
Was verwendest Du eigentlich für eine Oberfläche? Eine graphische
Oberfläche wird mit ziemlicher Sicherheit schon ein select in der
Main-Message-Loop haben, in die du Dich dann entsprechend einklinken
kannst/musst.
auron2008 schrieb:> Unter NET ist sowas kein Problem, einfach für jede Aufgabe eine> Klasse mit Events und fertig.
Ja weil .NET Zeug gleich ein ganzes Framework mitliefert. Wenn du unter
C++ ein Framework wie Qt oder Gtk+ verwendest hast du das da ganz
genauso. Da kannst du dir pro Sekunde eine Funktion aufrufen lassen und
wenn Daten ankommen. Nur weil der C++ Sprachkern das nicht mitliefert
heißt das nicht dass das nicht geht...
A. H. schrieb:> Was verwendest Du eigentlich für eine Oberfläche? Eine graphische> Oberfläche wird mit ziemlicher Sicherheit schon ein select in der> Main-Message-Loop haben, in die du Dich dann entsprechend einklinken> kannst/musst.
Es wird eine Konsolenanwendung da der spätere Einsatz ein mini PC
(raspberry Pi) sein soll. Die konfiguration soll mittels TCP ermöglich
werden.
>Auch für select schreibst Du eine Art Handler, die die eigentliche>Arbeit erledigen.
Nur eine Verständnisfrage, du meinst solch ein vorgehen :
1
if(FD_ISSET(...))
2
{
3
lese();
4
}else{
5
cout<<"nix empfangen";
6
}
Dr. Sommer schrieb:> Ja weil .NET Zeug gleich ein ganzes Framework mitliefert. Wenn du unter> C++ ein Framework wie Qt oder Gtk+ verwendest hast du das da ganz> genauso. Da kannst du dir pro Sekunde eine Funktion aufrufen lassen und> wenn Daten ankommen. Nur weil der C++ Sprachkern das nicht mitliefert> heißt das nicht dass das nicht geht...
Ich hatte mir schon Qt angesehen und muss sagen das ich lieber ohne
anfangen möchte um in die Sprache besser einzusteigen.
Aber so sieht'S jetzt schon ganz gut aus. Die SeialPort Klasse wird
stark vereinfacht und auch die Verarbeitung der Daten ist einfacher da
ich keine static Funktionen mehr benötige.
auron2008 schrieb:> Nur eine Verständnisfrage, du meinst solch ein vorgehen :
Pseudocode mäßig so:
1
while(true){
2
fd_setrfds;
3
for(IOObject&obj:myIOObjects){
4
FD_SET(obj.getFD(),&rfds);
5
}
6
select(...,&rfds,...);
7
for(IOObject&obj:myIOObjects){
8
if(FD_ISSET(obj.getFD(),&rfds)){
9
obj.onIO();
10
}
11
}
12
}
Und um deinen 1sec-Timer zu realisieren verwendest du den
Timeout-Parameter an select(). Geht alles, die anderen Frameworks machen
auch nichts anderes, erfordern aber weniger Fummelei.
Dr. Sommer schrieb:> Und um deinen 1sec-Timer zu realisieren verwendest du den> Timeout-Parameter an select(). Geht alles, die anderen Frameworks machen> auch nichts anderes, erfordern aber weniger Fummelei.
so einfach ist das aber nicht. Was ist wenn der 1sek timer durch die
RS232 abgebrochen wurde. Dann muss man sich merken das man jetzt noch
0.2sek warten muss. Und was ist wenn man noch mehr Timer braucht ...
Wenn man dann auch etwas in eine DB schreiben will, wo man kaum
Kontrolle darüber hat wie lange der Aufruft dauert, dann verpasst man
noch wichtige Read oder Timerevents.
Und in Anbetracht der Sache, das heute ein Handy schon 4 cores hat,
würde ich es einfach mit Threads lösen. Dann bekommt jeder RS232 einen
Thread und gut ist.
Klar muss man sich die über die wie Synchronisation Gedanken machen,
aber das finde ich einfacher als alles über ein select laufen zu lassen.
Peter II schrieb:> Dr. Sommer schrieb:>> Und um deinen 1sec-Timer zu realisieren verwendest du den>> Timeout-Parameter an select(). Geht alles, die anderen Frameworks machen>> auch nichts anderes, erfordern aber weniger Fummelei.>> so einfach ist das aber nicht. Was ist wenn der 1sek timer durch die> RS232 abgebrochen wurde. Dann muss man sich merken das man jetzt noch> 0.2sek warten muss. Und was ist wenn man noch mehr Timer braucht ...
Ja man muss sich pro Timer merken wann er abläuft, und dann immer
abfragen ob die schon abgelaufen sind. Als Timeout muss man die
Zeitspanne zum am nächsten ablaufenden Timer angeben. Soo schwierig ist
das auch wieder nicht.
>> Wenn man dann auch etwas in eine DB schreiben will, wo man kaum> Kontrolle darüber hat wie lange der Aufruft dauert, dann verpasst man> noch wichtige Read oder Timerevents.
Das sollte man dann natürlich auch asynchron machen.
> Und in Anbetracht der Sache, das heute ein Handy schon 4 cores hat,> würde ich es einfach mit Threads lösen. Dann bekommt jeder RS232 einen> Thread und gut ist.> Klar muss man sich die über die wie Synchronisation Gedanken machen,> aber das finde ich einfacher als alles über ein select laufen zu lassen.
Kommt drauf an, Thread-Synchronisation kann ganz schön tricky sein.
Peter II schrieb:> so einfach ist das aber nicht. Was ist wenn der 1sek timer durch die> RS232 abgebrochen wurde. Dann muss man sich merken das man jetzt noch> 0.2sek warten muss. Und was ist wenn man noch mehr Timer braucht ...
Im Prinzip – wie immer :-) – ganz einfach: Du erzeugst für jeden
Vorgang, der in der Zukunft ablaufen soll, ein Timer-Event. Diese Events
werden, aufsteigend sortiert nach Ablaufzeit, in eine Warteschlange
gestellt. Vor dem select prüfst Du auf abgelaufene Events (Ablaufzeit <
aktuelle Zeit), sie werden aus der Warteschlange entfernt und die
entsprechenden Handler abgearbeitet (wobei durch die Handler durchaus
neue Events in die Queue gestellt werden können). Das erste Event, dass
noch nicht abgelaufen ist, bestimmt die Wartezeit ( = Ablaufzeit –
aktuelle Zeit). Je nach geforderter Genauigkeit und Laufzeit der
IO-Handler kannst Du die Abarbeitung der Event-Queue zusätzlich noch vor
und zwischen den IO-Handlern anstoßen.
PS: Das funktioniert sogar, wenn Du gar kein IO überwachen musst.
select wirkt dann als reines delay, aber genauer als viele andere
Lösungen.
Peter II schrieb:> Klar muss man sich die über die wie Synchronisation Gedanken machen,> aber das finde ich einfacher als alles über ein select laufen zu lassen.
Ich finde, man sollte bei solchen Ratschlägen auch den persönlichen
Erfahrungshorizont des Fragenden berücksichtigen. Auch wenn Threads
vielleicht die technisch elegantere Lösung sind, er muss sie auch
beherrschen können. Nicht dass ich ihm da irgendetwas ausreden möchte –
um Gottes Willen – man kann letztlich ja nur lernen. Er sollte aber
wenigstens wissen, was auf ihn zukommt. Übrigens halte ich das sichere
Beherrschen des Timings bei einem select fast für eine Voraussetzung,
um sich an Threads oder Signalen zu versuchen und nein, einfacher ist
die Lösung mit Threads ganz sicher nicht, vielleicht codeseitig weniger
aufwendig, aber das ist nicht dasselbe.
Dr. Sommer schrieb:> auron2008 schrieb:>> Nur eine Verständnisfrage, du meinst solch ein vorgehen :>> Pseudocode mäßig so:> while (true) {> fd_set rfds;> for (IOObject& obj : myIOObjects) {> FD_SET (obj.getFD (), &rfds);> }> select (..., &rfds, ...);> for (IOObject& obj : myIOObjects) {> if (FD_ISSET (obj.getFD (), &rfds)) {> obj.onIO ();> }> }> }Und um deinen 1sec-Timer zu realisieren verwendest du den> Timeout-Parameter an select(). Geht alles, die anderen Frameworks machen> auch nichts anderes, erfordern aber weniger Fummelei.
Tut mir leid das ich nochmal frage, aber so richtig einleuchtend ist mir
das gerad nicht.
Zeile 1 + 2 is klar.
In Zeile 3 gehst du davon aus das es mehrere Objekte gibt (Bsp.
SerialPort) und die gehst du nacheinander durch ? Mit FD_SET setzt du
dann die Filediscriptoren.
Zeile 4 select() setzt den Timer und den größten FD und wartest genau
dort bis Daten ankommen oder die Zeit abgelaufen ist.
Anschließend gehst du wieder alle erzeugten Objekte durch und schaust
mit dem Filediskriptor ob sich was getan hat oder nicht.
Hab ich das soweit richtig verstanden ?
Wenn ja, dann stellt sich mir die Frage zu Zeile 4 und eurer
anschließenden Diskussion über die Unterbrechung des Timers durch die
RS232 Schitsstelle ? Das einzige was ich mir vorstellen kann ist, dass
durch das empfangen der Daten der Timer unterbrochen wird. Dann geht's
gleich zum lesen und hier könnte es ein Problem geben weil eventuell
noch nicht alles empfangen wurde. Einfache Lösung wäre dann eine kurze
Wartefuntkion.
Im Moment sieht's so aus und funktioniert super.
auron2008 schrieb:> Hab ich das soweit richtig verstanden ?
Ja - das war so hauptsächlich um zeigen wie man mit select auf mehrere
dateideskriptoren wartet.
auron2008 schrieb:> Das einzige was ich mir vorstellen kann ist, dass durch das empfangen> der Daten der Timer unterbrochen wird. Dann geht's gleich zum lesen
Nein, denn dann liefert FD_ISSET false zurück und nix wird gelesen.
Ich würde zudem immer nach select prüfen ob Timer abgelaufen sind und
sie ausführen. Zum Rechnen mit Zeitspannen und -Punkten gibt es im
Standard C++ jetzt das std::chrono API.
auron2008 schrieb:> Im Moment sieht's so aus und funktioniert super.
Wenn du dass so machst und im SerialPort das select ausführst blockiert
du natürlich während des wartens das gesamte restliche Programm. Wenn du
nebenher noch auf andere Eingaben warten willst solltest du das select
in die main () packen und da alle Deskriptoren rein stecken.
auron2008 schrieb:> Wenn ja, dann stellt sich mir die Frage zu Zeile 4 und eurer> anschließenden Diskussion über die Unterbrechung des Timers durch die> RS232 Schitsstelle ? Das einzige was ich mir vorstellen kann ist, dass> durch das empfangen der Daten der Timer unterbrochen wird.
Ein select ist eigentlich nichts anderes als eine Art erweitertes
sleep. Es suspendiert den Prozess bis ein „interessantes Ereignis“
eintritt. Das kann entweder der Ablauf einer gewissen Zeit sein oder
aber eine entsprechende Statusänderung an einem der überwachten
Filedeskriptoren. Nicht mehr und nicht weniger. Es liegt dann an Dir als
Programmierer auszuwerten, welche Ereignisse eingetreten sind und
entsprechend zu reagieren. Dabei gilt es gegebenfalls auch zu
berücksichtigen, welche Ereignisse eventuell noch nicht eingetreten
sind, etwa dass der Timer (ich würde übrigens lieber von einem Timeout
sprechen) noch nicht abgelaufen ist oder das eben noch nicht alle Daten
da sind.
> Dann geht's> gleich zum lesen und hier könnte es ein Problem geben weil eventuell> noch nicht alles empfangen wurde.
Richtig, für ein als zum Lesen/Schreiben bereit markierten Fildeskriptor
ist garantiert – soweit ich die man-Page richtig im Kopf habe – dass das
nächste read/write nicht blocken würde, selbst wenn der FD blockierend
gelesen/geschrieben wird. Es ist nicht garantiert, dass eine bestimmte
Datenmenge da ist. (Ja nicht einmal, dass überhaupt Daten da sind, nur
dass read/write sofort zurückkommt. Das kann auch mit einem EOF oder
einem Fehler der Fall sein.) Du musst selbst dafür sorgen den Vorgang so
lange zu wiederholen, bis alle Daten da sind (und natürlich auf mögliche
Fehler achten!).
> Einfache Lösung wäre dann eine kurze Wartefuntkion.
Ganz falscher Ansatz! Erstens kann auch ein noch so langes Warten nicht
garantieren, dass alle Daten da sind. Zweitens wartest Du hier an einer
Stelle nur auf ein Ereignis während alle anderen unberücksichtigt
bleiben und das System folglich an allen anderen Stellen hängt. Das
führt die ganze Idee von select ad absurdum. Du wartest im ganzen
System nur an einer Stelle und das ist im select.
> Im Moment sieht's so aus und funktioniert super.
Das sieht nur so aus, aber wir sind auf einem guten Weg. :-)
So nu bin ich wieder hier, musste auf Dienstreise.
Also um's besser zu machen sollte man das Timeout wohl sehr niedrig
halten bzw. ganz weglassen und einfach prüfen ob was da ist oder nicht.
Wäre an sich auch kein Problem da die ankommenden Daten so aufgebaut
sind. Id, ~Id, länge, daten, crc16 prüfsumme . Also einfach schauen ob
Daten da sind, bis zum 3. Byte lesen (immer) und dann weiß ich wie viele
Bytes ich zu erwarten habe.