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