5. chat (socketset, OOP)
import std.socket : InternetAddress, Socket, TcpSocket, SocketOptionLevel, SocketOption, Address, SocketSet; import std.stdio : writefln; import std.algorithm : remove, startsWith; import std.string : strip; import std.format : format; import std.conv : to; enum MAX_CONNECTIONS = 40; enum PORT = 4444; struct Client { Socket sock; Address addr; string name; ubyte[256] buf; this(Socket sock) @safe { this.sock = sock; addr = sock.remoteAddress; } // bool: is the client still alive? bool receive(size_t id, ChatServer server) @safe { ptrdiff_t got = sock.receive(buf[]); if (got <= 0) return false; if (name != "") { // named clients can speak server.broadcast(id, format!"<%s> %s"(name, cast(char[])(buf[0 .. got]))); } else if (buf[0 .. got].startsWith("/nick ")) { static const prefix = "/nick ".length; name = strip(cast(char[])(buf[prefix .. got])).idup; writefln!"Connection %s is now known as %s."(addr, name); server.broadcast(-1, format!"* %s has joined.\n"(name)); } else { sock.send("* Identify yourself with /nick yourname\n"); } return true; } void send(string msg) @safe { if (name != "") // named clients can hear sock.send(msg); } } class ChatServer { private: TcpSocket listener; Client[] clients; SocketSet sockets; public: this(short port) @safe { listener = new TcpSocket; sockets = new SocketSet(MAX_CONNECTIONS + 1); listener.blocking = true; listener.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, 1); listener.bind(new InternetAddress(port)); listener.listen(10); writefln!"Listening on %d."(port); } void broadcast(ptrdiff_t except, string msg) @safe { foreach (i; 0 .. clients.length) { if (i != except) clients[i].send(msg); } } void select() @safe { sockets.reset; sockets.add(listener); foreach (client; clients) sockets.add(client.sock); Socket.select(sockets, null, null); for (size_t i = 0; i < clients.length; i++) { if (!sockets.isSet(clients[i].sock)) continue; if (!clients[i].receive(i, this)) { writefln!"Lost connection from %s."(clients[i].addr); clients[i].sock.close; clients = clients.remove(i); i--; // restart loop at same i } } if (sockets.isSet(listener)) { Socket client = listener.accept; if (clients.length < MAX_CONNECTIONS) { writefln!"Received connection from %s."(client.remoteAddress); clients ~= Client(client); } else { writefln!"Rejected connection from %s; too many connections (%d)."(client.remoteAddress, clients.length); client.close; } } } } void main() @safe { scope server = new ChatServer(PORT); while (true) server.select; }
This server is very similar to brutish chat, but client structs keep track of their name and handle their own I/O, only broadcasting when named.
Server interaction
Listening on 4444. Received connection from 127.0.0.1:42376. Connection 127.0.0.1:42376 is now known as bob. Received connection from 127.0.0.1:42404. Connection 127.0.0.1:42404 is now known as alice. Lost connection from 127.0.0.1:42404. Lost connection from 127.0.0.1:42376.
Client interaction(s)
/nick bob * bob has joined. * alice has joined. hello? <alice> hi ^C
hi * Identify yourself with /nick yourname /nick alice * alice has joined. <bob> hello? hi ^C