Search code examples
d

Socket - receive an exact amount of data


I'm trying to make a socket manager for a program.

My problem is that for my needs I need to retrieve an exact amount of data from the socket to a buffer.

1) Using static arrays

Making a static buffer like this works for me, but I can't always know the size of the data to be received.

ubyte[2] buffer;
socket.receive(buffer);

This will not work :

int size = buffer[0]; // for example ...
ubyte[size] buffer2; // crash at compilation because size is not known
socket.receive(buffer2);

So my first question is : Is it possible to have static arrays without knowing its size at compilation ?

I've searched a way to use dynamic buffers, but i've encoutered other problems.

2) Using slices to extract data from the buffer

ubyte[] buffer;
socket.receive(buffer);
ubyte[2] header = buffer[0..2];

This works, but how can I remove the extracted slice from the main buffer ? Using the remove function from std.algorithm with a tuple, this way :

buffer = remove(buffer,tuple(0,2));

doesn't works for me I don't know why, I have this error at compilation :

Error: undefined identifier tuple

What is the problem here ?

Moreover, my socket is non-blocking, because I want it to be asynchronous. Doing a remove on the main buffer will cause problems if the socket is trying to push data on it at the same time ?

Thank you for reading me, and sorry for my poor english, I'm not a native-speaker.


Solution

  • Caveat: I haven't used std.socket before, so this is untested. I have experience with sockets in C and experience with D in general, but have not yet used the two together ;)

    This is just a guess at what you want, but here goes:

    import std.socket;
    import std.bitmanip;
    
    Socket socket = new Socket(AddressFamily.INET, SocketType.STREAM);
    // ... bind, connect, etc ...
    ubyte[2] header;
    socket.receive(header);
    ushort size = std.bitmanip.bigEndianToNative!ushort(header);
    ubyte[] content = new ubyte[size];
    socket.receive(content);
    // ... do your stuff ...
    

    You'll need to know the size of the incoming data somehow, even if at runtime. In my example the size is packed in the first 2 bytes that come out of the stream. Using that information, we dynamically allocate a buffer to hold the rest. For simplicity, this example is synchronous: it assumes that receive will block until content is filled. This example also blatantly ignores return values, which is bad, but I'm being simple.

    Since you intend to do asynchronous communication, you may want to write your own receive function that calls receive as many times as necessary to fill the buffer or aborts if some condition you have defined is not met.

    In your example 1, the problem is just what you expect: there is no way to size something at compile-time (static arrays) without knowing the size at compile time.

    The answer to your question then is no, it is not possible to have static arrays without knowing their size at compile-time.

    In your example 2, "ubyte[] buffer;" is initialized to null before being passed into receive. That should probably give you an assertion crash or a segfault. Usually when a function in D asks for a buffer, it will expect the memory to be allocated already; the function's responsibility is to fill that already-allocated memory with all of the goodies that you want. The only exception might be if the argument is declared as ref, ex: "void foo(ref ubyte[] buffer)", which suggests that the function will allocate the buffer for you if you don't do it (the ref is needed to accomplish that). This is why my example does the "new ubyte[size]" evaluation before grabbing things from receive().

    To answer your question about slicing, this is the simple way to dice up an array:

    ubyte[] someArray = myArraySource();
    ubyte[] header = someArray[0 .. 2];
    someArray = someArray[2 .. $]; // Remove the "header" by slicing out everything else.
    

    To answer your last question: no, it should not cause problems. You should be fine.

    Details: For doing 2-way communication over sockets, I do not believe that your buffer manipulations will matter to the sockets themselves. You should be able to "remove" things from the main buffer without affecting your sockets, because the sockets won't persist any references to that buffer. It's your buffer because you allocated it (I hope). This wouldn't depend on the synchronous vs asynchronous nature of the socket either; it's just that the socket doesn't care about your buffer that much. Once you've received/sent your bytes, then the socket has already done its job, at least until it is called next time.

    Hope that helps!