git.iko.soy/distributed-process
on master help sign in
Basic implementation of the TCP transport
3217273Edsko de Vries
network-transport-2/network-transport.cabal+27−7
@@ -11,21 +11,41 @@ Library
11 Build-Depends: base,
11 Build-Depends: base,
12 bytestring,
12 bytestring,
13 containers,
13 containers,
14− data-lens
14+ data-lens,
15+ network,
16+ regex-applicative,
17+ mtl
15 Exposed-modules: Network.Transport,
18 Exposed-modules: Network.Transport,
16 Network.Transport.Chan
19 Network.Transport.Chan
17− extensions: EmptyDataDecls
20+ extensions: EmptyDataDecls, ForeignFunctionInterface
18 ghc-options: -Wall -fno-warn-unused-do-bind
21 ghc-options: -Wall -fno-warn-unused-do-bind
19 HS-Source-Dirs: src
22 HS-Source-Dirs: src
20
23
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+
21Test-Suite TestMulticastInMemory
38Test-Suite TestMulticastInMemory
22 Type: exitcode-stdio-1.0
39 Type: exitcode-stdio-1.0
23 Main-Is: TestMulticastInMemory.hs
40 Main-Is: TestMulticastInMemory.hs
24 Build-Depends: base,
41 Build-Depends: base,
25 containers,
42 containers,
26 bytestring,
43 bytestring,
27− data-lens
44+ data-lens,
45+ network,
46+ regex-applicative,
47+ mtl
28− extensions: EmptyDataDecls OverloadedStrings
48+ extensions: EmptyDataDecls, OverloadedStrings
29 ghc-options: -Wall -fno-warn-unused-do-bind
49 ghc-options: -Wall -fno-warn-unused-do-bind
30 HS-Source-Dirs: tests src
50 HS-Source-Dirs: tests src
31
51
@@ -35,8 +55,8 @@ Test-Suite TestInMemory
35 Build-Depends: base,
55 Build-Depends: base,
36 containers,
56 containers,
37 bytestring,
57 bytestring,
38− data-lens
58+ data-lens,
59+ mtl
39− extensions: EmptyDataDecls OverloadedStrings
60+ extensions: EmptyDataDecls, OverloadedStrings
40 ghc-options: -Wall -fno-warn-unused-do-bind
61 ghc-options: -Wall -fno-warn-unused-do-bind
41 HS-Source-Dirs: tests src
62 HS-Source-Dirs: tests src
42−
network-transport-2/src/Network/Transport.hs+49−17
@@ -1,43 +1,75 @@
1-- | Network Transport
1-- | 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
3
21
4import Data.ByteString (ByteString)
22import Data.ByteString (ByteString)
5import Control.Concurrent (forkIO)
23import Control.Concurrent (forkIO)
6import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
24import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
25+import Control.Monad.Error (Error(..))
7
26
8-- | To create a network abstraction layer, use one of the
27-- | To create a network abstraction layer, use one of the
9-- @Network.Transport.*@ packages.
28-- @Network.Transport.*@ packages.
10data Transport = Transport {
29data Transport = Transport {
11 -- | Create a new end point (heavyweight operation)
30 -- | Create a new end point (heavyweight operation)
12− newEndPoint :: IO (Either Error EndPoint)
31+ newEndPoint :: IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
13 }
32 }
14
33
15−-- | Address of an endpoint.
34+-- | EndPointAddress of an endpoint.
16−newtype Address = Address ByteString
35+newtype EndPointAddress = EndPointAddress ByteString
17 deriving (Show, Eq, Ord)
36 deriving (Show, Eq, Ord)
18
37
19−-- | Address of a multicast group.
38+-- | EndPointAddress of a multicast group.
20newtype MulticastAddress = MulticastAddress ByteString
39newtype MulticastAddress = MulticastAddress ByteString
21 deriving (Show, Eq, Ord)
40 deriving (Show, Eq, Ord)
22
41
23−-- | Error codes (none defined at the moment).
42+-- | Errors during the creation of an endpoint (currently, there are none)
24−data ErrorCode
43+data NewEndPointErrorCode
25
44
26−-- | Error consisting of an error code and a human readable error string.
45+-- | Connection failure
27−data Error = Error ErrorCode String
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
28
60
29-- | Network endpoint.
61-- | Network endpoint.
30data EndPoint = EndPoint {
62data EndPoint = EndPoint {
31 -- | Endpoints have a single shared receive queue.
63 -- | Endpoints have a single shared receive queue.
32 receive :: IO Event
64 receive :: IO Event
33− -- | Address of the endpoint.
65+ -- | EndPointAddress of the endpoint.
34− , address :: Address
66+ , address :: EndPointAddress
35 -- | Create a new lightweight connection.
67 -- | Create a new lightweight connection.
36− , connect :: Address -> Reliability -> IO (Either Error Connection)
68+ , connect :: EndPointAddress -> Reliability -> IO (Either (FailedWith ConnectErrorCode) Connection)
37 -- | Create a new multicast group.
69 -- | Create a new multicast group.
38− , newMulticastGroup :: IO (Either Error MulticastGroup)
70+ , newMulticastGroup :: IO (Either (FailedWith NewMulticastGroupErrorCode) MulticastGroup)
39 -- | Resolve an address to a multicast group.
71 -- | Resolve an address to a multicast group.
40− , resolveMulticastGroup :: MulticastAddress -> IO (Either Error MulticastGroup)
72+ , resolveMulticastGroup :: MulticastAddress -> IO (Either (FailedWith ResolveMulticastGroupErrorCode) MulticastGroup)
41 }
73 }
42
74
43-- | Reliability guarantees of a connection.
75-- | Reliability guarantees of a connection.
@@ -62,13 +94,13 @@ data Connection = Connection {
62data Event =
94data Event =
63 Received ConnectionId [ByteString]
95 Received ConnectionId [ByteString]
64 | ConnectionClosed ConnectionId
96 | ConnectionClosed ConnectionId
65− | ConnectionOpened ConnectionId Reliability Address
97+ | ConnectionOpened ConnectionId Reliability EndPointAddress
66 | ReceivedMulticast MulticastAddress [ByteString]
98 | ReceivedMulticast MulticastAddress [ByteString]
67 deriving Show
99 deriving Show
68
100
69-- | Multicast group.
101-- | Multicast group.
70data MulticastGroup = MulticastGroup {
102data MulticastGroup = MulticastGroup {
71− -- | Address of the multicast group.
103+ -- | EndPointAddress of the multicast group.
72 multicastAddress :: MulticastAddress
104 multicastAddress :: MulticastAddress
73 -- | Delete the multicast group completely.
105 -- | Delete the multicast group completely.
74 , deleteMulticastGroup :: IO ()
106 , deleteMulticastGroup :: IO ()
@@ -87,7 +119,7 @@ data MulticastGroup = MulticastGroup {
87-- | Fork a new thread, create a new end point on that thread, and run the specified IO operation on that thread.
119-- | Fork a new thread, create a new end point on that thread, and run the specified IO operation on that thread.
88--
120--
89-- Returns the address of the new end point.
121-- Returns the address of the new end point.
90−spawn :: Transport -> (EndPoint -> IO ()) -> IO Address
122+spawn :: Transport -> (EndPoint -> IO ()) -> IO EndPointAddress
91spawn transport proc = do
123spawn transport proc = do
92 addr <- newEmptyMVar
124 addr <- newEmptyMVar
93 forkIO $ do
125 forkIO $ do
network-transport-2/src/Network/Transport/Chan.hs+16−13
@@ -13,15 +13,15 @@ import qualified Data.ByteString.Char8 as BSC (pack)
13import Data.Lens.Lazy (Lens, lens, (^.), (^%=), (^=), (^+=), mapLens)
13import Data.Lens.Lazy (Lens, lens, (^.), (^%=), (^=), (^+=), mapLens)
14
14
15-- Global state: next available "address", mapping from addresses to channels and next available connection
15-- Global state: next available "address", mapping from addresses to channels and next available connection
16−data TransportState = State { _channels :: Map Address (Chan Event)
16+data TransportState = State { _channels :: Map EndPointAddress (Chan Event)
17− , _connections :: Map Address Int
17+ , _connections :: Map EndPointAddress Int
18 , _multigroups :: Map MulticastAddress (MVar [Chan Event])
18 , _multigroups :: Map MulticastAddress (MVar [Chan Event])
19 }
19 }
20
20
21−channels :: Lens TransportState (Map Address (Chan Event))
21+channels :: Lens TransportState (Map EndPointAddress (Chan Event))
22channels = lens _channels (\ch st -> st { _channels = ch })
22channels = lens _channels (\ch st -> st { _channels = ch })
23
23
24−connections :: Lens TransportState (Map Address Int)
24+connections :: Lens TransportState (Map EndPointAddress Int)
25connections = lens _connections (\conn st -> st { _connections = conn })
25connections = lens _connections (\conn st -> st { _connections = conn })
26
26
27multigroups :: Lens TransportState (Map MulticastAddress (MVar [Chan Event]))
27multigroups :: Lens TransportState (Map MulticastAddress (MVar [Chan Event]))
@@ -30,10 +30,10 @@ multigroups = lens _multigroups (\gs st -> st { _multigroups = gs })
30at :: Ord k => k -> Lens (Map k v) v
30at :: Ord k => k -> Lens (Map k v) v
31at k = lens (Map.! k) (Map.insert k)
31at k = lens (Map.! k) (Map.insert k)
32
32
33−channelAt :: Address -> Lens TransportState (Chan Event)
33+channelAt :: EndPointAddress -> Lens TransportState (Chan Event)
34channelAt addr = channels >>> at addr
34channelAt addr = channels >>> at addr
35
35
36−connectionAt :: Address -> Lens TransportState Int
36+connectionAt :: EndPointAddress -> Lens TransportState Int
37connectionAt addr = connections >>> at addr
37connectionAt addr = connections >>> at addr
38
38
39multigroupAt :: MulticastAddress -> Lens TransportState (MVar [Chan Event])
39multigroupAt :: MulticastAddress -> Lens TransportState (MVar [Chan Event])
@@ -52,11 +52,11 @@ createTransport = do
52 return Transport { newEndPoint = chanNewEndPoint state }
52 return Transport { newEndPoint = chanNewEndPoint state }
53
53
54-- Create a new end point
54-- Create a new end point
55−chanNewEndPoint :: MVar TransportState -> IO (Either Error EndPoint)
55+chanNewEndPoint :: MVar TransportState -> IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
56chanNewEndPoint state = do
56chanNewEndPoint state = do
57 chan <- newChan
57 chan <- newChan
58 addr <- modifyMVar state $ \st -> do
58 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
60 return ((channelAt addr ^= chan) . (connectionAt addr ^= 0) $ st, addr)
60 return ((channelAt addr ^= chan) . (connectionAt addr ^= 0) $ st, addr)
61 return . Right $ EndPoint { receive = readChan chan
61 return . Right $ EndPoint { receive = readChan chan
62 , address = addr
62 , address = addr
@@ -66,7 +66,7 @@ chanNewEndPoint state = do
66 }
66 }
67
67
68-- Create a new connection
68-- 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)
70chanConnect myAddress state theirAddress _ = do
70chanConnect myAddress state theirAddress _ = do
71 (chan, conn) <- modifyMVar state $ \st -> do
71 (chan, conn) <- modifyMVar state $ \st -> do
72 let chan = st ^. channelAt theirAddress
72 let chan = st ^. channelAt theirAddress
@@ -75,10 +75,10 @@ chanConnect myAddress state theirAddress _ = do
75 writeChan chan $ ConnectionOpened conn ReliableOrdered myAddress
75 writeChan chan $ ConnectionOpened conn ReliableOrdered myAddress
76 return . Right $ Connection { send = writeChan chan . Received conn
76 return . Right $ Connection { send = writeChan chan . Received conn
77 , close = writeChan chan $ ConnectionClosed conn
77 , close = writeChan chan $ ConnectionClosed conn
78− }
78+ }
79
79
80-- Create a new multicast group
80-- 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)
82chanNewMulticastGroup state endpoint = do
82chanNewMulticastGroup state endpoint = do
83 group <- newMVar []
83 group <- newMVar []
84 addr <- modifyMVar state $ \st -> do
84 addr <- modifyMVar state $ \st -> do
@@ -110,9 +110,12 @@ chanMulticastGroup state endpoint addr group =
110 }
110 }
111
111
112-- Resolve a multicast group
112-- 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)
114chanResolveMulticastGroup state endpoint addr = do
117chanResolveMulticastGroup state endpoint addr = do
115 group <- (^. (multigroups >>> mapLens addr)) <$> readMVar state
118 group <- (^. (multigroups >>> mapLens addr)) <$> readMVar state
116 case group of
119 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")
118 Just mvar -> return . Right $ chanMulticastGroup state endpoint addr mvar
121 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 @@
1module TestTransport where
1module TestTransport where
2
2
3−import Control.Concurrent (forkIO)
3+import Control.Concurrent (forkIO, myThreadId)
4import Control.Monad (liftM2, replicateM, replicateM_, when)
4import Control.Monad (liftM2, replicateM, replicateM_, when)
5import Control.Applicative ((<$>))
5import Control.Applicative ((<$>))
6import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
6import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
@@ -12,13 +12,22 @@ import qualified Data.Map as Map (empty, insert, (!), delete)
12
12
13type Test = IO Bool
13type Test = IO Bool
14
14
15+-- Logging (for debugging)
16+tlog :: String -> IO ()
17+tlog msg = do
18+ tid <- myThreadId
19+ putStrLn $ show tid ++ ": " ++ msg
20+
15-- Server that echoes messages straight back to the origin endpoint.
21-- Server that echoes messages straight back to the origin endpoint.
16echoServer :: EndPoint -> IO ()
22echoServer :: EndPoint -> IO ()
17−echoServer endpoint = go Map.empty
23+echoServer endpoint = do
24+ tlog "Echo server"
25+ go Map.empty
18 where
26 where
19 go :: Map ConnectionId Connection -> IO ()
27 go :: Map ConnectionId Connection -> IO ()
20 go cs = do
28 go cs = do
21 event <- receive endpoint
29 event <- receive endpoint
30+ tlog (show event)
22 case event of
31 case event of
23 ConnectionOpened cid rel addr -> do
32 ConnectionOpened cid rel addr -> do
24 Right conn <- connect endpoint addr rel
33 Right conn <- connect endpoint addr rel
@@ -33,31 +42,41 @@ echoServer endpoint = go Map.empty
33 -- Ignore
42 -- Ignore
34 go cs
43 go cs
35
44
36−ping :: EndPoint -> Address -> Int -> ByteString -> IO ()
45+ping :: EndPoint -> EndPointAddress -> Int -> ByteString -> IO ()
37ping endpoint server numPings msg = do
46ping endpoint server numPings msg = do
38 -- Open connection to the server
47 -- Open connection to the server
48+ tlog "Open connection"
39 Right conn <- connect endpoint server ReliableOrdered
49 Right conn <- connect endpoint server ReliableOrdered
40
50
41 -- Wait for the server to open reply connection
51 -- Wait for the server to open reply connection
52+ tlog "Wait for ConnectionOpened message"
42 ConnectionOpened _ _ _ <- receive endpoint
53 ConnectionOpened _ _ _ <- receive endpoint
43
54
44 -- Send pings and wait for reply
55 -- Send pings and wait for reply
56+ tlog "Send ping and wait for reply"
45 replicateM_ numPings $ do
57 replicateM_ numPings $ do
46 send conn [msg]
58 send conn [msg]
59+ event <- receive endpoint
60+ case event of
47− Received _ [reply] <- receive endpoint
61+ Received _ [reply] | reply == msg ->
48− when (reply /= msg) . error $ "Message mismatch: " ++ show msg ++ " /= " ++ show reply
62+ return ()
63+ _ -> do
64+ error $ "Unexpected event " ++ show event
49
65
50 -- Close the connection
66 -- Close the connection
67+ tlog "Close the connection"
51 close conn
68 close conn
52
69
53-- Basic ping test
70-- Basic ping test
54testPingPong :: Transport -> Int -> Test
71testPingPong :: Transport -> Int -> Test
55testPingPong transport numPings = do
72testPingPong transport numPings = do
73+ tlog "Starting ping pong test"
56 server <- spawn transport echoServer
74 server <- spawn transport echoServer
57 result <- newEmptyMVar
75 result <- newEmptyMVar
58
76
59 -- Client
77 -- Client
60 forkIO $ do
78 forkIO $ do
79+ tlog "Ping client"
61 Right endpoint <- newEndPoint transport
80 Right endpoint <- newEndPoint transport
62 ping endpoint server numPings "ping"
81 ping endpoint server numPings "ping"
63 putStrLn $ "client did " ++ show numPings ++ " pings"
82 putStrLn $ "client did " ++ show numPings ++ " pings"
@@ -131,8 +150,9 @@ testConnections transport numPings = do
131
150
132-- Transport tests
151-- Transport tests
133testTransport :: Transport -> IO Bool
152testTransport :: Transport -> IO Bool
134−testTransport transport =
153+testTransport transport = do
154+ tlog "Starting transport tests"
135− foldl (liftM2 (&&)) (return True) [ testPingPong transport 10000
155+ foldl (liftM2 (&&)) (return True) [ testPingPong transport 1
136− , testEndPoints transport 10000
156+ , testEndPoints transport 1
137− , testConnections transport 10000
157+ , testConnections transport 1
138 ]
158 ]