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.




:- initialization chat_server(5000).
 
chat_server(Port) :-
      tcp_socket(Socket),
      tcp_bind(Socket, Port),
      tcp_listen(Socket, 5),
      tcp_open_socket(Socket, AcceptFd, _),
      dispatch(AcceptFd).
 
dispatch(AcceptFd) :-
        tcp_accept(AcceptFd, Socket, _),
        thread_create(process_client(Socket, _), _, [detached(true)]),
        dispatch(AcceptFd).
 
process_client(Socket, _) :-
        setup_call_cleanup(
            tcp_open_socket(Socket, Str),
            handle_connection(Str),
            close(Str)).
 
% a connection was made, get the username and add the streams so the
% client can be broadcast to.
handle_connection(Str) :-
      send_msg(Str, msg_welcome, []),
      repeat,
      send_msg(Str, msg_username, []),
      read_line_to_string(Str, Name),
      connect_user(Name, Str), !.
 
% connections are stored here
:- dynamic(connected/2).
 
connect_user(Name, Str) :-
      connected(Name, _),
      send_msg(Str, msg_username_taken, []),
      fail.
connect_user(Name, Str) :-
      \+ connected(Name, _),
      send_msg(Str, msg_welcome_name, Name),
 
      % make sure that the connection is removed when the client leaves.
      setup_call_cleanup(
          assert(connected(Name, Str)),
          (
              broadcast(Name, msg_joined, Name),
              chat_loop(Name, Str), !,
              broadcast(Name, msg_left, Name)
          ),
          retractall(connected(Name, _))
      ).
 
% wait for a line to be sent then broadcast to the rest of the clients
% finish this goal when the client disconnects (end of stream)
chat_loop(Name, Str) :-
      read_line_to_string(Str, S),
      dif(S, end_of_file),
      broadcast(Name, msg_by_user, [Name, S]),
      chat_loop(Name, Str).
chat_loop(_, Str) :- at_end_of_stream(Str).
 
% send a message to all connected clients except Name (the sender)
broadcast(Name, Msg, Params) :-
    forall(
        (connected(N, Str), dif(N, Name)),
        (send_msg(Str, Msg, Params), send_msg(Str, msg_new_line, []))
    ).
 
send_msg(St, MsgConst, Params) :-
      call(MsgConst, Msg),
      format(St, Msg, Params),
      flush_output(St).
 
% constants for the various message types that are sent
msg_welcome('Welcome to Chatalot\n\r').
msg_username('Please enter your nickname: ').
msg_welcome_name('Welcome ~p\n\r').
msg_joined(' -- "~w" has joined the chat --').
msg_left(' -- "~w" has left the chat. --').
msg_username_taken('That username is already taken, choose another\n\r').
msg_new_line('\n\r').
msg_by_user('~w> ~w').