git.iko.soy/distributed-process
on master help sign in
Basic implementation of the TCP transport

Still buggy, in need of optimization, and does not yet deal with connection errors

commit321727388816001a906ff7f074f1ae875a14f40f authorEdsko de Vries <edsko@edsko.net> dateMon 23 Apr 2012, 10:28 (15 years ago) parent53e0889
7 files changed+396−46
network-transport-2/network-transport.cabal+27−7 network-transport-2/src/Network/Transport.hs+49−17 network-transport-2/src/Network/Transport/Chan.hs+16−13 network-transport-2/src/Network/Transport/Internal.hs+55 network-transport-2/src/Network/Transport/TCP.hs+210 network-transport-2/tests/TestTCP.hs+10 network-transport-2/tests/TestTransport.hs+29−9
network-transport-2/network-transport.cabal+27−7
@@ -11,21 +11,41 @@ Library
1111 Build-Depends: base,
1212 bytestring,
1313 containers,
14− data-lens
14+ data-lens,
15+ network,
16+ regex-applicative,
17+ mtl
1518 Exposed-modules: Network.Transport,
1619 Network.Transport.Chan
17− extensions: EmptyDataDecls
20+ extensions: EmptyDataDecls, ForeignFunctionInterface
1821 ghc-options: -Wall -fno-warn-unused-do-bind
1922 HS-Source-Dirs: src
2023
24+Test-Suite TestTCP
25+ Type: exitcode-stdio-1.0
26+ Main-Is: TestTCP.hs
27+ Build-Depends: base,
28+ containers,
29+ bytestring,
30+ data-lens,
31+ network,
32+ regex-applicative,
33+ mtl
34+ extensions: EmptyDataDecls, OverloadedStrings, ForeignFunctionInterface
35+ ghc-options: -Wall -fno-warn-unused-do-bind
36+ HS-Source-Dirs: tests src
37+
2138Test-Suite TestMulticastInMemory
2239 Type: exitcode-stdio-1.0
2340 Main-Is: TestMulticastInMemory.hs
2441 Build-Depends: base,
2542 containers,
2643 bytestring,
27− data-lens
28− extensions: EmptyDataDecls OverloadedStrings
44+ data-lens,
45+ network,
46+ regex-applicative,
47+ mtl
48+ extensions: EmptyDataDecls, OverloadedStrings
2949 ghc-options: -Wall -fno-warn-unused-do-bind
3050 HS-Source-Dirs: tests src
3151
@@ -35,8 +55,8 @@ Test-Suite TestInMemory
3555 Build-Depends: base,
3656 containers,
3757 bytestring,
38− data-lens
39− extensions: EmptyDataDecls OverloadedStrings
58+ data-lens,
59+ mtl
60+ extensions: EmptyDataDecls, OverloadedStrings
4061 ghc-options: -Wall -fno-warn-unused-do-bind
4162 HS-Source-Dirs: tests src
42−
network-transport-2/src/Network/Transport.hs+49−17
@@ -1,43 +1,75 @@
11-- | Network Transport
2−module Network.Transport where
2+module Network.Transport ( -- * Types
3+ Transport(..)
4+ , EndPoint(..)
5+ , Connection(..)
6+ , Event(..)
7+ , ConnectionId
8+ , Reliability(..)
9+ , MulticastGroup(..)
10+ , EndPointAddress(..)
11+ , MulticastAddress(..)
12+ -- * Utility functions
13+ , spawn
14+ -- * Error codes
15+ , FailedWith(..)
16+ , NewEndPointErrorCode
17+ , ConnectErrorCode(..)
18+ , NewMulticastGroupErrorCode
19+ , ResolveMulticastGroupErrorCode(..)
20+ ) where
321
422import Data.ByteString (ByteString)
523import Control.Concurrent (forkIO)
624import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
25+import Control.Monad.Error (Error(..))
726
827-- | To create a network abstraction layer, use one of the
928-- @Network.Transport.*@ packages.
1029data Transport = Transport {
1130 -- | Create a new end point (heavyweight operation)
12− newEndPoint :: IO (Either Error EndPoint)
31+ newEndPoint :: IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
1332 }
1433
15−-- | Address of an endpoint.
16−newtype Address = Address ByteString
34+-- | EndPointAddress of an endpoint.
35+newtype EndPointAddress = EndPointAddress ByteString
1736 deriving (Show, Eq, Ord)
1837
19−-- | Address of a multicast group.
38+-- | EndPointAddress of a multicast group.
2039newtype MulticastAddress = MulticastAddress ByteString
2140 deriving (Show, Eq, Ord)
2241
23−-- | Error codes (none defined at the moment).
24−data ErrorCode
42+-- | Errors during the creation of an endpoint (currently, there are none)
43+data NewEndPointErrorCode
2544
26−-- | Error consisting of an error code and a human readable error string.
27−data Error = Error ErrorCode String
45+-- | Connection failure
46+data ConnectErrorCode =
47+ ConnectInvalidAddress -- ^ Could not parse the address
48+
49+-- | Failure during the creation of a new multicast group
50+data NewMulticastGroupErrorCode
51+
52+-- | Failure during the resolution of a multicast group
53+data ResolveMulticastGroupErrorCode =
54+ MulticastGroupNotFound
55+
56+data FailedWith error = FailedWith error String
57+
58+instance Error (FailedWith error) where
59+ strMsg = FailedWith undefined
2860
2961-- | Network endpoint.
3062data EndPoint = EndPoint {
3163 -- | Endpoints have a single shared receive queue.
3264 receive :: IO Event
33− -- | Address of the endpoint.
34− , address :: Address
65+ -- | EndPointAddress of the endpoint.
66+ , address :: EndPointAddress
3567 -- | Create a new lightweight connection.
36− , connect :: Address -> Reliability -> IO (Either Error Connection)
68+ , connect :: EndPointAddress -> Reliability -> IO (Either (FailedWith ConnectErrorCode) Connection)
3769 -- | Create a new multicast group.
38− , newMulticastGroup :: IO (Either Error MulticastGroup)
70+ , newMulticastGroup :: IO (Either (FailedWith NewMulticastGroupErrorCode) MulticastGroup)
3971 -- | Resolve an address to a multicast group.
40− , resolveMulticastGroup :: MulticastAddress -> IO (Either Error MulticastGroup)
72+ , resolveMulticastGroup :: MulticastAddress -> IO (Either (FailedWith ResolveMulticastGroupErrorCode) MulticastGroup)
4173 }
4274
4375-- | Reliability guarantees of a connection.
@@ -62,13 +94,13 @@ data Connection = Connection {
6294data Event =
6395 Received ConnectionId [ByteString]
6496 | ConnectionClosed ConnectionId
65− | ConnectionOpened ConnectionId Reliability Address
97+ | ConnectionOpened ConnectionId Reliability EndPointAddress
6698 | ReceivedMulticast MulticastAddress [ByteString]
6799 deriving Show
68100
69101-- | Multicast group.
70102data MulticastGroup = MulticastGroup {
71− -- | Address of the multicast group.
103+ -- | EndPointAddress of the multicast group.
72104 multicastAddress :: MulticastAddress
73105 -- | Delete the multicast group completely.
74106 , deleteMulticastGroup :: IO ()
@@ -87,7 +119,7 @@ data MulticastGroup = MulticastGroup {
87119-- | Fork a new thread, create a new end point on that thread, and run the specified IO operation on that thread.
88120--
89121-- Returns the address of the new end point.
90−spawn :: Transport -> (EndPoint -> IO ()) -> IO Address
122+spawn :: Transport -> (EndPoint -> IO ()) -> IO EndPointAddress
91123spawn transport proc = do
92124 addr <- newEmptyMVar
93125 forkIO $ do
network-transport-2/src/Network/Transport/Chan.hs+16−13
@@ -13,15 +13,15 @@ import qualified Data.ByteString.Char8 as BSC (pack)
1313import Data.Lens.Lazy (Lens, lens, (^.), (^%=), (^=), (^+=), mapLens)
1414
1515-- Global state: next available "address", mapping from addresses to channels and next available connection
16−data TransportState = State { _channels :: Map Address (Chan Event)
17− , _connections :: Map Address Int
16+data TransportState = State { _channels :: Map EndPointAddress (Chan Event)
17+ , _connections :: Map EndPointAddress Int
1818 , _multigroups :: Map MulticastAddress (MVar [Chan Event])
1919 }
2020
21−channels :: Lens TransportState (Map Address (Chan Event))
21+channels :: Lens TransportState (Map EndPointAddress (Chan Event))
2222channels = lens _channels (\ch st -> st { _channels = ch })
2323
24−connections :: Lens TransportState (Map Address Int)
24+connections :: Lens TransportState (Map EndPointAddress Int)
2525connections = lens _connections (\conn st -> st { _connections = conn })
2626
2727multigroups :: Lens TransportState (Map MulticastAddress (MVar [Chan Event]))
@@ -30,10 +30,10 @@ multigroups = lens _multigroups (\gs st -> st { _multigroups = gs })
3030at :: Ord k => k -> Lens (Map k v) v
3131at k = lens (Map.! k) (Map.insert k)
3232
33−channelAt :: Address -> Lens TransportState (Chan Event)
33+channelAt :: EndPointAddress -> Lens TransportState (Chan Event)
3434channelAt addr = channels >>> at addr
3535
36−connectionAt :: Address -> Lens TransportState Int
36+connectionAt :: EndPointAddress -> Lens TransportState Int
3737connectionAt addr = connections >>> at addr
3838
3939multigroupAt :: MulticastAddress -> Lens TransportState (MVar [Chan Event])
@@ -52,11 +52,11 @@ createTransport = do
5252 return Transport { newEndPoint = chanNewEndPoint state }
5353
5454-- Create a new end point
55−chanNewEndPoint :: MVar TransportState -> IO (Either Error EndPoint)
55+chanNewEndPoint :: MVar TransportState -> IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
5656chanNewEndPoint state = do
5757 chan <- newChan
5858 addr <- modifyMVar state $ \st -> do
59− let addr = Address . BSC.pack . show . Map.size $ st ^. channels
59+ let addr = EndPointAddress . BSC.pack . show . Map.size $ st ^. channels
6060 return ((channelAt addr ^= chan) . (connectionAt addr ^= 0) $ st, addr)
6161 return . Right $ EndPoint { receive = readChan chan
6262 , address = addr
@@ -66,7 +66,7 @@ chanNewEndPoint state = do
6666 }
6767
6868-- Create a new connection
69−chanConnect :: Address -> MVar TransportState -> Address -> Reliability -> IO (Either Error Connection)
69+chanConnect :: EndPointAddress -> MVar TransportState -> EndPointAddress -> Reliability -> IO (Either (FailedWith ConnectErrorCode) Connection)
7070chanConnect myAddress state theirAddress _ = do
7171 (chan, conn) <- modifyMVar state $ \st -> do
7272 let chan = st ^. channelAt theirAddress
@@ -75,10 +75,10 @@ chanConnect myAddress state theirAddress _ = do
7575 writeChan chan $ ConnectionOpened conn ReliableOrdered myAddress
7676 return . Right $ Connection { send = writeChan chan . Received conn
7777 , close = writeChan chan $ ConnectionClosed conn
78− }
78+ }
7979
8080-- Create a new multicast group
81−chanNewMulticastGroup :: MVar TransportState -> Chan Event -> IO (Either Error MulticastGroup)
81+chanNewMulticastGroup :: MVar TransportState -> Chan Event -> IO (Either (FailedWith NewMulticastGroupErrorCode) MulticastGroup)
8282chanNewMulticastGroup state endpoint = do
8383 group <- newMVar []
8484 addr <- modifyMVar state $ \st -> do
@@ -110,9 +110,12 @@ chanMulticastGroup state endpoint addr group =
110110 }
111111
112112-- Resolve a multicast group
113−chanResolveMulticastGroup :: MVar TransportState -> Chan Event -> MulticastAddress -> IO (Either Error MulticastGroup)
113+chanResolveMulticastGroup :: MVar TransportState
114+ -> Chan Event
115+ -> MulticastAddress
116+ -> IO (Either (FailedWith ResolveMulticastGroupErrorCode) MulticastGroup)
114117chanResolveMulticastGroup state endpoint addr = do
115118 group <- (^. (multigroups >>> mapLens addr)) <$> readMVar state
116119 case group of
117− Nothing -> return . Left $ Error undefined ("Group " ++ show addr ++ " not found")
120+ Nothing -> return . Left $ FailedWith MulticastGroupNotFound ("Group " ++ show addr ++ " not found")
118121 Just mvar -> return . Right $ chanMulticastGroup state endpoint addr mvar
network-transport-2/src/Network/Transport/Internal.hsnew file+55
@@ -0,0 +1,55 @@
1+-- | Internal functions
2+module Network.Transport.Internal ( -- * Encoders/decoders
3+ encodeInt32
4+ , decodeInt32
5+ , encodeInt16
6+ , decodeInt16
7+ -- * Miscellaneous abstractions
8+ , maybeToErrorT
9+ ) where
10+
11+import Data.Int (Int16, Int32)
12+import Foreign.Storable (pokeByteOff, peekByteOff)
13+import Foreign.C (CInt(..), CShort(..))
14+import Foreign.ForeignPtr (withForeignPtr)
15+import Data.ByteString (ByteString)
16+import qualified Data.ByteString.Internal as BSI (create, toForeignPtr)
17+import Control.Monad.Error (ErrorT, Error, throwError)
18+
19+foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
20+foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt
21+foreign import ccall unsafe "htons" htons :: CShort -> CShort
22+foreign import ccall unsafe "ntohs" ntohs :: CShort -> CShort
23+
24+-- | Serialize 32-bit to network byte order
25+encodeInt32 :: Int32 -> IO ByteString
26+encodeInt32 i32 =
27+ BSI.create 4 $ \p ->
28+ pokeByteOff p 0 (htonl (fromIntegral i32))
29+
30+-- | Deserialize 32-bit from network byte order
31+decodeInt32 :: ByteString -> IO Int32
32+decodeInt32 bs =
33+ let (fp, _, _) = BSI.toForeignPtr bs in
34+ withForeignPtr fp $ \p -> do
35+ w32 <- peekByteOff p 0
36+ return (fromIntegral (ntohl w32))
37+
38+-- | Serialize 16-bit to network byte order
39+encodeInt16 :: Int16 -> IO ByteString
40+encodeInt16 i16 =
41+ BSI.create 2 $ \p ->
42+ pokeByteOff p 0 (htons (fromIntegral i16))
43+
44+-- | Deserialize 16-bit from network byte order
45+decodeInt16 :: ByteString -> IO Int16
46+decodeInt16 bs =
47+ let (fp, _, _) = BSI.toForeignPtr bs in
48+ withForeignPtr fp $ \p -> do
49+ w16 <- peekByteOff p 0
50+ return (fromIntegral (ntohs w16))
51+
52+-- | Convert maybe to an ErrorT value
53+maybeToErrorT :: (Monad m, Error a) => a -> Maybe b -> ErrorT a m b
54+maybeToErrorT err Nothing = throwError err
55+maybeToErrorT _ (Just x) = return x
network-transport-2/src/Network/Transport/TCP.hsnew file+210
@@ -0,0 +1,210 @@
1+-- | TCP transport
2+module Network.Transport.TCP where
3+
4+import Network.Transport
5+import Network.Transport.Internal ( encodeInt16
6+ , decodeInt16
7+ , encodeInt32
8+ , decodeInt32
9+ , maybeToErrorT
10+ )
11+import qualified Network.Socket as N ( HostName
12+ , ServiceName
13+ , Socket
14+ , SocketType(Stream)
15+ , SocketOption(ReuseAddr)
16+ , getAddrInfo
17+ , defaultHints
18+ , socket
19+ , bindSocket
20+ , listen
21+ , addrFamily
22+ , addrAddress
23+ , defaultProtocol
24+ , setSocketOption
25+ , connect
26+ , sClose
27+ , accept
28+ )
29+import qualified Network.Socket.ByteString as NSB (sendMany, recv)
30+import Control.Concurrent (forkIO, ThreadId)
31+import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
32+import Control.Concurrent.MVar (MVar, newMVar, modifyMVar, readMVar)
33+import Control.Category ((>>>))
34+import Control.Applicative ((<*>), (*>), (<$>), pure)
35+import Control.Monad (forever)
36+import Control.Monad.Error (ErrorT, liftIO, runErrorT)
37+import Data.ByteString (ByteString)
38+import qualified Data.ByteString as BS (length, concat)
39+import qualified Data.ByteString.Char8 as BSC (pack, unpack)
40+import Data.IntMap (IntMap)
41+import qualified Data.IntMap as IntMap (empty, size)
42+import Data.Lens.Lazy (Lens, lens, intMapLens, (^.), (^=), (^+=))
43+import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
44+
45+data TransportState = TransportState { _endPoints :: IntMap (Chan Event, MVar EndPointState) }
46+data EndPointState = EndPointState { _nextConnectionIx :: ConnectionIx }
47+type EndPointIx = Int
48+type ConnectionIx = Int
49+
50+endPoints :: Lens TransportState (IntMap (Chan Event, MVar EndPointState))
51+endPoints = lens _endPoints (\es st -> st { _endPoints = es })
52+
53+nextConnectionIx :: Lens EndPointState ConnectionIx
54+nextConnectionIx = lens _nextConnectionIx (\cix st -> st { _nextConnectionIx = cix })
55+
56+endPointAt :: Int -> Lens TransportState (Maybe (Chan Event, MVar EndPointState))
57+endPointAt ix = endPoints >>> intMapLens ix
58+
59+-- | Create a TCP transport
60+--
61+-- Hostname and service name should be "canonical" for the transport layer to
62+-- use TCP channels bidirectionally (otherwise when A connects to B and B
63+-- connects to A two socket pairs will be created that will both be used
64+-- unidirectionally, at degraded performance).
65+--
66+-- TODOs:
67+-- * Perhaps we should allow to use something different other than 'defaultHints'
68+-- * Deal with all exceptions that may occur
69+createTransport :: N.HostName -> N.ServiceName -> IO Transport
70+createTransport host port = do
71+ state <- newMVar $ TransportState { _endPoints = IntMap.empty }
72+ forkServer host port (transportServer state)
73+ return Transport { newEndPoint = tcpNewEndPoint state host port }
74+
75+-- | Start a server at the specified address
76+forkServer :: N.HostName -> N.ServiceName -> (N.Socket -> IO ()) -> IO ThreadId
77+forkServer host port server = do
78+ -- Resolve the specified address. By specification, getAddrInfo will never
79+ -- return an empty list (but will throw an exception instead) and will return
80+ -- the "best" address first, whatever that means
81+ addr:_ <- N.getAddrInfo (Just N.defaultHints) (Just host) (Just port)
82+ sock <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
83+ N.setSocketOption sock N.ReuseAddr 1
84+ N.bindSocket sock (N.addrAddress addr)
85+ N.listen sock 5
86+ forkIO $ server sock
87+
88+-- | The transport server handles incoming connections for all endpoints
89+--
90+-- TODO: lots of error checking
91+transportServer :: MVar TransportState -> N.Socket -> IO ()
92+transportServer state sock = forever $ do
93+ (clientSock, _) <- N.accept sock
94+ forkIO $ do
95+ -- Our endpoint the other side is interested in
96+ endPointBS <- NSB.recv clientSock 2
97+ endPointIx <- decodeInt16 endPointBS
98+ -- Full endpoint address of the other side
99+ theirAddress <- BS.concat <$> recvWithLength clientSock
100+ -- Start handling incoming messages
101+ endPointMb <- (^. endPointAt (fromIntegral endPointIx)) <$> readMVar state
102+ case endPointMb of
103+ Just (endPointCh, endPointSt) -> do
104+ endpointServer clientSock (EndPointAddress theirAddress) endPointCh endPointSt
105+ Nothing -> -- TODO: should we do more than just close the socket?
106+ N.sClose clientSock
107+
108+-- | Handle requests for one endpoint
109+endpointServer :: N.Socket -> EndPointAddress -> Chan Event -> MVar EndPointState -> IO ()
110+endpointServer sock theirAddress chan state = forever $ do
111+ -- Get connection number
112+ connBS <- NSB.recv sock 2
113+ connIx <- fromIntegral <$> decodeInt16 connBS
114+ if connIx == 0
115+ then createNewConnection
116+ else readMessage connIx
117+ where
118+ -- Create a new connection
119+ createNewConnection :: IO ()
120+ createNewConnection = do
121+ newIx <- modifyMVar state $ \st -> return (nextConnectionIx ^+= 1 $ st, st ^. nextConnectionIx)
122+ newBs <- encodeInt16 (fromIntegral newIx)
123+ NSB.sendMany sock [newBs]
124+ writeChan chan (ConnectionOpened newIx ReliableOrdered theirAddress)
125+
126+ -- Read a message and output it on the endPoint's channel
127+ readMessage :: Int -> IO ()
128+ readMessage connIx = do
129+ payload <- recvWithLength sock
130+ writeChan chan (Received connIx payload)
131+
132+-- | Create a new endpoint
133+tcpNewEndPoint :: MVar TransportState -> N.HostName -> N.ServiceName -> IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
134+tcpNewEndPoint transportState host port = do
135+ chan <- newChan
136+ endPointState <- newMVar $ EndPointState { _nextConnectionIx = 1 } -- Connection id 0 is reserved for requesting new connections
137+ endPointIx <- modifyMVar transportState $ \st -> do let ix = IntMap.size (st ^. endPoints)
138+ return ((endPointAt ix ^= Just (chan, endPointState)) $ st, ix)
139+ let addr = EndPointAddress . BSC.pack $ host ++ ":" ++ port ++ ":" ++ show endPointIx
140+ return . Right $ EndPoint { receive = readChan chan
141+ , address = addr
142+ , connect = tcpConnect addr
143+ , newMulticastGroup = undefined
144+ , resolveMulticastGroup = undefined
145+ }
146+
147+-- | Connnect to an endpoint
148+tcpConnect :: EndPointAddress -> EndPointAddress -> Reliability -> IO (Either (FailedWith ConnectErrorCode) Connection)
149+tcpConnect myAddress theirAddress _ = runErrorT $ do
150+ sock <- socketForEndPoint myAddress theirAddress
151+ connIx <- liftIO $ requestNewConnection sock
152+ return $ Connection { send = sendWithLength sock (Just connIx)
153+ , close = N.sClose sock
154+ }
155+
156+-- | Request a new connection
157+requestNewConnection :: N.Socket -> IO ByteString
158+requestNewConnection sock = do
159+ pure <$> encodeInt16 0 >>= NSB.sendMany sock
160+ NSB.recv sock 2
161+
162+-- | Find a socket to the specified endpoint
163+--
164+-- TODOs:
165+-- * Reuse connections
166+-- * Hints?
167+socketForEndPoint :: EndPointAddress -- ^ Our address
168+ -> EndPointAddress -- ^ Their address
169+ -> ErrorT (FailedWith ConnectErrorCode) IO N.Socket
170+socketForEndPoint (EndPointAddress myAddress) (EndPointAddress theirAddress) = do
171+ (host, port, endPointIx) <- maybeToErrorT invalidAddress (decodeEndPointAddress theirAddress)
172+ liftIO $ do
173+ -- Connect to the destination transport
174+ addr:_ <- N.getAddrInfo Nothing (Just host) (Just port)
175+ sock <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
176+ N.setSocketOption sock N.ReuseAddr 1
177+ N.connect sock (N.addrAddress addr)
178+ -- Tell it what endpoint we're interested in and our own address
179+ endPointBS <- encodeInt16 (fromIntegral endPointIx)
180+ sendWithLength sock (Just endPointBS) [myAddress]
181+ -- Connection is now established
182+ return sock
183+ where
184+ invalidAddress = FailedWith ConnectInvalidAddress "Invalid address"
185+
186+-- | Send a bunch of bytestrings prepended with their length
187+sendWithLength :: N.Socket -- ^ Socket to send on
188+ -> Maybe ByteString -- ^ Optional header to send before the length
189+ -> [ByteString] -- ^ Payload
190+ -> IO ()
191+sendWithLength sock header payload = do
192+ lengthBs <- encodeInt32 (fromIntegral . sum . map BS.length $ payload)
193+ let msg = maybe id (:) header $ lengthBs : payload
194+ NSB.sendMany sock msg
195+
196+-- | Read a length and then a payload of that length
197+-- TODO: error handling
198+recvWithLength :: N.Socket -> IO [ByteString]
199+recvWithLength sock = do
200+ msgLengthBs <- NSB.recv sock 4
201+ msgLength <- fromIntegral <$> decodeInt32 msgLengthBs
202+ pure <$> NSB.recv sock msgLength
203+
204+-- | Decode end point address
205+-- TODO: This uses regular expression parsing, which is nice, but unnecessary
206+decodeEndPointAddress :: ByteString -> Maybe (N.HostName, N.ServiceName, Int)
207+decodeEndPointAddress bs = match endPointAddressRE (BSC.unpack bs)
208+ where
209+ endPointAddressRE :: RE Char (N.HostName, N.ServiceName, Int)
210+ endPointAddressRE = (,,) <$> few anySym <*> (sym ':' *> few anySym) <*> (sym ':' *> (read <$> many anySym))
network-transport-2/tests/TestTCP.hsnew file+10
@@ -0,0 +1,10 @@
1+module Main where
2+
3+import TestTransport
4+import Network.Transport.TCP
5+import System.Exit (exitFailure, exitSuccess)
6+
7+main :: IO ()
8+main = do
9+ success <- createTransport "127.0.0.1" "8080" >>= testTransport
10+ if success then exitSuccess else exitFailure
network-transport-2/tests/TestTransport.hs+29−9
@@ -1,6 +1,6 @@
11module TestTransport where
22
3−import Control.Concurrent (forkIO)
3+import Control.Concurrent (forkIO, myThreadId)
44import Control.Monad (liftM2, replicateM, replicateM_, when)
55import Control.Applicative ((<$>))
66import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
@@ -12,13 +12,22 @@ import qualified Data.Map as Map (empty, insert, (!), delete)
1212
1313type Test = IO Bool
1414
15+-- Logging (for debugging)
16+tlog :: String -> IO ()
17+tlog msg = do
18+ tid <- myThreadId
19+ putStrLn $ show tid ++ ": " ++ msg
20+
1521-- Server that echoes messages straight back to the origin endpoint.
1622echoServer :: EndPoint -> IO ()
17−echoServer endpoint = go Map.empty
23+echoServer endpoint = do
24+ tlog "Echo server"
25+ go Map.empty
1826 where
1927 go :: Map ConnectionId Connection -> IO ()
2028 go cs = do
2129 event <- receive endpoint
30+ tlog (show event)
2231 case event of
2332 ConnectionOpened cid rel addr -> do
2433 Right conn <- connect endpoint addr rel
@@ -33,31 +42,41 @@ echoServer endpoint = go Map.empty
3342 -- Ignore
3443 go cs
3544
36−ping :: EndPoint -> Address -> Int -> ByteString -> IO ()
45+ping :: EndPoint -> EndPointAddress -> Int -> ByteString -> IO ()
3746ping endpoint server numPings msg = do
3847 -- Open connection to the server
48+ tlog "Open connection"
3949 Right conn <- connect endpoint server ReliableOrdered
4050
4151 -- Wait for the server to open reply connection
52+ tlog "Wait for ConnectionOpened message"
4253 ConnectionOpened _ _ _ <- receive endpoint
4354
4455 -- Send pings and wait for reply
56+ tlog "Send ping and wait for reply"
4557 replicateM_ numPings $ do
4658 send conn [msg]
47− Received _ [reply] <- receive endpoint
48− when (reply /= msg) . error $ "Message mismatch: " ++ show msg ++ " /= " ++ show reply
59+ event <- receive endpoint
60+ case event of
61+ Received _ [reply] | reply == msg ->
62+ return ()
63+ _ -> do
64+ error $ "Unexpected event " ++ show event
4965
5066 -- Close the connection
67+ tlog "Close the connection"
5168 close conn
5269
5370-- Basic ping test
5471testPingPong :: Transport -> Int -> Test
5572testPingPong transport numPings = do
73+ tlog "Starting ping pong test"
5674 server <- spawn transport echoServer
5775 result <- newEmptyMVar
5876
5977 -- Client
6078 forkIO $ do
79+ tlog "Ping client"
6180 Right endpoint <- newEndPoint transport
6281 ping endpoint server numPings "ping"
6382 putStrLn $ "client did " ++ show numPings ++ " pings"
@@ -131,8 +150,9 @@ testConnections transport numPings = do
131150
132151-- Transport tests
133152testTransport :: Transport -> IO Bool
134−testTransport transport =
135− foldl (liftM2 (&&)) (return True) [ testPingPong transport 10000
136− , testEndPoints transport 10000
137− , testConnections transport 10000
153+testTransport transport = do
154+ tlog "Starting transport tests"
155+ foldl (liftM2 (&&)) (return True) [ testPingPong transport 1
156+ , testEndPoints transport 1
157+ , testConnections transport 1
138158 ]