/** * TCP echo server. * * When started it binds to free local port and listens * for incoming connections. * * It uses several Tango features: * * threads; * * logging; * * containers; * * networking I/O; * * Contact: * dawid.ciezarkiewicz@asn.pl */ import tango.io.Stdout; import tango.io.Buffer; import tango.core.Thread; import tango.net.ServerSocket; import tango.net.InternetAddress; import tango.net.Socket; import tango.net.SocketConduit; import tango.text.convert.Format; import tango.util.log.ConsoleAppender; import tango.util.log.Log; import tango.util.log.Logger; import tango.util.collection.LinkSeq; /** * Each connection will have one instance of this. */ class ClientHandler { private Logger logger; private SocketConduit socket; private void[] buf; private static uint GlobalId = 0; private uint id; public this(SocketConduit socket) { this.socket = socket; id = GlobalId++; logger = Log.getLogger("client.handler." ~ Formatter("{0}", id)); logger.info("creating"); socket.getSocket.blocking(false); socket.setTimeout(0); /* this should give funny effect */ buf = new byte[2]; } public ~this() { /* get new one, because members are not valid now */ auto logger = Log.getLogger("client.handler." ~ Formatter("{0}", id)); logger.info("resources disposed"); } /** * Will throw error on the end of connection. */ public void call() { /* * XXX: * SocketConduit.read() is broken for a moment * http://www.dsource.org/projects/tango/ticket/279 * because of that - all this isn't quite as * it should be, but it works well. */ auto ammount = socket.getSocket.receive(buf); if (ammount == -1) { /* nothing */ } else if ((ammount == IConduit.Eof || ammount == 0)) { logger.info("connection ended"); throw new Exception("connection ended"); } else if (ammount > 0) { logger.trace("got " ~ Formatter("{0}", ammount) ~ " bytes"); socket.write(buf[0..ammount]); } } } /** * Manages client connections. * * It's a thread looping over list of clients * calling ClientHandler.call() member * and removing closed ones. */ class ClientManager { private Thread thread; private Logger logger; LinkSeq!(ClientHandler) clients; public this() { clients = new LinkSeq!(ClientHandler); logger = Log.getLogger("client.manager"); logger.info("creating thread"); thread = new Thread(&this.clientLoop); logger.info("starting thread"); thread.start(); } private void clientLoop() { while (true) { synchronized (clients) { auto to_delete = new LinkSeq!(ClientHandler); foreach (client; clients) { try { client.call(); } catch (Exception e) { to_delete.append(client); } } foreach (client; to_delete) { clients.remove(client); } } Thread.sleep(0.1); } } public void spawn(SocketConduit sc) { synchronized (clients) { clients.append(new ClientHandler(sc)); } } } /** * Thread that loops accepting incoming connections * and pass them to ClientManager. */ class Server { private Thread thread; private Logger logger; ServerSocket socket; ClientManager clientManager; public this(ClientManager cm) in { assert(cm); } body { this.clientManager = cm; logger = Log.getLogger("server"); logger.info("creating socket"); socket = new ServerSocket(new InternetAddress("0.0.0.0", 0)); logger.info("listening on " ~ socket.getSocket().localAddress().toUtf8()); logger.info("creating thread"); thread = new Thread(&this.serverLoop); logger.info("starting thread"); thread.start(); } private void serverLoop() { while (true) { auto client_socket = socket.accept(); clientManager.spawn(client_socket); } } public void join() { thread.join(); } } int main() { Logger logger = Log.getRootLogger(); logger.setLevel(); logger.addAppender(new ConsoleAppender); auto ch = new ClientManager(); auto a = new Server(ch); a.join(); return 0; }