std.socket tutorials? examples?

bauss jj_1337 at live.dk
Thu Oct 4 13:07:30 UTC 2018


On Thursday, 4 October 2018 at 09:54:40 UTC, Chris Katko wrote:
> On Thursday, 4 October 2018 at 08:52:28 UTC, Andrea Fontana 
> wrote:
>> On Thursday, 4 October 2018 at 08:32:13 UTC, Chris Katko wrote:
>>> I've been Google'ing and there's like... nothing out there.
>>>
>>> One of the top results for "std.socket dlang examples"... is 
>>> for TANGO. That's how old it is.
>>
>> Socket paradigm is quite standard across languages.
>>
>> Anyway you can find a couple of example here:
>> https://github.com/dlang/dmd/blob/master/samples/listener.d
>> https://github.com/dlang/dmd/blob/master/samples/htmlget.d
>>
>> Andrea
>
> I was hoping someone would have a walk-through or something. 
> Those examples are 110 lines each!
>
> Usually, with D, there's plenty of useful 
> paradigms/templates/Phobos magic. So if I just port a C/C++/C# 
> socket example over, how am I supposed to know if I'm doing it 
> "the right/proper/best way" in D? That kind of thing.

Not exactly a tutorial or a walkthrough, but I'll try to explain 
basic usage of std.socket using an asynchronous tcp server as 
example.

Usually sockets are pretty low-level though, so not a lot of D 
magic will be present in the example.

First of all D implements classes for the two most common 
protocols UDP and TCP, both called UdpSocket and TcpSocket for 
obvious reasons.

We'll focus on TcpSocket in these examples, but using UdpSocket 
is not much different if you at the very least understand UDP and 
how it works.

The first thing you want to do for the server is creating a new 
instance of the TcpSocket class.

```
auto server = new TcpSocket;
```

Then to make it a non-blocking socket you simply set "blocking" 
to false.

This will call the low-level OS functions that creates 
non-blocking sockets on Windows it would be: ioctlsocket().

https://docs.microsoft.com/en-us/windows/desktop/api/winsock/nf-winsock-ioctlsocket

For Posix it would be fcntl() wih first "F_GETFL" and then 
"F_SETFL" where the flags are updated with "O_NONBLOCK"

http://man7.org/linux/man-pages/man2/fcntl.2.html

```
server.blocking = false;
```

The next thing we do is binding the socket.

In D you can use the "InternetAddress" class to construct a valid 
address for the socket.


```
server.bind(new InternetAddress(ip, port));
```

"ip" and "port" can be changed to the ip and port of your server.

Ex:

```
const ip = "127.0.0.1";
const port = 9988;
```

Once the socket has been bound then we can simply listen for 
connections.

It can be done with the listen function.

```
server.listen(100); // The backlog is set to 100, can be whatever 
you prefer.
```

As you can see so far there has been no real D magic, because 
it's pretty low-level.

Now to actual listen for the connections etc. it'll be a little 
more complex, because we're going to use the "SocketSet" class.

The major difference between something like implementing sockets 
in ex. C++ and D is that in D you don't have to implement your 
socket logic per platform, because phobos already creates that 
logic for you. Of course that's not taking something like boost 
into account which gives same usability in C++.

The next thing is to actually accept the sockets and since we're 
using non-blocking sockets then we don't really need to make a 
separate thread for accepting them, neither do the sockets 
actually need a thread for themselves.

First we need some collection that can hold all current sockets 
accepted (An array will be fine for now.)

```
Socket[] clients;
```

Then we'll create two instances of the "SocketSet" class.

That's because we need a socket set for the server socket and one 
for all the connected sockets.

```
auto serverSet = new SocketSet;
auto clientSet = new SocketSet;
```

A simple infinite while loop will be okay.

```
while (true)
{
     ...
}
```

Now let's dive into our loop, because this is where the "magic" 
of the socket handling actually happens.

At the beginning of the loop we'll want to reset the socket sets.

This can be done using the "reset" function.

This would most definitely be handled differently in a more 
performance critical server, but you'd also use multiple threads 
that each holds a set of sockets etc. we'll not do such things 
for the sake of simplicity.

```
serverSet.reset();
clientSet.reset();
```
First we want to add the server socket to "serverSet".

```
serverSet.add(server);
```

Next we'll loop through our client array and add each socket to 
the client set.

```
if (clients)
{
   foreach (client; clients)
   {
     clientSet.add(client);
   }
}
```

At first we want to check if there are any new sockets connected 
that we can accept.

By calling "Socket.select()" we can get different socket states 
based on a socket set.

```
auto serverResult = Socket.select(serverSet, null, null);
```

If the result from "Socket.select()" is below 1 then there are no 
new sockets to accept.

If the result is 0 then it timed out, if it's -1 then it was 
interrupted.

We want to check for that before we can call "accept()"

```
if (serverResult > 0)
{
   auto client = server.accept();

   ...
}
```

Once we have called "accept()" and received a socket then we can 
add that to our client array.


```
if (client)
{
   clients ~= client;
}
```

The next thing is to simply read data from the sockets.

Again we need to check the set's result using "Socket.select()" 
to make sure there actually are sockets that have data we can 
read from.

```
auto clientResult = Socket.select(clientSet, null, null);

if (clientSet < 1)
{
   continue;
}

```

If the result is above 0 then we have sockets that can be read 
from.

The next thing is simply to read the data they have available.

Now we want to loop through each client and check if they're in 
the set.

They will be removed from the set if they don't have data.

```
foreach (client; clients)
{
   if (!clientSet.isSet(client))
   {
     continue;
   }

   ...
}
```

You simply call the "receive()" function with a buffer and it'll 
fill the buffer with the data currently available.

If the function returns 0 then it has disconnected.

In that case you can remove it from the client array. Normally an 
associative array would be more suitable, because we can give 
each socket an identifier.

Else you can check if the function returned "Socket.ERROR" which 
generally means disconnect in one way or another, but it could be 
more specific.

If the value is above 0 then it'll be the amount of bytes that 
were available in the socket.

```
auto buffer = new ubyte[1024];

auto received = client.receive(buffer);

if (recv == 0 || recv == Socket.ERROR)
{
   continue; // Most likely disconnected ...
}

buffer = buffer[0 .. $]; // Slice the buffer to the actual size 
of the received data.
```

Important: The data received from "receive()" may not be the 
whole buffer when using non-blocking sockets; make sure that you 
have all the data before you start processing it and/or slices it.

Now the next thing to do is simply using the buffer with the data.

Disclaimer: This is not a 100% ideal way of writing socket 
applications, but it should be sufficient enough to get by for a 
start and to at least understand sockets in general.


More information about the Digitalmars-d-learn mailing list