Funktionsweise von IO-Streams
-
Hallo!
Ich will aus Übungszwecken, um die IO-Streams besser zu verstehen, die Library von C++ um IO-Sockets erweitern. Leider finde ich nie so die rechten Beispiele, die ausführlich genug sind.Ich habe eine Klasse, basic_sockbuf, die ich einmal von std::basic_streambuf und zum anderen privat von einer Ressourcenklasse ableite:
template <typename Ch, typename Tr = std::char_traits<Ch> > class basic_sockbuf : private stream_resource<Ch>, public std::basic_streambuf<Ch, Tr> { // ... } // Ressourcenklasse template <typename Ch> class stream_resource { protected: Ch* pbuf; public: stream_resource(std::streamsize n = 64) { try { pbuf = new Ch[n]; } catch(const std::bad_alloc&) { throw net_error("socket buffer too big"); } } ~stream_resource() { delete [] pbuf; } };1. Wie groß soll ich die Puffergröße defaulttechnisch wählen? Ich habe jetzt einfach irgendeine 2er-Potenz genommen (64 * sizeof(Ch)), aber was eignet sich am besten als Default?
2. Wie sollte ein Fehlermanagement aussehen? Speziell die Kommunikation zwischen Stream und Puffer. Funktionsrückgabewerte, also 0 für failure sind ja schön und gut, aber bei sockets kann ja so viel schiefgehen zur Runtime, dass die Info badbit irgendwie nicht sehr viel hilft, wenn z.B. u. Windows winsock.dll nicht geladen werden konnte, man keinen Socket-Deskriptor erstellen kann usw. usf. Ich habe an Exceptions gedacht, das scheint aber mehr als Kommunikationslayer zwischen Applikation und Stream als zwischen Stream und Puffer zu wirken (--> exceptions()). Wie kann ich sinnvolles Debugmanagement anbieten?
3. Wie arbeitet z.B. die Funktion overflow() genau? Sie wird aufgerufen, wenn der pptr() an den eptr() rankommt, aber wo steht das? Muss ich das selber definieren? Stroustrup meint: "Nur die Funktionen, die zur Initialisierung und der Pufferungsstrategie betreffen, [...] müssen überladen werden." Und was dann? Wird der ganze Puffer entleert, wenn es zum Overflow kommt? Ich muss in ihr ja irgendwann den Systemcall send() aufrufen.
Sorry für den vielen Text, aber ich stehe leider etwas auf dem Schlauch, da ich einfach nichts gescheites im Netz finde (da werden einfach nur die Implementierungen von filebufs benutzt, also nichts allzu fundamentales, aber sockets unterscheiden sich da ja). Ich habe mir auch die Implementierung der Standardlibary angeschaut, aber die ist ziemlich schwer leserlich.
:xmas2:
-
Warum leitest du denn die Klasse privat ab?
-
So verhindere ich Schwierigkeiten im Destruktor von basic_streambuf, wenn er auf den physikalischen Puffer zugreifen will. Hier wird erst der streambuf zerstört, danach erst das Array.
-
Ad aCTa schrieb:
1. Wie groß soll ich die Puffergröße defaulttechnisch wählen? Ich habe jetzt einfach irgendeine 2er-Potenz genommen (64 * sizeof(Ch)), aber was eignet sich am besten als Default?
.. so groß, dass Dein darunter liegendes Device (Sockets) damit zurechtkommt. Am besten Du probierst es einfach aus.
Ad aCTa schrieb:
2. Wie sollte ein Fehlermanagement aussehen? Speziell die Kommunikation zwischen Stream und Puffer. Funktionsrückgabewerte, also 0 für failure sind ja schön und gut, aber bei sockets kann ja so viel schiefgehen zur Runtime, dass die Info badbit irgendwie nicht sehr viel hilft, wenn z.B. u. Windows winsock.dll nicht geladen werden konnte, man keinen Socket-Deskriptor erstellen kann usw. usf. Ich habe an Exceptions gedacht, das scheint aber mehr als Kommunikationslayer zwischen Applikation und Stream als zwischen Stream und Puffer zu wirken (--> exceptions()).
Der std::streambuf bietet Dir zwei Arten von Fehlerbehandlung an. Zum einen kannst Du bei einem Aufruf von overflow (bein Schreiben) oder underflow (beim Lesen) jeweils mit traits_type::eof() zurückkommen, d.h. das Schreiben bzw. Lesen war nicht erfolgreich und der überlagerte Stream geht dann in den Fehlerzustand. Oder Du wirfst eine Exception - ohne weitere Kommandos wird dann im Stream die Exception gefangen und das std::ios_base::badbit gesetzt. Das entspricht einem fatalen Fehler, der nur mit Abbruch und wieder aufsetzen der Verbindung zu beheben ist.
Falls Du den spezifischen Fehler weiterleiten möchtest, so musst Du im stream (std::ostream oder std::istream) mit stream.exception(std::ios_base::badbit) das Exception-Flag so setzen, dass eine Exception des streambufs weitergeleitet wird. In den aufrufenden Funktionen kann man dann die exception fangen und die anhängende Meldung z.B. ausgeben.
Ad aCTa schrieb:
Wie kann ich sinnvolles Debugmanagement anbieten?
Was verstehst Du darunter? Ich würde Dir eine Art Trace-System (evt. schaltbar empfehlen), was alle ein- und ausgehenden Meldungen mitprotokolliert.
Ad aCTa schrieb:
3. Wie arbeitet z.B. die Funktion overflow() genau? Sie wird aufgerufen, wenn der pptr() an den eptr() rankommt, aber wo steht das? Muss ich das selber definieren? Stroustrup meint: "Nur die Funktionen, die zur Initialisierung und der Pufferungsstrategie betreffen, [...] müssen überladen werden." Und was dann? Wird der ganze Puffer entleert, wenn es zum Overflow kommt? Ich muss in ihr ja irgendwann den Systemcall send() aufrufen.
Es steht im C++-Standard (), aber das ist keine Quelle, um zu lernen, wie es funktioniert, sondern wird Dich nur frustrieren, wenn Du nicht schon weißt, wie man einen streambuf verwendet. Im Grunde gibt es meines Wissens nur eine vernünftige Quelle - das ist das Buch 'Standard C++ Iostreams and Locales'. Das Überladen eines streambufs wird dort auf vielleicht einem Dutzend Seiten beschrieben. Ansonsten suche in diesem Forum nach
streambuf- da wirst Du auch einiges finden - oder frage einfach konkret nach; .. dann erzähle ich es Dir
zu overflow: Es ist nirgends(!) beschrieben wie sie arbeitet, es ist lediglich definiert was sie tun soll; nämlich das übergebende Zeichen und alle im Buffer stehenden - falls da welche stehen - an das unterliegende Device übergeben - im Standard steht:
Consumes some initial subsequence of the characters of the pending sequence.Gruß
Werner
-
Vielen Dank für deine Antwort.
Mein Problem hier ist:
Pseudocodeclass buf { public: buf() { if(!this->lade_dll()) { // lade_dll == 0 // Exception... } if(!this->get_socket()) { // konnte keinen socket-descriptor bekommen // Exception... } } }; class stream { private: buf b; public: stream() : b() { } stream(buffer* bptr) : b(bptr) { } }; int main() { try { stream sout; // socket out <---- Erste Try-Möglichkeit } catch(...) { // tu was } }Der Konstruktor hat keinen Rückgabewert, deshalb kann er keine Information über den Erfolg der Pufferinitialisierung per return geben.
Möglichkeit Exception:
Ich kann die Exception einfach nirgendwo fangen und "verschlucken", um sie den Kunden nicht auf zu nötigen. Der erwartet ja keine Exceptions, wenn er nicht exceptions() aufruft. Ich kann im Stream auch kein clear(ios_base::badbit) machen, da die Exception ja schon bei der Konstruktion des Puffers auftritt und somit auch den Streamkonstruktor abbricht. Das geht so denke ich nicht.
Ich könnte die Initialisierungsfunktion auch außerhalb des Konstruktors vom Buffer platzieren. Damit hätte ich das Exceptionproblem nicht mehr:class buf { public: buf() { } Traits::state_type init() { if(!load_dll) return FEHLERCODE_1; if(!get_socket) return FEHLERCODE_2; return ERFOLGSCODE; } }; class stream { private: buf b; public: stream() : b() { state_type ret = b.init(); switch(ret) { case FEHLERCODE_1: // ab hier kann man nicht mehr rausfinden, was nicht geklappt hat case FEHLERCODE_2: std::ios_base::clear(std::ios_base::badbit); break; case ERFOLGSCODE: // Juppi!!!! } } };Ich bin mir aber nicht schlüssig, ob man solche init()-Methoden explizit anbieten sollte. Ich finde, sie ist einfach zu sehr Teil der Implementierung und der Aufruf ist bei jeder Pufferverwendung erforderlich, dass der Ctor sie doch lieber automatisch aufrufen sollte, um das Interface einfach zu halten und den echten Sinn der Initialisierung zu wahren. Der Ctor hat nur eben keine Rückgabewerte und die Exceptions fliegen bei ihm durch, da die Member initialisuert werden, bevor ich im Stream-Ctor irgendwas machen kann.
Falls Du den spezifischen Fehler weiterleiten möchtest, so musst Du im stream (std::ostream oder std::istream) mit stream.exception(std::ios_base::badbit) das Exception-Flag so setzen, dass eine Exception des streambufs weitergeleitet wird
Wie müsste das dann ungefähr aussehen?
PS: Danke für deine Hilfe. :xmas2: Das Thema finde ich nicht sehr einfach und es gibt kaum noch jemanden, der mir hier helfen kann.
-
Ich weiß nicht, ob du es schon kennst -)
Es gibt noch die Möglichkeit das Schlüsselwort 'try' um den gesamten Block des Konstruktors (inkl. Initialisierungsliste) zu setzen:
stream() try : b() { } catch(...) { ... }s.a. http://www.cs.technion.ac.il/~imaman/programs/throwingctor.html
Schlüsselbegriff dafür: function-try-blockBeachte aber, daß du auf jeden Fall eine Exception werfen mußt bzw. der Compiler automatisch die Exception weiterwirft. Du kannst dann aber noch auf die geworfene Exception des Memberobjekts reagieren.
Ich hoffe, ich habe dein Problem richtig verstanden?
-
Hm... sehr komisch, dieses Sprachfeature kannte ich noch gar nicht, danke.

Aber leider würde das nicht mein Problem lösen, da Streams per default keine Exceptions werfen (nur mit Aufruf von exceptions()), der Catch-Block jedoch weiterwirft und nicht verschlucken kann. Ich bin dank Forensuche aber auf ein Buchexzerpt des von Werner genannten Buches gestoßen, zu finden auf http://www.angelikalanger.com/IOStreams/Excerpt/excerpt.htm
Das schaue ich mir erst mal an und melde mich hier noch mal, wenn's Probleme gibt oder fertig ist.
-
Das Problem 'eine Exception im Konstruktor abzufangen' hat zunächst mal nichts mit streambuf und streams zu tun.
Du könntest aber - was die Schnittstelle zum Benutzer angeht - genauso vorgehen wie beim std::fstream.Was Du auf jeden Fall benötigst ist die vom std::streambuf (bzw. basic_streambuf - das unterscheide ich hier mal nicht) abgeleitete Klasse; also den basic_sockbuf. Dieser Klasse kannst Du einen Konsruktor verpassen, der zunächst 'nichts' tut und zusätzlich eine Methode open (oder connect), die den Socket aktiviert. Das entspricht dem std::basic_filebuf mit der Methode open(..).
Einen eigenen Stream zu schreiben ist unnötig, da std::ostream (bzw. std::basic_ostream) für die Ausgabe und std::istream für die Eingabe bereits zur Verfügung stehen. Um den Anwender die Arbeit zu vereinfachen kann man aber einen von std::basic_ostream abgeleitet Stream anbieten (für die Eingabe entsprechende basic_istream), der nichts tut, außer die Klasse basic_sockbuf als Member aufzumehmen und die Methoden is_open und open (bzw. connect & is_connected) anzubieten. Das wäre das Pendant zu std::basic_ofstream (bzw. std::basic_ifstream). Z.B. basic_o(i)socketstream.Falls basic_sockbuf::open eine Exception wirft, wird diese im Body von basic_o(i)socketstream wieder gefangen und der rdbuf() wird nicht gesetzt, bleibt also bei NULL. Die Methode basic_osocketstream::is_open() fragt jetzt lediglich den rdbuf() des o(i)streams ab, ist dieser !=0, so wird true zurückgeliefert. Damit hat der Anwender eine Schnittstelle, wie bei std::fstream und es wird ihm auch keine Exception um die Ohren gehauen, wenn er dies nicht explicit einschaltet.
Als Buffer würde ich einfach einen std::vector< char_type > als Member in basic_sockbuf verwenden, statt von der Klasse stream_resource abzuleiten.
Gruß
Werner
-
Klar, das mit open ginge ja problemlos. Mein Problem steckt aber in der Initialisierung. Filebufs bekommen ihre Deskriptoren durch open(), das connect()-Äquivalent einer Clientsocket-Variante. Der socket-Deskriptor kommt aber von der Funktion socket(), unter Win kommt sogar noch eine DLL-Initialisierung vor. Würde ich den Konstruktor leer lassen, diese Initialisierungen somit explizit anbieten, entspricht das der 2. Variante in meinem obigen Post.
Dort verliere ich hingegen die Fehlerinformation. badbit - toll, das sagt dem Programmierer, hat nicht geklappt. Bloß was? errno, GetLastError() (unter Win WSAGetLastError()) ist dort aufschlussreicher. Ich glaube, ich werde deshalb dem Streampuffer auch Zustandsflags geben, der Nummer von GetLastError(), und im Stream eine kurze Methode definieren, vllt. sogar ein Exception-Schmeißer á la:class stream { // ... void last_error() throw(net_error) { switch(rdbuf().error_flag) { case 0: return; // alles okay case 0xDEADBEEF: throw net_error(0xDEADBEEF, "error loading dll"); // ... } } }; int main() { stream s; // macht init, setzt evtl. stream auf badbit, puffer auf getlasterror ... if(!s) { try { s.last_error(); } catch(net_error& e) { log("Fehler: " + e.what() + "\nNummer: " + e.number()); } } }So kann man zusätzlich noch genauere Informationen anbieten, Exception auf Nachfrage so zu sagen, ich denke, das nötigt sich auch nicht auf, fail() o. operator!() können dem User ja auch reichen.
Ich mache jetzt erst mal weiter, die Clientseite fertig. Werner, dir besonders vielen Dank für die Hilfe. Ich mache die Clientseite fertig und erstelle noch ein paar hüppsche Manipulatoren dazu. Ich werde mich spätestens wieder melden, wenn ich Leseoperationen machen will.
(Stichwort (A)synchrones Multiplexing).Den Thread grabe ich dann wieder aus.
-
Ad aCTa schrieb:
Klar, das mit open ginge ja problemlos. Mein Problem steckt aber in der Initialisierung. Filebufs bekommen ihre Deskriptoren durch open(), das connect()-Äquivalent einer Clientsocket-Variante. Der socket-Deskriptor kommt aber von der Funktion socket(), unter Win kommt sogar noch eine DLL-Initialisierung vor. Würde ich den Konstruktor leer lassen, diese Initialisierungen somit explizit anbieten, entspricht das der 2. Variante in meinem obigen Post.
Dort verliere ich hingegen die Fehlerinformation. badbit - toll, das sagt dem Programmierer, hat nicht geklappt. Bloß was? errno, GetLastError() (unter Win WSAGetLastError()) ist dort aufschlussreicher.Sicher .. es verbiete Dir doch niemand, der Klasse basic_sockbuf und der Klasse basic_o(i)socketstream eine GetLastErrorMsg() -Methode zu verpassen. Dort könnte dann der Text der zuletzt gefangenen Exception hinterlegt sein.
Vielleicht verstehe ich Dein Problem auch nicht so ganz.Gruß
Werner
-
Schon okay, ich mache es jetzt so. Wenn es am Ende nicht gut sein sollte, werde ich es eben bereuhen und verbessern.

-
Noch mal einen Tipp, da ich gerade Deinen anderen Thread hier gesehen habe.
Dieses Buch über neben-läufige Pattern könnte für Dich interessant sein. Im Netz gibt es auch einiges zum Design von Programmen die viele Anfragen verarbeiten müssen - insbesondere zum Proactor-PatternUnd ansonsten liefert boost.asio eigentlich schon alles. Selbst wenn man es nicht benutzt, als Vorlage taugt es alle mal.
So damit bist Du eine Weile beschäftigt

Werner
-
Okay, ich habe schon ma etwas weiter gemacht. Mei Interface schaut so aus:
#include <iostream> #include "ionstream.hpp" int main() { try { net::endpoint server("127.0.0.1", 55555); net::onstream nout(server, net::tcp); if(!nout) nout.error(); nout << " ahahl" << std::flush; for(;;); } catch(const net::net_error& e) { std::cerr << e.what() << "\nError code: " << e.number() << '\n'; } }Jetzt habe ich eine Frage:
Wie funktioniert die Kommunikation zwischen Stream und Puffer beim operator <<()? Ich habe die Überladung leider nicht gefunden, ich nehme aber an, dass der Operator buffer.xsputn() aufruft, oder? Wenn das der Fall ist, was muss xsputn() (von meinem Puffer überladen) zurückgeben, damit der Stream auf badbit oder failbit gesetzt wird? Standardmäßig ist es ja die Anzahl der in den internen Puffer geschriebenen Zeichen, unabhängig davon, ob ein overflow() dazwischenkommt. Wie bewerkstellige ich das? Sind die beiden Methoden so okay?virtual std::streamsize xsputn(const Ch* p, std::streamsize n) { setp(pbuf, pbuf + buflen()); std::streamsize i; for(i = 0; i < n; i++) if(sputc(p[i]) == -1) // löst irgendwann overflow() aus return 0; // ein Fehler ist passiert, bloß wie benachrichtigen? return i; } virtual int_type overflow(int_type c = Tr::eof()) { int_type result = ::send(handle, pbase(), pptr() - pbase(), 0); // den bis jetzt beschriebenen Puffer senden bstate = WSAGetLastError(); // Pufferzustand retten seekpos(0, std::ios_base::out); // pptr() auf 0 zurückstellen sputc(c); // neues Zeichen in den Puffer schreiben return result; // bei Fehler -1, sonst 0 }Danke :xmas2:
-
Ach und die zweite Frage betrifft setbuf().
Wie genau arbeitet die Funktion für gewöhnlich? Etwa so?virtual basic_sockbuf<Ch, Tr>* setbuf(Ch* p, std::streamsize n) { std::cout << "PUFFER SETZEN: " << p << std::endl; setp(p, p + n); // put-area setzen setg(p, p, p + n); // get-area setzten return this; }Wird die put/get-Area eigentlich nicht erst gesetzt, wenn eine Put/Get-Operation aufgerufen wird? Ich stehe da leider etwas auf dem Schlach.

-
Kann mir da niemand helfen?

Ich will nur erreichen, dass das erst mal funktioniert:
char buf[] = "Hello"; // = 6 byte net::basic_onstream nout; nout.rdbuf()->pubsetbuf(buf, 6); // --> setbuf() --> setp() UND setg() ? nout << "Ich bin eine Nachricht"; // operator <<() --> sputn() --> xsputn() --> sputc() --> overflow() bei 'i'Was genau verursacht setbuf()? Wird die get-Area UND die put-Area neu gesetzt, nicht aber pptr(), gptr(), eptr() usw.? Werden die dann erst durch setp/setg bei Aufrufen von xsgetn() und xsputn() beeinflusst?
Und da hätte ich auch den nächsten Salad: der physikalische Puffer (Ch*) ist ein Heap-Array, welches im Destruktor frei gegeben wird. Wenn ich nun in setbuf() den Ch*-Zeiger auf das per Parameter erhaltene neue Ch*-Array umbiege, wird natürlicherweise das vom Destruktor mit delete [] gelöscht. Nur: wer garantiert, dass das kein Stack-Array ist? delete auf Stack ist ja undefiniert. Muss ich dann 2 Puffer anlegen (Ch* default_buf, *user_buf)?
-
Oh hallo - gleich drei Postings und so viele Fragen ...
Ad aCTa schrieb:
Wie funktioniert die Kommunikation zwischen Stream und Puffer beim operator <<()? Ich habe die Überladung leider nicht gefunden, ich nehme aber an, dass der Operator buffer.xsputn() aufruft, oder?
Nein - das ist etwas anders. Solange Du nicht genau weißt, wie es funktioniert, vergisst bitte 'xsputn' zunächst mal.
Ad aCTa schrieb:
... was muss xsputn() (von meinem Puffer überladen) zurückgeben, damit der Stream auf badbit oder failbit gesetzt wird?
Mit der Methode overflow allein, kannst Du einen überladenen Streambuf realisieren. Mit dem Zeichen, das nicht mehr in den internen Buffer (s.u.) geschrieben werden kann, wird streambuf::overflow aufgerufen. Kommt die Methode mit EOF zurück, so liegt ein Fehler vor und der Stream, der den Buffer nutzt, setzt das failbit.
Da Du aber auf jeden Fall einen Speicherbereich mit mehr als einem Zeichen für die Socketkommunikation benötigst, gehe ich gleich einen Schritt weiter.
Du benötigst in dem basic_sockbuf ein Stück Memory (den internen Buffer), wo man Zeichen hineinschreiben kann, am besten als 'Member' des basic_sockbuf. Der Einfachheit halber nehme ich mal an, dass da ein std::vector< char_type > existiert. Dieser wird als interner Buffer benutzt. Das sieht im Prinzip so aus:
#include <streambuf> #include <vector> template< typename Ch, typename Tr = std::char_traits< Ch > > class basic_sockbuf : public std::basic_streambuf< Ch, Tr > { public: basic_sockbuf() : m_buf( 400 ) // einfach mal 400 Zeichen reservieren { setp( &m_buf[0], &m_buf[0] + m_buf.size() - 1 ); // Put-Pointer auf den Buffer; ein Zeichen Platz lassen } protected: virtual int_type overflow( int_type m = traits_type::eof() ) { const char* pend = pptr(); if( !traits_type::eq_int_type( m, traits_type::eof() ) ) { // es wurde noch ein Zeichen übergeben *pptr() = traits_type::to_char_type( m ); // hinten anfügen ++pend; } if( !senddata( pbase(), pend ) ) return traits_type:eof(); // Fehler setp( &m_buf[0], &m_buf[0] + m_buf.size() - 1 ); // Put-Pointer neu setzen return traits_type::not_eof( m ); // ok } private: bool senddata( const char_type* first, const char_type* last ) { // ... hier alle Zeichen im Intervall [first,last) über den Socket schicken return true; // ok } // -- Members std::vector< char_type > m_buf; };Das bezeichnet man als eine gepufferte Ausgabe. Wie Du siehst benötigt man dazu weder setbuf (hat damit nichts zu tun) noch xsputn.
Ad aCTa schrieb:
Mei Interface schaut so aus: ..
Es sollte ungefähr so aussehen:
#include <iostream> #include "sockbuf.hpp" int main() { try { basic_sockbuf< char > server; // ("127.0.0.1", 55555); Parameter nach Bedarf hinzufügen std::ostream nout(&server); // Du brauchst keinen(!) eigenen ostream nout << " ahahl" << std::flush; for(;;); } catch(const net::net_error& e) { // das könnte von 'basic_sockbuf< char >' geworfen werden std::cerr << e.what() << "\nError code: " << e.number() << '\n'; } return 0; }Damit das std::flush funktioniert, musst Du noch das basic_streambuf::sync überladen.
Danach sollte das Senden von Daten grundsätzlich funktionieren.
Gruß
Werner
-
Erst mal vielen Dank. Um das mit dem Overflow zu klären:
// Aufruf von overflow durch basic_streambuf<Ch, Tr>::sputc, wenn // pptr() - pbase() = 1, also noch ein Zeichen in den Puffer passt virtual int_type overflow(int_type c = Tr::eof()) { const Ch* end = pptr(); if(!Tr::eq_int_type(c, Tr::eof())) { // letztes Zeichen in den Puffer lesen, voll *pptr() = Tr::to_char_type(c); ++end; } if(::send(handle, pbase(), end - pbase(), 0) == -1) { bstate = WSAGetLastError(); return Tr::eof(); } setp(&pbuf[0], &pbuf[0] + pbuf.size() - 1); return Tr::not_eof(c); }Das sehe ich so richtig, oder?
Ich habe meinen Stream jetzt auch mit Manipulatoren getestet und den sockbuf mal für std::cout gesetzt, klappt alles wunderbar. Ich habe den Stream einfach wegen besserer Fehlerbehandlung erstellt, und auch wegen der TCP/UDP-Steuerung, das kann man einfach nicht vernünftig mit std::basic_ostream alleine implementieren. Der vector zum speichern ist mir noch ein Dorn im Auge. Wenn man den Puffer (und dessen Größe) manuell setzten will, was im Netzwerk vorkommen kann, wegen lahmer Bandbreite etc., sollte pubsetbuf funktionieren. Ich muss mir da noch was einfallen lassen.
-
Ad aCTa schrieb:
Erst mal vielen Dank. Um das mit dem Overflow zu klären:
// Aufruf von overflow durch basic_streambuf<Ch, Tr>::sputc, wenn // pptr() - pbase() = 1, also noch ein Zeichen in den Puffer passt virtual int_type overflow(int_type c = Tr::eof()) { const Ch* end = pptr(); if(!Tr::eq_int_type(c, Tr::eof())) { // letztes Zeichen in den Puffer lesen, voll *pptr() = Tr::to_char_type(c); ++end; } if(::send(handle, pbase(), end - pbase(), 0) == -1) { bstate = WSAGetLastError(); return Tr::eof(); } setp(&pbuf[0], &pbuf[0] + pbuf.size() - 1); return Tr::not_eof(c); }Das sehe ich so richtig, oder?
ich denke - ja. Nur der Kommentar ist falsch - wenn 'pptr() - pbase() == 1', dann liegt genau schon ein Zeichen im Buffer. Mit epptr()-pptr() bekommt man den freien Bereich im internen Buffer, wenn der 0 ist - also epptr()==pptr() - dann wird vom stream overflow gerufen.
Ad aCTa schrieb:
Ich habe den Stream einfach wegen besserer Fehlerbehandlung erstellt, und auch wegen der TCP/UDP-Steuerung, das kann man einfach nicht vernünftig mit std::basic_ostream alleine implementieren.
Wenn ich das lese, bin ich mir gar nicht sicher, ob Du das mit den Streams richtig verstanden hat. Ein Stream selbst hat per se nichts mit der Fehlerbehandlung z.B. einer Socketverbindung zu tun. Auch die TCP/UDP-Steuerung ist Sache des basic_sockbuf. Ein eigener Stream macht Sinn, wenn er z.B. von std::ostream abgeleitet ist, den socketbuf als Member beinhaltet und dessen spezifische Methoden durchleitet. Wenn Du das so meinst, was ist dann aber net::endpoint?
Wie sieht denn Dein net::onstream aus? Von was ist er abgeleitet?Ad aCTa schrieb:
Der vector zum speichern ist mir noch ein Dorn im Auge. Wenn man den Puffer (und dessen Größe) manuell setzten will, was im Netzwerk vorkommen kann, wegen lahmer Bandbreite etc., sollte pubsetbuf funktionieren. Ich muss mir da noch was einfallen lassen.
Der vector war nur ein Beispiel, es ist natürlich prinzipiell jedes Stück Memory möglich; es ist schlicht praktisch, wenn das innerhalb des socketbuf liegt. Aber auch hier verstehe ich Deine Anforderung nicht so ganz.
Nachtrag zu sync. Das kann so aussehen:
virtual int sync() { return traits_type::eq_int_type( overflow(), traits_type::eof() )? -1: 0; // -1: Fehler; 0: Ok }Gruß
Werner
-
Naja, mein sync() sieht eigentlich nur so aus:
virtual int_type sync() { return overflow(); }Wenn ein Fehler auftritt und overflow() Tr::eof() zurückgibt, kann ich das auch gleich weiterreichen.
> Auch die TCP/UDP-Steuerung ist Sache des basic_sockbuf.
Habe mich etwas unglücklich ausgedrückt. Ich will nur die Informationen an den Puffer weitergeben. Einen Endpunkt hat nun mal jede Verbindung, deshalb habe ich ihn extern einmal repräsentiert. Der Konstruktor muss nun einen Endpunkt aufnehmen können und zusätzlich ein paar Zusatzspezifikationen:
template <typename Ch, typename Tr = std::char_traits<Ch> > class basic_onstream : public std::basic_ostream<Ch, Tr> { public: typedef basic_sockbuf<Ch, Tr> buf_type; basic_onstream() : std::basic_ostream<Ch, Tr>(&buffer) { } basic_onstream(const std::basic_streambuf<Ch, Tr>* b) : basic_ostream<Ch, Tr>(b), buffer(*b) { } // default: net::tcp explicit basic_onstream(const endpoint& ep, const sockmodes& sm = sockmodes(0xF0F0)) : std::basic_ostream<Ch, Tr>(&buffer) { // basic_sockbuf initialisieren, Socketmodus weiterreichen, Flags setzten etc. } // noch etwas anderes.... private: // kopieren verhindern etc. buf_type buffer; };Die Sockmodes sin ähnlich ios_base::in/out, nur noch um Eigenschaften wie TCP und UDP erweitert. Die Konstruktoren sind einfach zu speziell. Ich denke so wie fstream extra existieren muss, muss auch ein socket_stream existieren, alleine schon wegen der Konstruktoren.
Was mich hierbei etwas stört ist das doppelte Vorhandensein des Puffers. Einmal speichere ich einen und den selben als Zeiger in der Basisklasse, einmal habe ich selber einen da. Es ist das Slicing-Problem: wenn ich buffer nicht als Element in meiner Klasse hätte, müsste ich rdbuf() anwenden. Da das aber einen Zeiger auf basic_streambuf zurückgibt, entfallen alle speziellen Methoden meines eigenen Puffers (das Interface ist logischerweise nicht da).
-
Ad aCTa schrieb:
Naja, mein sync() sieht eigentlich nur so aus:
virtual int_type sync() { return overflow(); }Wenn ein Fehler auftritt und overflow() Tr::eof() zurückgibt, kann ich das auch gleich weiterreichen.
Nicht ganz, da sync protected ist.
Und lt. Standard liefert sync -1 im Fehlerfall und 0, wenn alles ok ist.Ad aCTa schrieb:
Was mich hierbei etwas stört ist das doppelte Vorhandensein des Puffers.
Ich sehe immer nur einen Puffer. Wo ist da was doppelt?
Dein net::onstream ist das was ich meinte - das sollte ok sein.
Gruß
Werner
-
> Ich sehe immer nur einen Puffer. Wo ist da was doppelt?
Na die Basisklasse hat ja auch einen. Irgendwo muss sie irgendeinen basic_streambuf<>*-Member haben, der von rdbuf() zurückgegeben wird. Ich würde ja gerne genau diesen internen Puffer benutzen (der mit dem Member identisch ist), aber das geht ja eben (wegen Slicing-Problem) nicht.