// // sockstream.cc - various tests on socket streams, using C++ with homemade // networkbuf, C++ with gnu's stdio_filebuf, and C with // plain old fopen and friends. Compiles on gcc 3.3.4. // // Copyright 2005, Chris Frey. To God be the glory. // You are free to use, modify, redistribute, and sublicense this code, // as long as this copyright message is not removed from the source, // and as long as the existence of any changes are noted in the source // as well. (i.e. You don't need a complete history, just don't claim // that the modified code was written entirely by me -- include your own // copyright notice as well.) // // If you find this code useful, please email me and let me know. // // Conclusion: // C++'s getline() processes the stream one character at a time, in order // to search for the delimiter. Therefore, with networkbuf, which only // fills the input buffer as much as it can with the available network // data and then returns, it doesn't block until it really needs to, // and gets all available lines from the kernel, even with a nice large // buffer. // // With stdio_filebuf, this is built on top of C's streams, which has its // own buffer. So the likelyhood of blocking before getting all the // data is higher, since C's buffers may empty and get forced to fill // with an internal fread() while C++'s getline is only asking for a // single char. // // C++ streambufs and derived classes are actually pretty cool. I just // wish they had used more user-friendly names. But as is, you can // still use functions like xsgetn and xsputn as read and write, and // the streambuf supplies all the needed buffering. Plus you can do it // on a character basis, while still maintaining efficiency with // buffered kernel calls. Plus there are iterators to work with these // things, which I haven't fully investigated. // // Time to rethink my design of reuse lib's buffer classes, and turn // them into streambufs perhaps, or at least derive streambuf interface // classes to make use of them. Also, the transfer classes should be // able to use streambufs as well. // // This stuff is complicated, and not commonly well documented, but // it sure is useful. // // Chris Frey // // 2005/02/13 // // c++ io #include #include // c io #include // networking #include #include #include #include // posix io #include #include #include // debugging #include using namespace std; void cpp_networkbuf(int s); void cpp_stdio_filebuf(int s); void cpp_work(istream &nin, ostream &nout); void c_work(int s); // // networkbuf - gives a simple buffer to posix read/write calls, suitable // for use with a network. Do not use this for plain files, // since there is no conflict checking code for reading/writing // to different sections of the same file. It is meant for // processing two independent streams, just like a network. // class networkbuf : public streambuf { int _fd; char_type *_getbuf, *_putbuf; size_t _getbuf_size; size_t _putbuf_size; public: networkbuf(int fd, size_t size) : _fd(fd) { _getbuf = new char_type[size]; _getbuf_size = size; // set the input buffer to "nothing there" setg(_getbuf, _getbuf, _getbuf); _putbuf = new char_type[size + 1]; // one more, for when // overflow has extra byte _putbuf_size = size; // set the output buffer to "empty" setp(_putbuf, _putbuf + _putbuf_size); } ~networkbuf() { sync(); close(_fd); delete [] _getbuf; _getbuf_size = 0; delete [] _putbuf; _putbuf_size = 0; } // write out any data in the out buffer, and write c as well if c!=eof() virtual int_type overflow (int_type c) { // cout << "overflow called: " << c << endl; assert( pbase() ); bool have_extra = c != traits_type::eof(); // pbase() is a pointer at the start of our buffer, // and pptr() is a pointer to the next free spot, // so we can check for data in buffer by comparing them if( pptr() > pbase() || have_extra ) { // we have something to write! ssize_t count = pptr() - pbase(); if( have_extra ) { // tack extra value onto the end of the buf // (note constructor added a byte for us here, // just in case) *(pptr()) = traits_type::to_char_type(c); count++; } // loop and write all bytes, even if ::write() returns // less than what we asked for ssize_t written; char_type *wp = pbase(); do { written = ::write(_fd, wp, count); if( written > 0 ) { count -= written; assert(count >= 0); wp += written; } else { // fixme... do we set badbit here? // failbit? return traits_type::eof(); } } while(count); // reset output buffer to empty state setp(_putbuf, _putbuf + _putbuf_size); } return traits_type::not_eof(c); } // called on ostream::flush()... return -1 here on failure virtual int sync() { if( overflow(traits_type::eof()) == traits_type::eof() ) return -1; return 0; } // fill the empty buffer, and return the first char in it, // without advancing the pointer. if EOF, return eof() and // leave gptr() == egptr() (this is the default, since this // function won't be called if this wasn't the case, so we // just do nothing if EOF, below) virtual int_type underflow () { int_type ret = traits_type::eof(); ssize_t count; if( (count = ::read(_fd, _getbuf, _getbuf_size)) > 0 ) { // set our input buffer to indicate new data setg(_getbuf, _getbuf, _getbuf + count); // return new character ret = traits_type::to_int_type(*_getbuf); } // cout << "underflow called: " << endl; return ret; } }; void cpp_networkbuf(int s) { networkbuf nb(s, 100); iostream mail(&nb); cpp_work(mail, mail); } void cpp_filebuf(int s) { // You must make separate filebufs here, since it seems the filebuf // classes assume a file is seekable, while a socket is really just // two independent data streams, one in, ont out. So treat it like // that in code. __gnu_cxx::stdio_filebuf fbin(s, ios::in, true, 100); __gnu_cxx::stdio_filebuf fbout(s, ios::out, false, 100); istream mailin(&fbin); ostream mailout(&fbout); cpp_work(mailin, mailout); } void cpp_work(istream &nin, ostream &nout) { cout << "Sending data to socket..." << endl; nout << "MAIL from: cdfrey" << endl; nout << "RCPT to: destination-dude" << endl; // nout << "quit" << endl; // commented out to see buffer behaviour cout << "Looping for data..." << endl; char buffer[1024]; while( nin.getline(buffer, sizeof(buffer)) ) { cout << "socket data: " << buffer << endl; if( nin.fail() ) { cout << "failbit \r" << flush << endl; } if( nin.bad() ) { cout << "badbit \r" << flush << endl; } if( nin.eof() ) { cout << "eofbit \r" << flush << endl; } } /* // alternate method of copying a streambuf cout << "Copying all socket data..." << endl; cout << nin.rdbuf(); */ } void c_work(int s) { FILE *mail = fdopen(s, "r+"); if( mail == NULL ) return; cout << "Sending data to socket..." << endl; fputs("MAIL from: cdfrey\n", mail); fputs("RCPT to: destination-dude\n", mail); fputs("quit\n", mail); cout << "Looping for data..." << endl; char buffer[1024]; while( !feof(mail) ) { // normal method // fgets(buffer, sizeof(buffer), mail); // play with seeking on a network to test its behaviour fseek(mail, 10, SEEK_CUR); size_t count = fread(buffer, 1, 1, mail); if( count == 1 ) ungetc(buffer[0], mail); count = fread(buffer, 1, 1, mail); buffer[count] = 0; if(count == 0) cout << "(" << count << ")" << flush; cout << buffer << flush; } fclose(mail); } int main() { std::ios::sync_with_stdio(false); // optional, only affects cout, etc. // setup socket and connect to local mail host int s = socket(PF_INET, SOCK_STREAM, 0); if( s == -1 ) { cerr << "socket error" << endl; return 1; } sockaddr_in dest; dest.sin_family = AF_INET; dest.sin_port = htons(25); dest.sin_addr.s_addr = inet_addr("127.0.0.1"); if( connect(s, (sockaddr *) &dest, sizeof(dest)) == -1 ) { cerr << "connect error" << endl; return 1; } // or use a file (for testing) // int s = open("blah.txt", O_RDWR); cpp_networkbuf(s); // cpp_filebuf(s); // c_work(s); return 0; }