1

All I have found until now about receiving data with sockets (recv) is in plain old C.

And even if it's C++, it's either receiving byte-by-byte on a std::vector<char> (eww!), or defining a static size (e.g. vec.resize(BIG_NUMBER)), and then receiving.

However, all of that seems very inefficient and / or ugly, so I was wondering, what's the most efficient, correct, and elegant way of doing it in C++? (not C)

A good example of where'd somebody need to receive an unknown amount of data is a web browser. (ignoring the Content-length header for the sake of the example)

Community
  • 1
  • 1
rev
  • 1,861
  • 17
  • 27
  • 2
    You can try using a library with abstractions. Try Poco, for example. – Photon Feb 16 '15 at 07:22
  • Not using recv, and instead using a C++ function? There is no more elegant way of "using recv". You'll have to "not use it", and use something else instead. – sashoalm Feb 16 '15 at 07:22
  • @sashoalm no, I know that `recv` is a C API, and not C++. My question is referring to the fact that in C, you need to do the memory allocation yourself, while in C++, there _may_ be a way to avoid it; and I'm wondering if there's such a way. – rev Feb 16 '15 at 07:24
  • @AcidShout So you're asking if recv can tell you the size of the vector? Then yes it can, but then the question title should have been "Find out the buffer size needed by recv" or something like that. See https://stackoverflow.com/questions/2862071/how-large-should-my-recv-buffer-be-when-calling-recv-in-the-socket-library – sashoalm Feb 16 '15 at 07:30
  • @sashoalm no, because that's how the linked question does it (more or less). I mean _completely_ abstract away the memory allocation part (including the size). like `std::string`'s 'magic'. – rev Feb 16 '15 at 07:31
  • @AcidShout What are you asking? For us to tell you if you can code it? Go code it. It can be done, of course. But you'll have to write that code, it won't write itself. And I certainly won't write it for you. – sashoalm Feb 16 '15 at 07:33
  • @sashoalm so there's no way to do it with STL, and I have to write my own container then, right? – rev Feb 16 '15 at 07:35
  • @AcidShout Well, I don't know of such a way at least. But think of it that way. If there was no STL at all, you could still write it yourself. Somebody had to write STL, it didn't write itself. And questions about existing libraries are off-topic as per the rules and closed. – sashoalm Feb 16 '15 at 07:38
  • *"no way to do it with STL, and I have to write my own container then, right?"* - that's a bit melodramatic. You can trivially `read`/`recv` into an automatic buffer ala `char buffer[8192];` and `+=` to a `std::string` or `std::vector` or whatever tickles your fancy. Alternatively, you can `.resize` enough additional space (e.g. `x.size() + 8192`) to `recv()` directly into the container (at `x.data() + x.size()`), then `x.resize()` back to the new cumulative size. – Tony Delroy Feb 16 '15 at 07:48
  • Separately, if you don't want to program at the socket level, consider a higher level library like `boost::asio`. – Tony Delroy Feb 16 '15 at 07:49
  • @TonyD: Of course, you don't want to do anything with `x.data() + x.size()`, you have to use a stored copy of what size used to be. – Ben Voigt Feb 16 '15 at 07:50
  • @BenVoigt: yes... I added the explicit mention of resize after I wrote the `.data() + .size()` bit... all a bit of a tangle but should un-knot with a little thought during implementation. – Tony Delroy Feb 16 '15 at 07:54

3 Answers3

5

I don't know any STL way to doing so , but anyway , STL has nothing to do with it. when dealing with old C API's one can simply wrap the mess with C++ classes , make the implementation not visible for the outside developer.

I suggest writing Socket class that includes SOCKET as inner member , than creating some methods that wrap the C around (Assuming we talk about stream socket!):

std::string Socket::receive (){
char buffer [SOME_SIZE];
int bytesRead;

std::string receivedData;

do{
    bytesRead = recv(socket,buffer,SOME_SIZE-2,0);
    buffer [SOME_SIZE-1] ='/0';
    receivedData += buffer;
} while (bytesRead != 0 && bytesRead != SOCKET_ERROR );

return receivedData;
}

BTW , I suspect that you may not understand recv() completely , recv() fills out a buffer, it will fill it out as long as you call recv() repeatedly. the number of bytes received by the socket are not limited to the buffer size. you can use this fact to append the data into std::string

David Haim
  • 25,446
  • 3
  • 44
  • 78
  • 1
    Very useful illustration. A few notes: the code presented will block indefinitely and drain all socket input until the socket's disconnected or has an error, whereas often you want to specify a maximum size or some sentinel character to watch for (e.g. a newline); `bytesread` does return `int` on windows, but a `ssize_t` on POSIX systems, with `0` meaning orderly shutdown and `-1` meaning an error (then see `errno`), you can support `'\0'` transfer over the socket by ditching the `buffer[SOMESIZE-1] = '\0'` line and replacing the `+=` line with `receivedData.append(buffer, bytesRead);`. – Tony Delroy Feb 17 '15 at 01:52
3

If you don't mind using 3rd-party libraries, Boost has nice templates for handling sockets in C++. Take a look at the equivalences between plain old C sockets API functions and theirs; and also in this example you'll see how to use streambufs to avoid allocating memory for your receiving buffers.

asamarin
  • 1,544
  • 11
  • 15
  • 1
    I was trying to find a way to compose this answer, but this answer really nails it – John Castleman Feb 16 '15 at 07:44
  • 1
    Here's the really good news, if developing with Visual Studio: there is now Nuget support for C++ third-party libraries, and Boost is one of them. It's also segregated very intelligently into one header package (boost) and multiple library packages (boost_*) so you don't have to bring down the entire Boost library anymore. If you just bring down the header package and start coding, the linker will tell you which library packages to download. For example, using the linked example, I needed boost_system (for boost::asio), boost_date_time, and boost_regex. – John Castleman Feb 16 '15 at 08:02
0

Fundamentally, for each socket you need to have two user-space buffers: one for sending data, another for receiving.

The optimal size for the receiving buffer is the same as the kernel buffer size. If your buffer is smaller - your application does more recv calls; if larger - that surplus capacity may never get used.

The wire protocol you use would normally have maximum message size. Your buffers must be at least that large. Bandwidth-delay product may also require adjusting your minimum buffer size.

Normally, you would like to allocate the buffers once along with the socket and not resize them after that. You may like to store your buffers as char[], std::array<char> or std::vector<char> (but avoid new char[]).

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271