views:

172

answers:

3

Howdy. I am in a networking class and we are creating our own "networking API" using the socket functions we have learned in class thus far.

For the assignment the professor provided the already complete chat and server programs and we were to fill in a blank .c file that had an associated header file that described the function calls in our little "networking API" that the chat and server programs used.

I am close to done but have ran into a problem. I was gonna go in and mess with my code, but all the other parts of my "networking API" work so I didn't want to to screw with it when I am so close to done and have a little change cause other parts not to work. Plus I am not 100% sure that I do in fact know what my bug is.

My problem is with my function called recv_line that is supposed to mimic recv().

My function recv_line gets handed the file descriptor from the client for the connection that was setup with the server. I then go ahead and associate a stream with the file descriptor using fdopen. The bit of code looks like:

  // Associate a stream with the sock_fd
  if (NULL == (fdstream = fdopen(sock_fd, "r"))) {
    return -1;
  }

Now this is where I believe my bug to be but I am not 100% sure as I said. Firstly, the first time I use my recv_line function it works great. It does in fact recv_line the line as expected. But the problem arises the second time around.

I stepped through my function with GDB and the above bit of code does excute without problems. I then go on to peel off a character from my stream (using fgetc) and check if it is equal to the EOF char. If it is I return, else I will process it. The second time that I call recv_line it returns cuse it read the EOF char every time.

I'm assuming this is happening because the data sent to the client is in the original stream (the one created the first time around) and the second time I call fdopen there isn't any data in the second stream I created? I'm not completely sure how fdopen works when I call it the second time around?

On I side note I my reading of my data this way as we are supposed to continue reading data until we read an EOF or the character sequence \r\n. I use getc and ungetc with the stream to provide a look ahead mechanism that will check for the EOF or the \r\n sequence.

So with all the being said, does anyone know what exactly is going on with my big of code and program? I Googled around for a method to check if a FD has an associated stream to it and wasn't successfully in finding/seeing anything. I did some research to see if there was some fstat time function that would let me see associated steams and came up with nothing. Can I just dup the FD that is passed in to the function recv_line and then fdopen on the dupped FD and avoid all these problems?

Thanks for all the help. I am sorry if I wasn't clear enough. I will do my best to clarify anything if you ask for me to do so. And sorry my question was so lengthy. =*(

Thanks again though. I really appreciate your help.

+1  A: 

I'm not sure if I really understand your problem, but I'd suggest to open the in/out streams directly when the socket is created and keep them in a struct together with the socket as long as the socket is valid (connected). This way you don't need to open new streams for every line. The recv_line function would then have the input stream as parameter instead of the socket.

x4u
+2  A: 

This is from the fdopen man page:

The fdopen() function associates a stream with the existing file descriptor, fd. The mode of the stream (one of the values "r", "r+", "w", "w+", "a", "a+") must be compatible with the mode of the file descriptor. The file position indicator of the new stream is set to that belonging to fd, and the error and end-of-file indicators are cleared. Modes "w" or "w+" do not cause truncation of the file. The file descriptor is not dup'ed, and will be closed when the stream created by fdopen() is closed. [emph. mine]

In other words, as I understand it (this isn't really my area of expertise), your idea of "hijacking" the socket file descriptor over and over again is in fact the problem, because when you're done with it for the first time, you don't only close your stream, but the socket connection alongside with it.

balpha
+2  A: 

I'm assuming this is happening because the data sent to the client is in the original stream (the one created the first time around) and the second time I call fdopen there isn't any data left in the file descriptor for in the second stream to read I created?

That is pretty much correct (with some minor corrections). The major duty of a stream object is to read the file descriptors data stream from the kernel to userland so you don't need the overhead of a system call. But once the stream object reads the data stream, the kernel advances it's 'read pointer' so the next time you try to read the data from the file descriptor, it picks up where you left off.

You can only call fdopen one time for a given descriptor. If you can restructure your code so that there is a place where you can easily call fdopen once, that is the way to go.

Once you call fdopen, you should no longer do anything with the file descriptor - not even close it (when you call fclose the underlying descriptor will be closed).

Based on Jonathon Leffler's comment, I'm guessing the professor's code is also going to close the descriptor. If that is true, you cannot use fdopen.

Some ideas:

  1. Just read the fd byte by byte. Not very performant but would work.
  2. Are you allowed to assume that your code is only handling one connection at a time? If so, just have a static buffer in recv_line that buffers the fd data. Fill up the buffer when it's empty and then read data from it.
  3. If you need to handle multiple connections, you can extend #2 to have one buffer per file descriptor. You will need some sort of map to handle this.

Update based on comment

@Chris - let me explain the risk of using fdopen. Once you have used fdopen on a file descriptor, you must call fclose (or you risk leaking resources allocated for the FILE *) and you cannot call close on the descriptor (since fclose the descriptor for you can run into problems closing the descriptor multiple times). So, unless your code has already been delegated the responsibility of closing the descriptor (in which case you can then just call fclose), you can't use fdopen.

There is no built in way to detect if fdopen has been called on a descriptor. You need to add logic yourself. You would need to keep a collection of FILE *'s returned by fdopen; if you already had a FILE * for a given descriptor, you would use it and otherwise you would call fdopen then.

R Samuel Klatchko
By the sounds of the question, the professor has created a header which defines the interface to recv_line() and changing that is not an option.
Jonathan Leffler
recv_line doesn't in fact have to close the FD. The recv_line should be able to work on any connection that is called on, whether one connection has been established or many.As far as calling fdopen only once, how do I go about checking whether I have called fdopen on my FD already? That seems to be my problem. I could create a map but it seems to be it would have to be dynamic and I don't know how to do that (haven't Googled it yet...). I am presuming I would just keep mallocing/reallocing an array that was static?Edit - Looks like I will just keep mallocing/reallocing stuff :D
Chris