{-# LANGUAGE MagicHash #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Std.IO.TCP (
ClientConfig(..)
, defaultClientConfig
, initClient
, ServerConfig(..)
, defaultServerConfig
, startServer
, module Std.IO.SockAddr
) where
import Control.Concurrent
import Control.Concurrent.MVar
import Control.Monad
import Control.Monad.IO.Class
import Data.Int
import Data.Primitive.PrimArray
import Foreign.C.Types
import Foreign.Ptr
import GHC.Ptr
import Std.Foreign.PrimArray
import Std.Data.Array
import Std.IO.Buffered
import Std.IO.Exception
import Std.IO.SockAddr
import Std.IO.Resource
import Std.IO.UV.FFI
import Std.IO.UV.Manager
import Std.Data.Vector
initTCPStream :: HasCallStack => UVManager -> Resource UVStream
initTCPStream = initUVStream (\ loop handle ->
throwUVIfMinus_ (uv_tcp_init loop handle))
initTCPExStream :: HasCallStack => CUInt -> UVManager -> Resource UVStream
initTCPExStream family = initUVStream (\ loop handle ->
throwUVIfMinus_ (uv_tcp_init_ex loop handle family))
newtype TCP = TCP UVStream deriving (Show, Input, Output)
data ClientConfig = ClientConfig
{ clientLocalAddr :: Maybe SockAddr
, clientTargetAddr :: SockAddr
, clientNoDelay :: Bool
}
defaultClientConfig :: ClientConfig
defaultClientConfig = ClientConfig Nothing (SockAddrInet 8888 inetLoopback) True
initClient :: HasCallStack => ClientConfig -> Resource TCP
initClient ClientConfig{..} = do
uvm <- liftIO getUVManager
client <- initTCPStream uvm
let handle = uvsHandle client
liftIO . withSockAddr clientTargetAddr $ \ targetPtr -> do
forM_ clientLocalAddr $ \ clientLocalAddr' ->
withSockAddr clientLocalAddr' $ \ localPtr ->
throwUVIfMinus_ (uv_tcp_bind handle localPtr 0)
when clientNoDelay $ throwUVIfMinus_ (uv_tcp_nodelay handle 1)
withUVRequest uvm $ \ _ -> hs_uv_tcp_connect handle targetPtr
return (TCP client)
data ServerConfig = ServerConfig
{ serverAddr :: SockAddr
, serverBackLog :: Int
, serverWorker :: TCP -> IO ()
, serverWorkerNoDelay :: Bool
}
defaultServerConfig :: ServerConfig
defaultServerConfig = ServerConfig
(SockAddrInet 8888 inetAny)
128
(\ uvs -> writeOutput uvs (Ptr "hello world"#) 11)
True
startServer :: HasCallStack => ServerConfig -> IO ()
startServer ServerConfig{..} = do
serverManager <- getUVManager
withResource (initTCPStream serverManager) $ \ (UVStream serverHandle serverSlot _ _) ->
bracket
(throwOOMIfNull $ hs_uv_accept_check_alloc serverHandle)
hs_uv_accept_check_close $ \ check -> do
throwUVIfMinus_ $ hs_uv_accept_check_init check
withSockAddr serverAddr $ \ addrPtr -> do
m <- getBlockMVar serverManager serverSlot
acceptBuf <- newPinnedPrimArray ACCEPT_BUFFER_SIZE
let acceptBufPtr = coerce (mutablePrimArrayContents acceptBuf :: Ptr UVFD)
withUVManager_ serverManager $ do
pokeBufferTable serverManager serverSlot acceptBufPtr 0
throwUVIfMinus_ (uv_tcp_bind serverHandle addrPtr 0)
throwUVIfMinus_ (hs_uv_listen serverHandle (max 4 (fromIntegral serverBackLog)))
forever $ do
takeMVar m
acceptBufCopy <- withUVManager_ serverManager $ do
tryTakeMVar m
accepted <- peekBufferTable serverManager serverSlot
acceptBuf' <- newPrimArray accepted
copyMutablePrimArray acceptBuf' 0 acceptBuf 0 accepted
pokeBufferTable serverManager serverSlot acceptBufPtr 0
unsafeFreezePrimArray acceptBuf'
let accepted = sizeofPrimArray acceptBufCopy
forM_ [0..accepted-1] $ \ i -> do
let fd = indexPrimArray acceptBufCopy i
if fd < 0
then throwUVIfMinus_ (return fd)
else void . forkBa $ do
uvm <- getUVManager
withResource (initUVStream (\ loop handle -> do
throwUVIfMinus_ (uv_tcp_init loop handle)
throwUVIfMinus_ (hs_uv_tcp_open handle fd)) uvm) $ \ client -> do
when serverWorkerNoDelay . throwUVIfMinus_ $
uv_tcp_nodelay (uvsHandle client) 1
serverWorker (TCP client)
when (accepted == ACCEPT_BUFFER_SIZE) $
withUVManager_ serverManager (hs_uv_listen_resume serverHandle)