Adds types and a basic server.
This commit is contained in:
commit
887b385879
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.cabal-sandbox/
|
||||||
|
.stack-work/
|
||||||
|
cabal.sandbox.config
|
||||||
|
dist/
|
30
LICENSE
Normal file
30
LICENSE
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
Copyright Author name here (c) 2015
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Author name here nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
10
app/Main.hs
Normal file
10
app/Main.hs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module Main where
|
||||||
|
|
||||||
|
import Link.Server
|
||||||
|
import System.Environment (getArgs)
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
port <- fmap (read . (!! 0)) getArgs
|
||||||
|
runServer port
|
||||||
|
|
46
link.cabal
Normal file
46
link.cabal
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
name: link
|
||||||
|
version: 0.1.0.0
|
||||||
|
synopsis: A simple multithreded chat server
|
||||||
|
description: Please see README.md
|
||||||
|
homepage: http://github.com/abhin4v/link#readme
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Abhinav Sarkar
|
||||||
|
maintainer: abhinav@abhinavsarkar.net
|
||||||
|
copyright: 2010 Abhinav Sarkar
|
||||||
|
category: Network
|
||||||
|
build-type: Simple
|
||||||
|
-- extra-source-files:
|
||||||
|
cabal-version: >=1.10
|
||||||
|
|
||||||
|
library
|
||||||
|
hs-source-dirs: src
|
||||||
|
exposed-modules: Link.Server,
|
||||||
|
Link.Types
|
||||||
|
default-extensions: OverloadedStrings, RecordWildCards, ScopedTypeVariables,
|
||||||
|
BangPatterns, TupleSections, NamedFieldPuns
|
||||||
|
build-depends: base >= 4.7 && < 5,
|
||||||
|
network >= 2.6 && < 2.7,
|
||||||
|
containers >= 0.5 && < 0.6
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
executable link-exe
|
||||||
|
hs-source-dirs: app
|
||||||
|
main-is: Main.hs
|
||||||
|
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||||
|
build-depends: base
|
||||||
|
, link
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
test-suite link-test
|
||||||
|
type: exitcode-stdio-1.0
|
||||||
|
hs-source-dirs: test
|
||||||
|
main-is: Spec.hs
|
||||||
|
build-depends: base
|
||||||
|
, link
|
||||||
|
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: https://github.com/abhin4v/link
|
66
src/Link/Server.hs
Normal file
66
src/Link/Server.hs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
module Link.Server where
|
||||||
|
|
||||||
|
import Control.Exception (finally)
|
||||||
|
import Control.Concurrent (forkFinally, newMVar, modifyMVar, modifyMVar_)
|
||||||
|
import Control.Monad (forever)
|
||||||
|
import Network (withSocketsDo, listenOn, accept, PortID(..))
|
||||||
|
import System.IO (hClose, hSetNewlineMode, hSetBuffering, BufferMode(..),
|
||||||
|
universalNewlineMode, hGetLine, Handle)
|
||||||
|
import Text.Printf (printf, hPrintf)
|
||||||
|
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
|
import Link.Types
|
||||||
|
|
||||||
|
runServer :: Int -> IO ()
|
||||||
|
runServer port = withSocketsDo $ do
|
||||||
|
serverUsers <- newMVar Map.empty
|
||||||
|
let server = Server serverUsers
|
||||||
|
sock <- listenOn . PortNumber . fromIntegral $ port
|
||||||
|
printf "Listening on port %d\n" port
|
||||||
|
forever $ do
|
||||||
|
(handle, host, port') <- accept sock
|
||||||
|
printf "Accepted connection from %s: %s\n" host (show port')
|
||||||
|
forkFinally (talk server handle) (\_ -> hClose handle)
|
||||||
|
|
||||||
|
talk :: Server -> Handle -> IO ()
|
||||||
|
talk server handle = do
|
||||||
|
hSetNewlineMode handle universalNewlineMode
|
||||||
|
hSetBuffering handle LineBuffering
|
||||||
|
readName
|
||||||
|
where
|
||||||
|
readName = do
|
||||||
|
name <- hGetLine handle
|
||||||
|
if null name
|
||||||
|
then readName
|
||||||
|
else do
|
||||||
|
let user = User name
|
||||||
|
ok <- checkAddClient server user handle
|
||||||
|
case ok of
|
||||||
|
Nothing -> do
|
||||||
|
hPrintf handle
|
||||||
|
"The name %s is in use, please choose another\n" name
|
||||||
|
readName
|
||||||
|
Just client ->
|
||||||
|
runClient server client
|
||||||
|
`finally` removeClient server user
|
||||||
|
|
||||||
|
checkAddClient :: Server -> User -> Handle -> IO (Maybe Client)
|
||||||
|
checkAddClient Server {..} user@User {..} handle = do
|
||||||
|
modifyMVar serverUsers $ \clientMap ->
|
||||||
|
if Map.member user clientMap
|
||||||
|
then return (clientMap, Nothing)
|
||||||
|
else do
|
||||||
|
let client = Client user handle
|
||||||
|
printf "New user connected: %s" userName
|
||||||
|
return (Map.insert user client clientMap, Just client)
|
||||||
|
|
||||||
|
runClient :: Server -> Client -> IO ()
|
||||||
|
runClient server Client {..} = forever $ do
|
||||||
|
command <- hGetLine clientHandle
|
||||||
|
print command
|
||||||
|
|
||||||
|
removeClient :: Server -> User -> IO ()
|
||||||
|
removeClient Server {..} user =
|
||||||
|
modifyMVar_ serverUsers $ \clientMap ->
|
||||||
|
return $ Map.delete user clientMap
|
17
src/Link/Types.hs
Normal file
17
src/Link/Types.hs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
module Link.Types where
|
||||||
|
|
||||||
|
import System.IO (Handle)
|
||||||
|
import Control.Concurrent (MVar)
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
|
data User = User { userName :: String }
|
||||||
|
deriving (Show, Eq, Ord)
|
||||||
|
|
||||||
|
data Client = Client {
|
||||||
|
clientUser :: User
|
||||||
|
, clientHandle :: Handle
|
||||||
|
} deriving (Show, Eq)
|
||||||
|
|
||||||
|
data Server = Server {
|
||||||
|
serverUsers :: MVar (Map.Map User Client)
|
||||||
|
}
|
5
stack.yaml
Normal file
5
stack.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
flags: {}
|
||||||
|
packages:
|
||||||
|
- '.'
|
||||||
|
extra-deps: []
|
||||||
|
resolver: lts-3.4
|
2
test/Spec.hs
Normal file
2
test/Spec.hs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
main :: IO ()
|
||||||
|
main = putStrLn "Test suite not yet implemented"
|
Loading…
Reference in New Issue
Block a user