| |
| Posted by bauss in reply to Chris Katko | PermalinkReply |
|
bauss
Posted in reply to Chris Katko
| 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.
|