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.




--
-- demo\rosetta\ChatServer.exw
-- ========================
--
-- translation of (qchat) chatServ.exw
--
-- Run this first, then ChatClient.exw, see also IRC_Gateway.exw
--
-- Note that I've only got a 32-bit windows eulibnet.dll, but it should not
-- be dificult to use a "real" libnet.dll/so, or something a little newer.
--
without js
include pGUI.e
constant dl = `Download rosetta\eulibnet\ from http://phix.x10.mx/pmwiki/pmwiki.php?n=Main.Eulibnet`
assert(get_file_type("eulibnet")=FILETYPE_DIRECTORY,dl)
include eulibnet/eulibnet.ew
Ihandle log_list, log_window, statusbar, timer

atom listconn
constant IP = "127.0.0.1",
         port = "29029",
         IPaddress = IP & ":" & port
constant MAX_MSG = 550
sequence connections = {},
         nicknames = {}

constant max_log_len = 300

procedure log_to_window(string txt)
    integer count = to_number(IupGetAttribute(log_list,"COUNT"))
    if count > max_log_len then
        for t=1 to count-max_log_len do
            IupSetAttribute(log_list,"REMOVEITEM","1")
        end for
    end if
    IupSetAttribute(log_list,"APPENDITEM",txt)
    IupSetAttribute(log_list,"TOPITEM",IupGetAttribute(log_list,"COUNT"))
    IupUpdate(log_list)
end procedure

procedure message(string msg, sequence args={})
    if length(args) then msg = sprintf(msg,args) end if
    IupSetStrAttribute(statusbar, "TITLE", msg)
    log_to_window(msg)
end procedure

procedure shutDown()
    message("Shutting down euLibnet...")
    for i = 1 to length(connections) do
        if net_closeconn(connections[i]) then crash("Error closing connection!") end if
    end for
    if net_closeconn(listconn) then crash("Error closing listconn!") end if
    if net_shutdown() then crash("Error shutting down euLibnet!") end if
end procedure

procedure sendToAll(string msg)
    -- Send msg to all clients
    for i=1 to length(connections) do
        atom ci = connections[i]
        message("Sending to connection %d (%s)",{ci,nicknames[i]})
        if net_send_rdm(ci, msg) then
            message("Error sending to connection %d",{ci})
        end if
    end for
end procedure

procedure mainWindow_onOpen()
    message("Initializing euLibnet...")
    if net_init() then crash("Error initializing euLibnet!") end if
    message("done.")
    message("Initializing driver...")
    if net_initdriver(NET_DRIVER_WSOCK_WIN) != 1 then
        crash("Error initializing WinSock driver!")
    end if
    message("done.")
    message("Opening port " & IPaddress & "...")
    listconn = net_openconn(NET_DRIVER_WSOCK_WIN, IPaddress)
    if listconn = NULL then
        crash("Couldn't open connection (server already running?)")
    end if
    message("done.")
    if net_listen(listconn) then
        crash("Error trying to listen to port")
    end if
    message("Listening on port " & IPaddress)
end procedure

function timer_cb(Ihandle /*timer*/)
    integer conn = net_poll_listen(listconn)
    if conn != NULL then
         connections = append(connections, conn)
         nicknames = append(nicknames, "")
         message("New connection open from " & net_getpeer(conn))
    end if

    -- Check for messages from clients
    for i = 1 to length(connections) do
        integer ci = connections[i]
        if net_query_rdm(ci) > 0 then
            string ni = nicknames[i]
            --Get the message
            sequence msg = net_receive_rdm(ci, MAX_MSG)
            message("received msg \"%s\" of length %d from %d aka %s",{msg[2],msg[1],ci,ni})
            if msg[1] < 0 then --Exit on error
                {} = net_ignore_rdm(ci)
                sendToAll("Server error: some data may be lost")
                exit
            end if

            msg = msg[2]
            if length(msg) > 4 and equal(msg[1..3], "/n:") then
                if find(msg[4..length(msg)], nicknames) then
                    if net_send_rdm(ci, "/nt") then
                        message("Error sending to %d",{ci})
                    end if
                else
                    string prevname = ni
                    ni = msg[4..length(msg)]
                    nicknames[i] = ni
                    if length(prevname) = 0 then
                        sendToAll("/j:" & ni & " has joined")
                        -- send fake "has joined"s to the new joiner,
                        -- so that it can build a full members list (see allj)
                        for n=1 to length(nicknames) do
                            if n!=i then
                                msg = "/j:" & nicknames[n] & " has joined"
                                if net_send_rdm(ci, msg) then
                                    message("Error sending to connection %d",{ci})
                                    exit
                                end if
                            end if
                        end for
                    else
                        sendToAll("/c:" & prevname & " has changed name to " & ni)
                    end if
                end if
            elsif equal(msg, "/d") then
                msg = "/l:"& ni & " has left"
                message(msg)
                nicknames[i..i] = {}
                connections[i..i] = {}
                sendToAll(msg)
                exit
            -- Aside: bunch of popup and file transfer stuff was here in qchat.exw,
            --        all ripped out in the name of keeping this short and sweet.
            else
                sendToAll(ni & ": " & msg) -- (Add nickname to message)
            end if
        end if
    end for
    return IUP_IGNORE
end function

function close_cb(Ihandle /*ih*/)
    shutDown()
    return IUP_DEFAULT
end function

IupOpen()
log_list = IupList("EXPAND=YES, CANFOCUS=NO, MULTIPLE=YES")
statusbar = IupLabel("Loading...","EXPAND=HORIZONTAL")
log_window = IupDialog(IupVbox({log_list,statusbar}),
            `TITLE="Chat Server", RASTERSIZE=400x400`)
IupSetCallback(log_window,"CLOSE_CB",Icallback("close_cb"))
IupShow(log_window)
mainWindow_onOpen()
timer = IupTimer(Icallback("timer_cb"), 500)
IupMainLoop()
IupClose()
