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.