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.