Search code examples
c++inheritanceiostreamwinsock2

C++ Inheriting from iostream failbit set


Where to begin? The beginning, I suppose.

I'm new to socket programming. I wanted to give it a shot. After research, I found that it seems that there is no standard C++ oop socket library (there were some third party libraries that might have been oop, but I decided to make my own anyway because, well, it's more fun that way since it's just a personal project anyway), so I decided to make my own.

I decided that my new library would be integrated with iostream. Sure, this probably isn't a good idea usually, but I figured 1) I had a decent reason to and 2) it was just as an academic exercise anyway. It couldn't hurt to at least learn how to do it.

Thus, I did a little digging and came up with this design:

class NetworkStream : public std::iostream {
    public: 
        // class network streambuff
        // should handle the actual writing
        class NetworkBuff : public std::streambuf {
            private:
                SOCKET socket; // I'm working with winsock2, btw
            public:
                NetworkBuff(SOCKET s);

            protected:
                // out

                virtual std::streamsize xsputn(const char* buffer, std::streamsize size);

                virtual std::streamsize overflow(char buffer);

                virtual int underflow();

                virtual std::streamsize xsgetn(char* buffer, std::streamsize size);

        };

        // constructors
        NetworkStream(SOCKET socket); // sets up the socket in NetworkBuff
        virtual ~NetworkStream();

    }; // end network stream

This NetworkStream object is supposed to handle the actual reading and writing on the network. The connection is maintained by higher level code (and by that, I mean an object that inherits from NetworkStream). That code is working (as far as I can tell, anyway) and is irrelevant to my question, so I've omitted it. I'm just mentioning it so you understand my design.

Anyway, the actual implementation for my new stream object looks something like this:

// class network streambuff
// should handle the actual writing

//constructor
NetworkStream::NetworkBuff::NetworkBuff(SOCKET s) {
    socket = s;
}

// out

std::streamsize NetworkStream::NetworkBuff::xsputn(const char* buffer, std::streamsize size) {
    // well, let's send the data
    int result = send(socket,buffer,static_cast<int>(size),0);

    // if that didn't work, throw an error
    if(result == SOCKET_ERROR) throw("Send Failure: " + WSAGetLastError()); 

    // NOTE: I realized after I wrote this that this throw may be useless, 
    // since I think iostream catches any errors thrown at this level, but 
    // just in case

    // and pass through to streambuff, because, well, it can't hurt
    return std::streambuf::xsputn(buffer, size);
}

// basically do the same thing as before...
std::streamsize NetworkStream::NetworkBuff::overflow(char buffer) {
    // well, let's send the data
    int result = send(socket,buffer,sizeof(buffer),0);

    // if that didn't work, throw an error
    if(result == SOCKET_ERROR) throw("Send Failure: " + WSAGetLastError()); 

    // and pass through to streambuff, because, well, it can't hurt
    return std::streambuf::overflow(buffer);
}

As far as I can tell, this works like a charm. I step through my debugger, and my xsputn() gets called. So I can do something like this:

std::string data = "Hello, world!";
networkstream << data;

And it gets called. I think it sent successfully. It doesn't stepping through my debugger, result doesn't have an error. However, I haven't tested it fully yet because it's my receiving function that doesn't work:

std::streamsize NetworkStream::NetworkBuff::xsgetn(char* buffer, std::streamsize size) {
    // well, let's read the data
    int result = recv(socket,buffer,static_cast<int>(size),0);

    // if that didn't work, throw an error
    if(result == SOCKET_ERROR) throw("Receive Failure: " + WSAGetLastError());

    // Now this I think is wrong, specifically comparing against SOCKET_ERROR.
    // That's not my problem, though. My problem is that this function seems to 
    // never get called, so a wrong comparison doesn't matter yet anyway

    // and pass through to streambuff, because, well, it can't hurt
    return std::streambuf::xsgetn(buffer, size);
}

// Now this guy, I'm pretty extra sure is probably wrong, but it's what I got. I 
// couldn't find a good example of using underflow, so I did my best from the 
// research I did find
int NetworkStream::NetworkBuff::underflow() {
    // well, let's read the data
    int result = recv(socket,gptr(),sizeof(*gptr()),0);

    // if that didn't work, throw an error
    if(result == SOCKET_ERROR) throw("Recieve Failure: " + WSAGetLastError());

    // and pass through to streambuff, because, well, it can't hurt
    return std::streambuf::underflow();
}

It compiles real nicely. However, when I try to use it:

std::string data = "";
networkstream >> data; // should wait for data on the network

It seems to pretend nothing happened. No, actually, according to my debugger, it seems to set a fail bit, ignore my virtually overloaded functions, and continue execution like nothing happened.

So, my question, in one paragraph, is: what exactly is wrong with my underflow/xsgetn function that is causing it to fail and essentially ignore my code? I'm sure I'm doing something wrong, but what exactly?


Solution

  • As it turns out, I can't just pass through. That's not how streambuf is designed.

    Best as I can tell (and someone please correct me if I'm wrong), streambuf actually lives up to its name. It is supposed to be a buffer for the stream.

    What wasn't clear, to me anyway, is that streambuf, while it is supposed to handle buffering, it has no actual internal way of holding or handling data. That's the whole point of overloading it. YOU are supposed to handle buffering. You can't pass through (again, someone correct if I'm wrong) through to the parent. That's just not how it's designed.

    What you need (or I needed, I suppose) was a good implementation of underflow and implement it proper. Fortunately, after more digging, I was able to find one. Long story short, I went with something like this:

    // Shoutout to http://www.voidcn.com/article/p-vjnlygmc-gy.html where I found out
    // how to do this proper
    std::streambuf::int_type NetworkStream::NetworkBuff::underflow() {
        const int readsize = 30;
    
        // first, check to make sure the buffer is not exausted:
        if(gptr() < egptr()) {
            return traits_type::to_int_type(*gptr());
        }
    
        // Now, let's read...
    
        // btw, inputbuffer is a data member that is a std::string
        // clear the buffer
        inputbuffer.clear();
        inputbuffer.resize(readsize);
    
        // let's read
        int bytesread = recv(socket,&inputbuffer[0],static_cast<int>(readsize)); 
    
        // return the end of file if we read no bytes
        if(bytesread == 0) {
            return traits_type::eof();
        }
    
        // set the pointers for the buffer...
        setg(&inputbuffer[0],&inputbuffer[0],&inputbuffer[readsize-1]);
    
        // finally, let's return the data type
        return traits_type::to_int_type(*gptr());
    
    }