module chatServer // ******************************************************************************** // Clean tutorial example program. // // This program demonstrates the usage of the selectChannel function // // ******************************************************************************** import StdEnv, StdTCPChannels, StdMaybe from StdFunc import seq chatPort :== 2000 :: *ChanInfo = { sndChan :: TCP_SChannel , rcvChan :: TCP_RChannel , nickname :: String } Start world # (ok, mbListener, world) = openTCP_Listener chatPort world | not ok = abort ("chatServer: can't listen on port "+++toString chatPort) # (console, world) = stdio world console = fwrites "This server program waits until a client program " console console = fwrites "tries to connect.\n" console = loop (fromJust mbListener) [] console world loop :: !TCP_Listener ![ChanInfo] !*File !*World -> *World loop listener channels console world # (sChans, rChans, nicknames) = unzip3 channels glue = (TCP_Listeners [listener]) :^: (TCP_RChannels rChans) ([(who,what):_],glue, _, world) = selectChannel_MT Nothing glue Void world ((TCP_Listeners [listener:_]) :^: (TCP_RChannels rChans)) = glue channels = zip3 sChans rChans nicknames // case 1: someone wants to join the chatroom | who==0 # (tReport, mbNewMember, listener, world) = receive_MT (Just 0) listener world | tReport<>TR_Success // the potential new member changed it's mind = loop listener channels console world # (_,{sChannel,rChannel}) = fromJust mbNewMember (byteSeq, rChannel, world) = receive rChannel world nickname = toString byteSeq message = "*** "+++nickname+++" joined the group." console = fwrites (message+++"\n") console channel = { sndChan=sChannel, rcvChan=rChannel, nickname=nickname } channels = [channel:channels] (channels, world) = broadcastString message channels [] world | nickname % (0,3)=="quit" = quit listener channels world = loop listener channels console world // case 2: somebody has something to say | what==SR_Available # (channel=:{rcvChan, nickname}, channels) = selectList (who-1) channels (byteSeq, rcvChan, world) = receive rcvChan world message = toString byteSeq channels = channels++[{channel & rcvChan=rcvChan}] (channels, world) = broadcastString (nickname+++": "+++message) channels [] world = loop listener channels (fwrites message console) world // case 3: somebody leaves the group | what==SR_EOM # ({sndChan, rcvChan, nickname}, channels) = selectList (who-1) channels message = "*** "+++nickname+++" left the group" console = fwrites (message+++"\n") console (channels, world) = broadcastString message channels [] world world = seq [closeChannel sndChan, closeRChannel rcvChan] world = loop listener channels console world broadcastString :: !String ![ChanInfo] ![ChanInfo] !*World -> ([ChanInfo],!*World) broadcastString string [] akku world = (u_reverse akku, world) broadcastString string [channel=:{sndChan}:channels] akku world # (sndChan, world) = send (toByteSeq string) sndChan world = broadcastString string channels [{channel & sndChan=sndChan}:akku] world //selectList :: !Int [.a] -> (!.a,![.a]) selectList n l # (left, [element:right]) = splitAt n l = (element, left++right) quit listener channels world # world = closeRChannel listener world = closeChannels channels world closeChannels [] world = world closeChannels [{sndChan, rcvChan}: channels] world # world = seq [closeChannel sndChan, closeRChannel rcvChan] world = closeChannels channels world unzip3 :: ![ChanInfo] -> (![TCP_SChannel], ![TCP_RChannel], ![String]) unzip3 [] = ([],[],[]) unzip3 [{sndChan, rcvChan, nickname}:t] # (a,b,c) = unzip3 t = ([sndChan:a], [rcvChan:b], [nickname:c]) zip3 :: ![TCP_SChannel] ![TCP_RChannel] ![String] -> [ChanInfo] zip3 [] [] [] = [] zip3 [sndChan:a] [rcvChan:b] [nickname:c] = [{sndChan=sndChan, rcvChan=rcvChan, nickname=nickname} : zip3 a b c] u_reverse list = reverse_ list [] where reverse_ [hd:tl] list = reverse_ tl [hd:list] reverse_ [] list = list