Design Problem: Exceptions
-
Ja, aber das ist doch kein ordentliches CodeDesign.
2.
Serveruser ist die zentrale Klasse schlechthin.
3.
Das wäre eine Möglichkeit, aber dadurch vermindere ich nicht die Anzahl der try- und catch-Blöcke.Danke schon mal :),
MfG Eisflamme
-
GetAndProcessMessages - eine Funktion hat _eine_ Aufgabe und nicht 2
die switch sind doof - mach mehrere Fehlerklassen.Warum erstellt GetAndProcMsg denn den Socket neu?
Was wenn es einen Fehler gab, so dass der Socket nicht neu aufgebaut werden kann - dann fliegt ne 2. Exception und du hast einen terminate() aufruf -> bot ist todDas wollen wir nicht.
Wenn es einen Socket Error gab, dann wird der irgendwo zentral gehandhabt, aber sicher nicht in GetAndProcMsg.Bleibt also nur noch die Plugin Fehlerabfrage übrig.
Das neuaufbauen des Plugin halte ich für fraglich - denn ein Fehler, auch wenn er kritisch ist, kann wohl kaum mit einem neu aufbauen des Plugins ausgebessert werden.Und musst du bei einem BAD_ERROR wirklich das Plugin killen? Das ist IMHO auch nicht so gut. Vielleicht ist das Plugin ja wichtig, aber es hab ne unerwartete Eingabe, die das Plugin gewaltig verwirrt hat - weiterlaufen tut es ja trotzdem.
Also hätten wir die Sache auch schon verkürzt.
Bleibt noch die Frage über, warum du eine Exception weiter wirfst, wenn noch andere Plugins weiterarbeiten wollen. Das hängt jetzt vom Design ab... Aber auf jedenfall müsste die Exception eine referenz auf das 'kaputte' Plugin beinhalten, sonst kannst du nix anderes machen als den Fehler loggen...
Ich komme somit zu etwa folgendem resultat:
//by value - wir wollen ja kein undefiniertes verhalten. std::string Serveruser::GetAndProcessMessages() { using namespace Exception; // Eine Verbindung muss bestehen, und sie muss verbunden sein. if(!m_psClient || !m_psClient->connected()) throw Exception::SocketError("Keine Verbindung bestehen."); std::string cMsg = m_psClient->SelectMainLoop(); for(vPlugin::iterator it = m_vPlugins.begin(); it != m_vPlugins.end(); ++it) { try { it->m_pfFunc(cMsg, 0); } catch(CriticalPluginError& eErr) { throw SomeException(*it); } catch(...) {} //normal weitermachen, nix kritisches passiert //uu auch loggen } return cMsg; }
-
jo,das meinte ich mit zentral ;).
du solltest eindeutige Fehlerobjekte erstellen,und die nicht innerhalb der Funktion abarbeiten.
der programmierer brauch nur zu wissen, dass es sein könnte, dass die connection flöten geht,und kann sich so drauf einstellen, und im notfall selber entscheiden, ob er ein exit(1) aufrufen will,oder ob er vielleicht eine notfall prozedur einleiten will("warning connection lost, try to get new connection in 10seconds" oder auch "warning connection lost,you play now alone").
-
Danke für die Tipps.
Und musst du bei einem BAD_ERROR wirklich das Plugin killen? Das ist IMHO auch nicht so gut. Vielleicht ist das Plugin ja wichtig, aber es hab ne unerwartete Eingabe, die das Plugin gewaltig verwirrt hat - weiterlaufen tut es ja trotzdem.
Wenn es eine Eingabe ist, die das Plugin gewaltig verwirrt hat, und es kein schlimmer Fehler ist, wird nicht BAD_ERROR geworfen, BAD_ERROR wird nur geworfen, wenn es wirklich keine Möglichkeit gibt weiterzuarbeiten.
Das richtig einzuschätzen liegt im Ermessen desjenigen, der die Plugins schreibt.Hm, zentral... Heißt das, ich baue einen try-Block um den Funktionsaufruf jener Funktion auf?
Ich wollte es eigentlich so handhaben, dass die Klasse das selber korrigieren kann. Oder soll ich eine andere Funktion nehmen bzw. auch eine weitere Klasse darum herum?Das Weiterwerfen ist ausschließlich zum Loggen gedacht, d.h. das kann der Benutzer selber entscheiden, Anzeigen kann er's sicher auch.
Jedenfalls danke für die Tipps, wenn ich Zeit habe, werde ich das mal gründlich überarbeiten.
MfG Eisflamme
-
OT: Deine Plugins, sind das DLL-Dateien?
Eine C++ Exception darf eine DLL nämlich niemals verlassen. Fehler lassen sich bei DLLs nur über Rückgabewerte oder Hilfsfunktionen (GetLastError) erkennen.
-
Eine C++ Exception darf eine DLL nämlich niemals verlassen
Doch das geht wenn man die Laufzeitbibliothek als DLL linkt.
-
such schrieb:
Eine C++ Exception darf eine DLL nämlich niemals verlassen
Doch das geht wenn man die Laufzeitbibliothek als DLL linkt.
Nein. DLLs haben ein vorgeschriebenes Interface. Diese Regeln legen unter anderem fest, dass eine DLL ausschließlich Funktionen exportieren darf, die einer bestimmten Aufrufkonvention folgen.
Exceptions waren dort nie eingeplant und verletzen diese Aufrufkonvention. Deswegen wird es höchstens dann funktionieren, wenn DLL und EXE vom selben Compiler mit derselben Compilerversion stammen. Und selbst das ist nicht garantiert.
-
Ups, gut, dass du mir das sagst, ich wäre voll reingerannt.
-
Hi,
//by value - wir wollen ja kein undefiniertes verhalten.
Es ist auch so nicht undefiniert, weil SelectMainLoop eine konstante Referenz zurückliefert, die durch den Aufbau ebenfalls gültig ist.
std::string cMsg = m_psClient->SelectMainLoop();
naja, SelectMainLoop kann auch Exceptions werfen und wo sollen die dann bitte aufgefangen werden?
Im Übrigen:int main() { try { Main main; main.Execute(); } catch(...) { ... } return 0; }
Was bringt hier das catch? Was soll das außer Fehlerausgabe und Wertrückgabe denn bitte tun?
Ich verstehe in der Tat überhaupt nicht, wann und wo es logisch ist Exceptions einzusetzen, wer sie behandeln sollte, inwiefern etc. anscheinend habe ich völlig falsche Vorstellung davon.
Kennt jemand dazu gutes Material (kein Buch)?Danke jedenfalls schon mal,
MfG Eisflamme
-
Mis2com schrieb:
//by value - wir wollen ja kein undefiniertes verhalten.
Es ist auch so nicht undefiniert, weil SelectMainLoop eine konstante Referenz zurückliefert, die durch den Aufbau ebenfalls gültig ist.
Mag ja sein, aber du kopierst den Inhalt ja in die lokale variable cMsg - und gibst dann eine referenz darauf zurueck.
[quote[naja, SelectMainLoop kann auch Exceptions werfen und wo sollen die dann bitte aufgefangen werden?[/quote]
Was wirft sie denn fuer exception?
Irgendwas wo du sinnvoll reagieren kannst?Was bringt hier das catch? Was soll das außer Fehlerausgabe und Wertrückgabe denn bitte tun?
Dem User eine Nachricht ausgeben:
"Es tut uns Leid aber wir haben voll Mist gebaut."
und dann das programm beenden.
bzw. anbieten das programm neu zustarten, oder im debug modus neu starten oder fehlerbericht drucken. ka, irgendwas halt.Das ist besser als wenn es einfach abstuertzt und eine standard meldung "unhandled exception bla bla" kommt.
Ich verstehe in der Tat überhaupt nicht, wann und wo es logisch ist Exceptions einzusetzen, wer sie behandeln sollte, inwiefern etc. anscheinend habe ich völlig falsche Vorstellung davon.
Kennt jemand dazu gutes Material (kein Buch)?Exceptional C++ hat mir sehr geholfen, wobei das eher nicht auf wann werfe ich exception eingeht, sondern exception sicherheit. ka ob dir das was bringt.
uU fuehlst du dich aber in java wohler - dort werden exception so aehnlich behandelt wie du es tust.
im prinzip gilt aber:
sowenig try-catch bloecke wie moeglich (aber natuerlich nicht zu wenige).
meistens erledigt RAII die ganze arbeit, ich habe deshalb auch quasi nie try catch bloecke...eine exception ist naemlich eine ausnahme - etwas das im prinzip nicht vorkommt wenn alles halbwegs ordentlich laeuft.
-
Hallo,
uU fuehlst du dich aber in java wohler - dort werden exception so aehnlich behandelt wie du es tust.
Nein, ich will nicht Java programmieren, die Sprache ziehe ich in Zukunft zwar auch in Betracht, aber das, was ich mache, gefällt mir selber nicht besonders gut, ich bin nur etwas hin- und hergerissen und weiß nicht, wie ich es tun soll...
Dem User eine Nachricht ausgeben:
"Es tut uns Leid aber wir haben voll Mist gebaut."
und dann das programm beenden.
bzw. anbieten das programm neu zustarten, oder im debug modus neu starten oder fehlerbericht drucken. ka, irgendwas halt.Das ist besser als wenn es einfach abstuertzt und eine standard meldung "unhandled exception bla bla" kommt.
Gut, ich dachte erst, dass catch in der main() wirklich etwas behandeln würde, also nicht nur Ausgabe, so ist mir das dann auch klar.
naja, SelectMainLoop kann auch Exceptions werfen und wo sollen die dann bitte aufgefangen werden?
Was wirft sie denn fuer exception?
Irgendwas wo du sinnvoll reagieren kannst?Ja, wenn der Server die Verbindung z.B. trennt o.Ä.
Oder wie sollte ich darauf reagieren?
Hier mal der weniger gute Code von SocketClnt:const std::string& SocketClnt::SelectMainLoop() { if(!m_bValid) throw Exception::SocketError("Kein valides Socket.", Exception::RS_ERROR); // Falls kein Buffer vorhanden -> erstellen if(!m_cstrBuffer) m_cstrBuffer = new char[NBUFFERSIZE]; // Nachrichtenliste leeren m_strRecv = ""; // fdset leeren FD_ZERO(&m_fdsRead); // Server fdset hinzufügen FD_SET(m_sSocket, &m_fdsRead); // Timeout zurücksetzen m_tvTimeout.tv_sec = m_tvTimeout.tv_usec = 0; // Schauen, ob was empfangen wurde if((m_nRc = select(0, &m_fdsRead, 0, 0, &m_tvTimeout) > 0)) { // Daten empfangen m_nRc = recv(m_sSocket, m_cstrBuffer, NBUFFERSIZE-1, 0); // Bei m_nRc == 0 hat der Server offensichtlich die Verbindung unterbrochen if(m_nRc == 0) throw Exception::SocketError("Der Server hat die Verbindung unterbrochen.", Exception::RS_ERROR); // Wenn beim Empfangen der nachricht ein Fehler stattfindet, so kann man damit rechnen, dass die Verbindung // nicht mehr valide ist if(m_nRc == SOCKET_ERROR) { int m = WSAEFAULT; int n = WSAGetLastError(); throw Exception::SocketError("Empfangen der Nachricht fehlgeschlagen.", Exception::RS_ERROR); } // Für die Nachrichtenauflistung mit Delimiter \n ist es hier sinnvoller, \n direkt zu setzen m_cstrBuffer[m_nRc] = '\0'; m_strRecv += m_cstrBuffer; // Nachricht hinzufügen }//WSAEINVAL // Empfangene Strings zurückliefern return m_strRecv; }
Ich werde das Konzept für die Sockets noch einmal überarbeiten, jedenfalls bin ich davon ausgegangen, dass ein nicht-verbundenes Socket auch keine Daseinsberechtigung hat und daher nicht valide ist. Ich hörte hier im Forum doch auch einmal davon, dass Methoden wie open() oder close() für basic_fstream kein gutes Design wären, daher übertrug ich dies auf Sockets, schließlich macht ein nicht-verbudenes Socket genauso wenig Sinn wie ein fstream ohne Dateiverbindung.
Das mit dem Buffer sollte auch klar sein, der wird nur ein Einziges mal allokiert.
Und ich hielt es eigentlich für eine gute Idee, das mit Exceptions zu machen, wenn getrennt wird...Also Exceptions sind Ausnahmen, klar, nur inwiefern?
Anscheinend ist die Trennung vom Server, die am Ende jedes Programmes stattfinden sollte, keine Ausnahme, sondern praktisch die Regel...
Mir ist klar, dass Exceptions nicht zur Programmsteuerung dienen sollten, in Java scheint dies genau anders zu sein?Jedenfalls will ich nicht die Sprache wechseln, sondern hier mal besser werden, danke vielmals.
MfG Eisflamme
-
Mis2com schrieb:
Ja, wenn der Server die Verbindung z.B. trennt o.Ä.
Oder wie sollte ich darauf reagieren?Wie willst du denn darauf reagieren? Am besten garnicht. Lass den user entscheiden was er will.
Normalerweise werde ich mir einen anderen server suchen, wenn mir dieser einen socket error liefert.
vielleicht will ich es aber auch in 1 minute nochmal probieren...die socket klasse kann das nicht wissen. sie meldet den fehler und wartet ab.
Ich werde das Konzept für die Sockets noch einmal überarbeiten, jedenfalls bin ich davon ausgegangen, dass ein nicht-verbundenes Socket auch keine Daseinsberechtigung hat und daher nicht valide ist.
Sehe ich genauso.
Ich hörte hier im Forum doch auch einmal davon, dass Methoden wie open() oder close() für basic_fstream kein gutes Design wären, daher übertrug ich dies auf Sockets, schließlich macht ein nicht-verbudenes Socket genauso wenig Sinn wie ein fstream ohne Dateiverbindung.
]
exakt.bedenke aber: was passiert wenn ein lesefehler bei fstream auftritt? (OK, angenommen man setzt mit setexception fuer failbit eine exception) - es fliegt dir eine exception um die ohren. die datei ist aber weiterhin 'offen' - auch wenn jede lese oder schreiboperation vermutlich auch fehlschlagen wird.
Mir ist klar, dass Exceptions nicht zur Programmsteuerung dienen sollten, in Java scheint dies genau anders zu sein?
C++ arbeitet _sehr_ sparsam mit exception. zB ausser .at() wirft keine methode der STL defaultmaessig.
das ist bei streams zwar doof, aber ist halt so
ich sehe es in etwa so: sobald eine exception in einem normalen durchlauf fliegen kann, dann ist es keine ausnahme.
zB darf EOF beim lesen aus einer datei keine exception werfen. wenn es aber lesefehler gibt, oder gar die datei nicht gefunden wird - dann schon. denn dann ist etwas unvorhergesehenes passiert.
bei sockets ist es aehnlich. der socket liest und liest, bis die verbindung beendet wird - soweit alles klar. allerdings muesste eine exception fliegen, wenn ich vom geschlossenen socket lesen will. das kann man natuerlcih auch immer etwas anders sehen - also zwing dich nicht dazu alles genauso zu sehen wie ich.
-
Hi,
gut, genauso wie du muss ich es nicht sehen, aber so oder so... Mein Konzept ist momentan einfach nicht tragbar, soviele catch-Anweisungen... das finde ich ja selbst nicht schön.
Aber z.B. kommt es auch vor, dass ein Socket die Verbindung verliert, weil der Provider mal kurz die Verbindung trennt und wiederverbindet (kommt vor, bei T-Online z.B.). Dies ist nicht sehr schlimm, aber ärgerlich, wenn keine automatische Wiederverbindung stattfindet.
Und da es sich hierbei um eine Ausnahme handelt, finde ich zwei Exceptions doch schon angemessen.
Allerdings, soll die nächsthöhere Instanz - hier GetAndProcessMessages - diese Ausnahme einfach ignorieren? In deinem Fall hättest du kein einziges catch darin und die Application-Klasse (also ich werde es demnächst wohl auch so mit Application machen)... sollte die überhaupt was davon mitbekommen?
Nagut, Serveruser stellt eine Klasse dar, die einen Manager der Nachrichten und Antworten zu einem Server vertritt. Allerdings beinhaltet ServerUser auch eine Verbindung zum Server, jetzt gibt es 2 Möglichkeiten:- ServerUser selbst stellt so eine Verbindung wieder her
- Application tut das über öffentliche Methoden von ServerUser
Ich mag mich nicht so recht entscheiden, ServerUser würde naheliegen, weil es bei mehreren Servern lästig wird, wenn dem Fehler ein Zeiger auf die Klasse, die wiederverbunden werden soll mitgeliefert wird, obwohl das Serveruser doch eigentlich alleine könnte.
Oder soll ich ServerUser eine Art Main-Methode geben, sodass dort GetAndProcessMessages aufgerufen wird, oder die Nachrichten verwaltet werden?Nochmals vielen Dank,
MfG Eisflamme
-
wenn die connection verloren wurde->exception innerhalb werfen und abarbeiten, wenn das nich klappt->exception nach aussen.
alternativ bool wert throwException. wenn true ->direkt exception nach aussen werfen, wenn false innerhalb abarbeiten
-
Äh, das widerspricht zu ziemlich dem, was Shade gerade sagte, weil eine verlorene Verbindung keine Ausnahme ist, da am Ende immer getrennt wird und bei dem Port 80 z.B. auch immer sofort eine Trennung erfolgt.
In dem Sinne ist eine Exception hier unangebracht, weil sie dann zur Programmsteuerung dienen würde, wo kein Ausnahmefall vorliegt. oOAber gut...
-
Woher weiss man warum die Verbindung geschlossen wurde? das kann man nur wissen, wenn man das verwendete Protokoll kennt - (da man sonst ja nicht weiss, ob dieses Ende der Verbindung beabsichtigt war oder nicht)
Dieses Problem hat der Socket. Er weiss, die Verbindung ist zuende - warum weiss er allerdings nicht.
Der Anwender der Klasse kann es aber sehr wohl wissen. Naemlich dann wenn noch Daten erwartet werden, aber der Socket zu ist - sprich, wenn ich von einem geschlossenen Socket lesen will.Ich sehe sockets als streams an, deshalb mal folgender pseudo code:
while(getline(socket, str)) { cout<<str; }
Jetzt koennte man SocketCLOSE als eine Art EOF interpretieren. Dann waere obriger Code korrekt.
Irgendwie gefaellt mir der obrige Code. Das lesen von einem Socket sollte sich wirklich nicht von dem lesen aus einer datei unterscheiden - denn genau das ist es ja in vielen faellen auch
eine exception koennte dann fliegen, wenn wir folgenden code verwenden wollen wuerden:
while(getline(socket, str)) { cout<<str; } socket.get(c); //PENG denn socket ist bereits zu
Wenn es dann noch eine reconnect() methode gibt, hat der Anwender alle moeglichkeiten richtig mit dem socket umzugehen.
der socket alleine ist dumm - erst das darueberliegende protokoll macht ihn klug
-
OK, ich verstehe...
Deine Idee ist wirklich interessant, was muss ich für die Verwendung von getline machen?
> und << sind zu überladen, soll ich erben lassen?
MfG Eisflamme
-
Mis2com schrieb:
Deine Idee ist wirklich interessant, was muss ich für die Verwendung von getline machen?
> und << sind zu überladen, soll ich erben lassen?
Einfach einen eigenen stream implementieren.
Das geht ueber einen streambuffer und uber das erben von ostream bzw. istreamletztens gab es dazu eh eine frage hier. ich habe da gleich Josuttis fdstream zum abschauen empfohlen
ansonsten schau dir mal an wie es socket++ macht - wird eh zeit dass boost sockets bekommt.
-
und worum handelt es sich bei so einem Streambuf?
-
Mis2com schrieb:
und worum handelt es sich bei so einem Streambuf?
Der macht im Prinzip die ganze Arbeit.
Er cacht die Daten, schreibt und bzw. oder liest.
Ein stream ist mittels eines streambuffers implementiert