{-# LANGUAGE CPP, BangPatterns #-} module Main where import Control.Monad import Data.Int import Network.Socket ( AddrInfoFlag (AI_PASSIVE), HostName, ServiceName, Socket , SocketType (Stream), SocketOption (ReuseAddr) , accept, addrAddress, addrFlags, addrFamily, bindSocket, defaultProtocol , defaultHints , getAddrInfo, listen, setSocketOption, socket, sClose, withSocketsDo ) import System.Environment (getArgs, withArgs) import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime) import System.IO (withFile, IOMode(..), hPutStrLn, Handle, stderr) import Control.Concurrent (forkIO) import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar) import qualified Network.Socket as N import Debug.Trace import Data.ByteString (ByteString) import Data.ByteString.Char8 (pack, unpack) import qualified Data.ByteString as BS import qualified Network.Socket.ByteString as NBS import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime) import Data.ByteString.Internal as BSI import Foreign.Storable (pokeByteOff, peekByteOff) import Foreign.C (CInt(..)) import Foreign.ForeignPtr (withForeignPtr) foreign import ccall unsafe "htonl" htonl :: CInt -> CInt foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt main :: IO () main = do [pingsStr] <- getArgs serverReady <- newEmptyMVar clientDone <- newEmptyMVar -- Start the server forkIO $ do putStrLn "server: creating TCP connection" serverAddrs <- getAddrInfo (Just (defaultHints { addrFlags = [AI_PASSIVE] } )) Nothing (Just "8080") let serverAddr = head serverAddrs sock <- socket (addrFamily serverAddr) Stream defaultProtocol setSocketOption sock ReuseAddr 1 bindSocket sock (addrAddress serverAddr) putStrLn "server: awaiting client connection" putMVar serverReady () listen sock 1 (clientSock, clientAddr) <- accept sock putStrLn "server: listening for pings" pong clientSock -- Start the client forkIO $ do takeMVar serverReady let pings = read pingsStr serverAddrs <- getAddrInfo Nothing (Just "127.0.0.1") (Just "8080") let serverAddr = head serverAddrs sock <- socket (addrFamily serverAddr) Stream defaultProtocol N.connect sock (addrAddress serverAddr) ping sock pings putMVar clientDone () -- Wait for the client to finish takeMVar clientDone pingMessage :: ByteString pingMessage = pack "ping123" ping :: Socket -> Int -> IO () ping sock pings = go pings where go :: Int -> IO () go 0 = do putStrLn $ "client did " ++ show pings ++ " pings" go !i = do before <- getCurrentTime send sock pingMessage bs <- recv sock 8 after <- getCurrentTime -- putStrLn $ "client received " ++ unpack bs let latency = (1e6 :: Double) * realToFrac (diffUTCTime after before) hPutStrLn stderr $ show i ++ " " ++ show latency go (i - 1) pong :: Socket -> IO () pong sock = do bs <- recv sock 8 -- putStrLn $ "server received " ++ unpack bs when (BS.length bs > 0) $ do send sock bs pong sock -- | Wrapper around NBS.recv (for profiling) recv :: Socket -> Int -> IO ByteString recv sock i = do (header, payload) <- BS.splitAt 4 `fmap` NBS.recv sock (4 + i) -- Ignore header return payload -- | Cached header header :: ByteString header = pack "fake" -- | Wrapper around NBS.send (for profiling) send :: Socket -> ByteString -> IO () send sock bs = do NBS.sendMany sock [header, bs] -- | Encode length (manual for now) encodeLength :: Int32 -> IO ByteString encodeLength i32 = BSI.create 4 $ \p -> pokeByteOff p 0 (htonl (fromIntegral i32)) -- | Decode length (manual for now) decodeLength :: ByteString -> IO Int32 decodeLength bs = let (fp, _, _) = BSI.toForeignPtr bs in withForeignPtr fp $ \p -> do w32 <- peekByteOff p 0 return (fromIntegral (ntohl w32))