Task

Write a server for a minimal text based chat.

People should be able to connect via ‘telnet’, sign on with a nickname, and type messages which will then be seen by all other connected users. Arrivals and departures of chat members should generate appropriate notification messages.




 
use System.IO.Net;
use System.Concurrency;
use Collection;
 
bundle Default {
  class ChatServer {
    @clients : StringMap;
    @clients_mutex : ThreadMutex;
 
    New() {
      @clients := StringMap->New();
      @clients_mutex := ThreadMutex->New("clients_mutex");
    }
 
    method : ValidLogin(login_name : String, clients : StringMap) ~ Bool {
      if(clients->Has(login_name)) {
        return false;
      };
 
      return true;
    }
 
    function : Main(args : String[]) ~ Nil {
      chat_server := ChatServer->New();
      chat_server->Run();
    }
 
    method : public : Broadcast(message : String, sender : Client) ~ Nil {
      client_array : Vector;
      critical(@clients_mutex) {
        client_array := @clients->GetValues();
      };
      each(i : client_array) {
        client := client_array->Get(i)->As(Client);
        if(client <> sender) {
          client->Send(message);
        };
      };      
    }
 
    method : public : Disconnect(sender : Client) ~ Nil {
      send_name := sender->GetName();
      Broadcast("+++ {$send_name} has left +++", sender);
      critical(@clients_mutex) {
        @clients->Remove(sender->GetName());
      };
      sender->Close();
    }
 
    method : public : Run() ~ Nil {
      server := TCPSocketServer->New(4661);
      if(server->Listen(5)) {
        while(true) {
          client_sock := server->Accept();
          critical(@clients_mutex) {
            client_sock->WriteString("login: ");
            login_name := client_sock->ReadString();
            if(ValidLogin(login_name, @clients)) {
              client := Client->New(login_name, client_sock, @self);
              @clients->Insert(client->GetName(), client);
              client->Execute(Nil);
            }
            else {
              client_sock->WriteString("+++ login in use +++\r\n");
              client_sock->Close();
            };
          };
        };
      };
      server->Close();
    }
  }
 
  class Client from Thread {
    @client_sock : TCPSocket;
    @server : ChatServer;
 
    New(login_name : String, client_sock : TCPSocket, server : ChatServer) {
      Parent(login_name);
      @client_sock := client_sock;
      @server := server;
    }
 
    method : public : Close() ~ Nil {
      @client_sock->Close();
    }
 
    method : public : Send(message : String) ~ Nil {
      if(@client_sock->IsOpen() & message->Size() > 0) {
        @client_sock->WriteString("{$message}\r\n");
      }
      else {
        @server->Disconnect(@self);
      };
    }
 
    method : public : Run(param : Base) ~ Nil {
      client_name := GetName();
      @server->Broadcast("+++ {$client_name} has arrived +++", @self);
 
      message := @client_sock->ReadString();
      while(message->Size() > 0 & message->Equals("/quit") = false) {
        @server->Broadcast("{$client_name}> {$message}", @self);      
        message := @client_sock->ReadString();
      };
      @server->Disconnect(@self);
    }
  }
}
 