| @@ -0,0 +1,99 @@ | |||||
| module Socket where | |||||
| import qualified Network.Socket as Net | |||||
| import qualified System.IO as Sys | |||||
| import Control.Concurrent | |||||
| import Control.Concurrent.Chan | |||||
| import Control.Monad (forever) | |||||
| import Control.Monad.Reader | |||||
| import Data.List.Split | |||||
| data Buffering = NoBuffering | |||||
| | LengthBuffering | |||||
| | DelimiterBuffering String | |||||
| deriving (Show) | |||||
| data ServerEnv = ServerEnv | |||||
| { buffering :: Buffering -- ^ Buffermode | |||||
| , socket :: Net.Socket -- ^ the socket used to communicate | |||||
| , global :: Chan String | |||||
| , onReceive :: OnReceive | |||||
| } | |||||
| type Server = ReaderT ServerEnv IO | |||||
| type OnReceive = Sys.Handle -> Net.SockAddr -> String -> Server () | |||||
| broadcast :: String -> Server () | |||||
| broadcast msg = do | |||||
| bufmode <- asks buffering | |||||
| chan <- asks global | |||||
| case bufmode of | |||||
| DelimiterBuffering delim -> liftIO $ writeChan chan $ msg ++ delim | |||||
| _ -> liftIO $ writeChan chan msg | |||||
| send :: Sys.Handle -> String -> Server () | |||||
| send connhdl msg = do | |||||
| bufmode <- asks buffering | |||||
| case bufmode of | |||||
| DelimiterBuffering delim -> liftIO $ Sys.hPutStr connhdl $ msg ++ delim | |||||
| _ -> liftIO $ Sys.hPutStr connhdl msg | |||||
| -- | Initialize a new server with the given port number and buffering mode | |||||
| initServer :: Net.PortNumber -> Buffering -> OnReceive -> IO ServerEnv | |||||
| initServer port buffermode handler = do | |||||
| sock <- Net.socket Net.AF_INET Net.Stream 0 | |||||
| Net.setSocketOption sock Net.ReuseAddr 1 | |||||
| Net.bind sock (Net.SockAddrInet port Net.iNADDR_ANY) | |||||
| Net.listen sock 5 | |||||
| chan <- newChan | |||||
| forkIO $ forever $ do | |||||
| msg <- readChan chan -- clearing the main channel | |||||
| return () | |||||
| return (ServerEnv buffermode sock chan handler) | |||||
| -- | Looping over requests and establish connection | |||||
| procRequests :: Server () | |||||
| procRequests = do | |||||
| sock <- asks socket | |||||
| (conn, clientaddr) <- liftIO $ Net.accept sock | |||||
| env <- ask | |||||
| liftIO $ forkIO $ runReaderT (procMessages conn clientaddr) env | |||||
| procRequests | |||||
| -- | Handle one client | |||||
| procMessages :: Net.Socket -> Net.SockAddr -> Server () | |||||
| procMessages conn clientaddr = do | |||||
| connhdl <- liftIO $ Net.socketToHandle conn Sys.ReadWriteMode | |||||
| liftIO $ Sys.hSetBuffering connhdl Sys.NoBuffering | |||||
| globalChan <- asks global | |||||
| commChan <- liftIO $ dupChan globalChan | |||||
| reader <- liftIO $ forkIO $ forever $ do | |||||
| msg <- readChan commChan | |||||
| Sys.hPutStrLn connhdl msg | |||||
| handler <- asks onReceive | |||||
| messages <- liftIO $ Sys.hGetContents connhdl | |||||
| buffermode <- asks buffering | |||||
| case buffermode of | |||||
| DelimiterBuffering delimiter -> | |||||
| mapM_ (handler connhdl clientaddr) (splitOn delimiter messages) | |||||
| LengthBuffering -> liftIO $ putStrLn (take 4 messages) | |||||
| _ -> return () | |||||
| -- clean up | |||||
| liftIO $ do killThread reader | |||||
| Sys.hClose connhdl | |||||
| sampleHandler :: OnReceive | |||||
| sampleHandler connhdl addr query = do | |||||
| liftIO $ putStrLn $ "new query " ++ query | |||||
| send connhdl $ "> " ++ query | |||||
| main :: IO () | |||||
| main = do | |||||
| env <- initServer 4242 LengthBuffering sampleHandler | |||||
| runReaderT procRequests env | |||||