4. chat (socketset)

import std.socket : InternetAddress, Socket, TcpSocket, SocketOptionLevel,
    SocketOption, Address, SocketSet;
import std.stdio : writefln;
import std.typecons : Unique, Nullable;
import std.algorithm : remove;

enum PORT = 4444;
enum MAX_CONNECTIONS = 40;

void main() {
    Unique!TcpSocket listener = new TcpSocket;
    listener.blocking = true;
    listener.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, 1);
    listener.bind(new InternetAddress(PORT));
    listener.listen(10);
    writefln!"Listening on %d."(PORT);

    scope sockets = new SocketSet(MAX_CONNECTIONS + 1);
    Socket[] reads;
    Address[] addrs;
    ubyte[4096] buf;
    ptrdiff_t len;

    while (true) {
        sockets.reset;
        sockets.add(listener.handle);
        foreach (sock; reads)
            sockets.add(sock.handle);
        Socket.select(sockets, null, null);
        for (size_t i = 0; i < reads.length; i++) {
            if (!sockets.isSet(reads[i].handle))
                continue;
            if (0 < (len = reads[i].receive(buf[]))) {
                foreach (j; 0 .. reads.length)
                    if (j != i)
                        reads[j].send(buf[0 .. len]);
            } else {
                writefln!"Lost connection from %s."(addrs[i]);
                reads[i].close;
                reads = reads.remove(i);
                addrs = addrs.remove(i);
                i--; // restart loop at same i
            }
        }
        if (sockets.isSet(listener.handle)) {
            Socket client = listener.accept;
            if (reads.length < MAX_CONNECTIONS) {
                writefln!"Received connection from %s."(client.remoteAddress.toString);
                addrs ~= client.remoteAddress;
                reads ~= client;
            } else {
                writefln!"Rejected connection from %s; too many connections (%d)."(
                        client.remoteAddress.toString, reads.length);
                client.close;
            }
        }
    }
}

This server is almost exactly like echo socketset in how it functions, except that it repeats all incoming traffic back to all connected clients, resulting in a crude chat environment.


With the connection limit, are those dynamic arrays as memory-efficient as static arrays?

No, they reallocate on every append that follows a .remove. This kind of stack-like use of dynamic arrays needs to make careful use of assumeSafeAppend to avoid unnecessary reallocations.

is this server fit for purpose?

It has the same problem with short sends but the intended functionality is already so unreliable, I think it's fine. In a pinch, if you have no other possible way to speak with a group of reasonably trusted people, this would suffice. But as soon as the pinch ended you'd want to at least implement the IRC protocol, surely.