git.iko.soy/distributed-process
on master help sign in
Use 32-bit integres for connection IDs

and reserve the first 1024 for control messages

commit85860aebee8e964af6994ff5fd1b58217e2a6062 authorEdsko de Vries <edsko@edsko.net> dateTue 24 Apr 2012, 13:31 (15 years ago) parentbf6c7a3
5 files changed+135−127
network-transport-2/src/Network/Transport.hs+2−1 network-transport-2/src/Network/Transport/Chan.hs+13−13 network-transport-2/src/Network/Transport/Internal/TCP.hs+24−1 network-transport-2/src/Network/Transport/TCP.hs+48−51 network-transport-2/tests/TestTCP.hs+48−61
network-transport-2/src/Network/Transport.hs+2−1
@@ -24,6 +24,7 @@ import Data.ByteString (ByteString)
2424import Control.Concurrent (forkIO)
2525import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
2626import Control.Monad.Error (Error(..))
27+import Data.Int (Int32)
2728
2829-- | To create a network abstraction layer, use one of the
2930-- @Network.Transport.*@ packages.
@@ -87,7 +88,7 @@ data Reliability =
8788 deriving Show
8889
8990-- | Connection IDs enable receivers to distinguish one connection from another.
90−type ConnectionId = Int
91+type ConnectionId = Int32
9192
9293-- | Lightweight connection to an endpoint.
9394data Connection = Connection {
network-transport-2/src/Network/Transport/Chan.hs+13−13
@@ -13,16 +13,16 @@ 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 EndPointAddress (Chan Event)
17− , _connections :: Map EndPointAddress Int
18− , _multigroups :: Map MulticastAddress (MVar [Chan Event])
16+data TransportState = State { _channels :: Map EndPointAddress (Chan Event)
17+ , _nextConnectionId :: Map EndPointAddress ConnectionId
18+ , _multigroups :: Map MulticastAddress (MVar [Chan Event])
1919 }
2020
2121channels :: Lens TransportState (Map EndPointAddress (Chan Event))
2222channels = lens _channels (\ch st -> st { _channels = ch })
2323
24−connections :: Lens TransportState (Map EndPointAddress Int)
25−connections = lens _connections (\conn st -> st { _connections = conn })
24+nextConnectionId :: Lens TransportState (Map EndPointAddress ConnectionId)
25+nextConnectionId = lens _nextConnectionId (\cid st -> st { _nextConnectionId = cid })
2626
2727multigroups :: Lens TransportState (Map MulticastAddress (MVar [Chan Event]))
2828multigroups = lens _multigroups (\gs st -> st { _multigroups = gs })
@@ -33,8 +33,8 @@ at k = lens (Map.! k) (Map.insert k)
3333channelAt :: EndPointAddress -> Lens TransportState (Chan Event)
3434channelAt addr = channels >>> at addr
3535
36−connectionAt :: EndPointAddress -> Lens TransportState Int
37−connectionAt addr = connections >>> at addr
36+nextConnectionIdAt :: EndPointAddress -> Lens TransportState ConnectionId
37+nextConnectionIdAt addr = nextConnectionId >>> at addr
3838
3939multigroupAt :: MulticastAddress -> Lens TransportState (MVar [Chan Event])
4040multigroupAt addr = multigroups >>> at addr
@@ -45,9 +45,9 @@ multigroupAt addr = multigroups >>> at addr
4545-- (threads can, and should, create their own endpoints though).
4646createTransport :: IO Transport
4747createTransport = do
48− state <- newMVar State { _channels = Map.empty
49− , _connections = Map.empty
50− , _multigroups = Map.empty
48+ state <- newMVar State { _channels = Map.empty
49+ , _nextConnectionId = Map.empty
50+ , _multigroups = Map.empty
5151 }
5252 return Transport { newEndPoint = chanNewEndPoint state }
5353
@@ -57,7 +57,7 @@ chanNewEndPoint state = do
5757 chan <- newChan
5858 addr <- modifyMVar state $ \st -> do
5959 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) . (nextConnectionIdAt addr ^= 1) $ st, addr)
6161 return . Right $ EndPoint { receive = readChan chan
6262 , address = addr
6363 , connect = chanConnect addr state
@@ -70,8 +70,8 @@ chanConnect :: EndPointAddress -> MVar TransportState -> EndPointAddress -> Reli
7070chanConnect myAddress state theirAddress _ = do
7171 (chan, conn) <- modifyMVar state $ \st -> do
7272 let chan = st ^. channelAt theirAddress
73− let conn = st ^. connectionAt theirAddress
74− return (connectionAt theirAddress ^+= 1 $ st, (chan, conn))
73+ let conn = st ^. nextConnectionIdAt theirAddress
74+ return (nextConnectionIdAt theirAddress ^+= 1 $ st, (chan, conn))
7575 writeChan chan $ ConnectionOpened conn ReliableOrdered myAddress
7676 return . Right $ Connection { send = \msg -> do writeChan chan (Received conn msg)
7777 return (Right ())
network-transport-2/src/Network/Transport/Internal/TCP.hs+24−1
@@ -5,11 +5,17 @@ module Network.Transport.Internal.TCP ( forkServer
55 , recvWithLength
66 , sendMany
77 , recvExact
8+ , recvInt16
9+ , recvInt32
10+ , sendInt16
11+ , sendInt32
812 ) where
913
1014import Prelude hiding (catch)
1115import Network.Transport.Internal ( encodeInt32
1216 , decodeInt32
17+ , encodeInt16
18+ , decodeInt16
1319 )
1420import qualified Network.Socket as N ( HostName
1521 , ServiceName
@@ -32,9 +38,10 @@ import Control.Concurrent (forkIO, ThreadId)
3238import Control.Monad (mzero, MonadPlus, unless)
3339import Control.Monad.IO.Class (MonadIO, liftIO)
3440import Control.Exception (catch, IOException)
41+import Control.Applicative (pure)
3542import Data.ByteString (ByteString)
3643import qualified Data.ByteString as BS (length, concat, null)
37−import Data.Int (Int32)
44+import Data.Int (Int16, Int32)
3845
3946-- | Start a server at the specified address
4047--
@@ -110,3 +117,19 @@ recvExact sock len = do
110117 if BS.null bs
111118 then return (True, reverse acc)
112119 else go (bs : acc) (l - fromIntegral (BS.length bs))
120+
121+-- | Receive a 16-bit integer
122+recvInt16 :: (MonadIO m, MonadPlus m) => N.Socket -> m Int16
123+recvInt16 sock = recvExact sock 2 >>= decodeInt16 . BS.concat
124+
125+-- | Receive a 32-bit integer
126+recvInt32 :: (MonadIO m, MonadPlus m) => N.Socket -> m Int32
127+recvInt32 sock = recvExact sock 4 >>= decodeInt32 . BS.concat
128+
129+-- | Send a 16-bit integer
130+sendInt16 :: (MonadIO m, MonadPlus m) => N.Socket -> Int16 -> m ()
131+sendInt16 sock n = encodeInt16 n >>= sendMany sock . pure
132+
133+-- | Send a 32-bit integer
134+sendInt32 :: (MonadIO m, MonadPlus m) => N.Socket -> Int32 -> m ()
135+sendInt32 sock n = encodeInt32 n >>= sendMany sock . pure
network-transport-2/src/Network/Transport/TCP.hs+48−51
@@ -7,7 +7,7 @@
77module Network.Transport.TCP ( -- * Main API
88 createTransport
99 , -- TCP specific functionality
10− EndPointIx
10+ EndPointId
1111 , decodeEndPointAddress
1212 ) where
1313
@@ -15,12 +15,13 @@ import Network.Transport
1515import Network.Transport.Internal.TCP ( forkServer
1616 , connectTo
1717 , recvExact
18− , sendMany
1918 , recvWithLength
2019 , sendWithLength
20+ , recvInt16
21+ , recvInt32
22+ , sendInt32
2123 )
2224import Network.Transport.Internal ( encodeInt16
23− , decodeInt16
2425 , maybeToErrorT
2526 , maybeTToErrorT
2627 )
@@ -34,7 +35,7 @@ import Control.Concurrent (forkIO)
3435import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
3536import Control.Concurrent.MVar (MVar, newMVar, modifyMVar, readMVar)
3637import Control.Category ((>>>))
37−import Control.Applicative ((<*>), (*>), (<$>), pure)
38+import Control.Applicative ((<*>), (*>), (<$>))
3839import Control.Monad (forever, forM_)
3940import Control.Monad.Error (ErrorT, liftIO, runErrorT)
4041import Control.Monad.Trans.Maybe (MaybeT(MaybeT), runMaybeT)
@@ -43,24 +44,24 @@ import Control.Monad.Writer.Class (tell)
4344import Data.ByteString (ByteString)
4445import qualified Data.ByteString as BS (concat)
4546import qualified Data.ByteString.Char8 as BSC (pack, unpack)
47+import Data.Int (Int16)
4648import Data.IntMap (IntMap)
4749import qualified Data.IntMap as IntMap (empty, size)
4850import Data.Lens.Lazy (Lens, lens, intMapLens, (^.), (^=), (^+=))
4951import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
5052
5153data TransportState = TransportState { _endPoints :: IntMap (Chan Event, MVar EndPointState) }
52−data EndPointState = EndPointState { _nextConnectionIx :: ConnectionIx }
53−type EndPointIx = Int
54−type ConnectionIx = Int
54+data EndPointState = EndPointState { _nextConnectionId :: ConnectionId }
55+type EndPointId = Int16
5556
5657endPoints :: Lens TransportState (IntMap (Chan Event, MVar EndPointState))
5758endPoints = lens _endPoints (\es st -> st { _endPoints = es })
5859
59−nextConnectionIx :: Lens EndPointState ConnectionIx
60−nextConnectionIx = lens _nextConnectionIx (\cix st -> st { _nextConnectionIx = cix })
60+nextConnectionId :: Lens EndPointState ConnectionId
61+nextConnectionId = lens _nextConnectionId (\cix st -> st { _nextConnectionId = cix })
6162
62−endPointAt :: EndPointIx -> Lens TransportState (Maybe (Chan Event, MVar EndPointState))
63−endPointAt ix = endPoints >>> intMapLens ix
63+endPointAt :: EndPointId -> Lens TransportState (Maybe (Chan Event, MVar EndPointState))
64+endPointAt ix = endPoints >>> intMapLens (fromIntegral ix)
6465
6566-- | Create a TCP transport
6667--
@@ -92,18 +93,14 @@ transportServer state sock = forever $ do
9293handleClient :: MVar TransportState -> N.Socket -> IO ()
9394handleClient state sock = do
9495 mendpoint <- runMaybeT $ do
95− -- Our endpoint the other side is interested in
96− endPointBS <- recvExact sock 2
97− endPointIx <- decodeInt16 (BS.concat endPointBS)
98− -- Full endpoint address of the other side
99− theirAddress <- BS.concat <$> recvWithLength sock
100− -- Find our endpoint
101− (endPointCh, endPointSt) <- MaybeT $ (^. endPointAt (fromIntegral endPointIx)) <$> readMVar state
102− return (endPointCh, endPointSt, theirAddress)
96+ ourEndPointId <- recvInt16 sock
97+ theirAddress <- BS.concat <$> recvWithLength sock
98+ ourEndPoint <- MaybeT $ (^. endPointAt (fromIntegral ourEndPointId)) <$> readMVar state
99+ return (ourEndPoint, EndPointAddress theirAddress)
103100 case mendpoint of
104− Just (endPointCh, endPointSt, theirAddress) -> do
105− openConnections <- endpointServer sock (EndPointAddress theirAddress) endPointCh endPointSt
106− forM_ openConnections $ \cix -> writeChan endPointCh (ConnectionClosed cix)
101+ Just ((endPointCh, endPointSt), theirAddress) -> do
102+ unclosedConnections <- endpointServer sock theirAddress endPointCh endPointSt
103+ forM_ unclosedConnections $ \cix -> writeChan endPointCh (ConnectionClosed cix)
107104 Nothing ->
108105 -- Invalid request
109106 return ()
@@ -111,41 +108,39 @@ handleClient state sock = do
111108-- | Handle requests for one endpoint. Returns only if an error occurs,
112109-- in which case it returns the connections that are still open (normally,
113110-- this should be an empty list).
114−endpointServer :: N.Socket -> EndPointAddress -> Chan Event -> MVar EndPointState -> IO [ConnectionIx]
111+endpointServer :: N.Socket -> EndPointAddress -> Chan Event -> MVar EndPointState -> IO [ConnectionId]
115112endpointServer sock theirAddress chan state = execWriterT . runMaybeT . forever $ do
116− connBS <- recvExact sock 2
117− connIx <- fromIntegral <$> decodeInt16 (BS.concat connBS)
118− if connIx == 0
119− then createNewConnection
120− else readMessage connIx
113+ connId <- recvInt32 sock
114+ case connId of
115+ 0 -> createNewConnection
116+ _ -> readMessage connId
121117 where
122118 -- A brief note on the types:
123119 -- These functions may fail (connection error): hence MaybeT
124120 -- We also need to keep track of which connections are still open: hence WriterT
125121
126122 -- Create a new connection
127− createNewConnection :: MaybeT (WriterT [ConnectionIx] IO) ()
123+ createNewConnection :: MaybeT (WriterT [ConnectionId] IO) ()
128124 createNewConnection = do
129− newIx <- liftIO $ modifyMVar state $ \st -> return (nextConnectionIx ^+= 1 $ st, st ^. nextConnectionIx)
130− newBs <- encodeInt16 (fromIntegral newIx)
131− sendMany sock [newBs]
132− liftIO $ writeChan chan (ConnectionOpened newIx ReliableOrdered theirAddress)
133− tell [newIx]
125+ newId <- liftIO $ modifyMVar state $ \st -> return (nextConnectionId ^+= 1 $ st, st ^. nextConnectionId)
126+ sendInt32 sock newId
127+ liftIO $ writeChan chan (ConnectionOpened newId ReliableOrdered theirAddress)
128+ -- We add the new connection ID to the list of open connections only once the
129+ -- endpoint has been notified of the new connection (sendInt32 may fail)
130+ tell [newId]
134131
135132 -- Read a message and output it on the endPoint's channel
136− readMessage :: ConnectionIx -> MaybeT (WriterT [ConnectionIx] IO) ()
137− readMessage connIx = do
138− payload <- recvWithLength sock
139− liftIO $ writeChan chan (Received connIx payload)
133+ readMessage :: ConnectionId -> MaybeT (WriterT [ConnectionId] IO) ()
134+ readMessage connId = recvWithLength sock >>= liftIO . writeChan chan . Received connId
140135
141136-- | Create a new endpoint
142137tcpNewEndPoint :: MVar TransportState -> N.HostName -> N.ServiceName -> IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
143138tcpNewEndPoint transportState host port = do
144139 chan <- newChan
145− endPointState <- newMVar EndPointState { _nextConnectionIx = 1 } -- Connection id 0 is reserved for requesting new connections
146− endPointIx <- modifyMVar transportState $ \st -> do let ix = IntMap.size (st ^. endPoints)
140+ endPointState <- newMVar EndPointState { _nextConnectionId = firstNonReservedConnectionId }
141+ endPointId <- modifyMVar transportState $ \st -> do let ix = fromIntegral $ IntMap.size (st ^. endPoints)
147142 return ((endPointAt ix ^= Just (chan, endPointState)) $ st, ix)
148− let addr = EndPointAddress . BSC.pack $ host ++ ":" ++ port ++ ":" ++ show endPointIx
143+ let addr = EndPointAddress . BSC.pack $ host ++ ":" ++ port ++ ":" ++ show endPointId
149144 return . Right $ EndPoint { receive = readChan chan
150145 , address = addr
151146 , connect = tcpConnect addr
@@ -155,24 +150,26 @@ tcpNewEndPoint transportState host port = do
155150 where
156151 newMulticastGroupError = FailedWith NewMulticastGroupUnsupported "TCP does not support multicast"
157152 resolveMulticastGroupError = FailedWith ResolveMulticastGroupUnsupported "TCP does not support multicast"
158−
153+
154+-- | We reserve a bunch of connection IDs for control messages
155+firstNonReservedConnectionId :: ConnectionId
156+firstNonReservedConnectionId = 1024
159157
160158-- | Connnect to an endpoint
161159tcpConnect :: EndPointAddress -> EndPointAddress -> Reliability -> IO (Either (FailedWith ConnectErrorCode) Connection)
162160tcpConnect myAddress theirAddress _ = runErrorT $ do
163161 sock <- socketForEndPoint myAddress theirAddress
164− connIx <- maybeTToErrorT undefined $ requestNewConnection sock
162+ connId <- maybeTToErrorT undefined $ requestNewConnection sock
165163 return Connection { send = runErrorT .
166164 maybeTToErrorT (FailedWith SendFailed "Send failed") .
167− sendWithLength sock (Just connIx)
165+ sendWithLength sock (Just connId)
168166 , close = N.sClose sock
169167 }
170168
171169-- | Request a new connection
170+-- Returns the new connection ID in serialized form
172171requestNewConnection :: N.Socket -> MaybeT IO ByteString
173−requestNewConnection sock = do
174− liftIO (pure <$> encodeInt16 0) >>= sendMany sock
175− BS.concat <$> recvExact sock 2
172+requestNewConnection sock = sendInt32 sock 0 >> BS.concat <$> recvExact sock 4
176173
177174-- | Find a socket to the specified endpoint
178175--
@@ -183,9 +180,9 @@ socketForEndPoint :: EndPointAddress -- ^ Our address
183180 -> EndPointAddress -- ^ Their address
184181 -> ErrorT (FailedWith ConnectErrorCode) IO N.Socket
185182socketForEndPoint (EndPointAddress myAddress) theirAddress = do
186− (host, port, endPointIx) <- maybeToErrorT invalidAddress (decodeEndPointAddress theirAddress)
183+ (host, port, endPointId) <- maybeToErrorT invalidAddress (decodeEndPointAddress theirAddress)
187184 sock <- maybeTToErrorT undefined (connectTo host port)
188− endPointBS <- liftIO $ encodeInt16 (fromIntegral endPointIx)
185+ endPointBS <- encodeInt16 (fromIntegral endPointId)
189186 maybeTToErrorT undefined $ sendWithLength sock (Just endPointBS) [myAddress]
190187 return sock
191188 where
@@ -193,8 +190,8 @@ socketForEndPoint (EndPointAddress myAddress) theirAddress = do
193190
194191-- | Decode end point address
195192-- TODO: This uses regular expression parsing, which is nice, but unnecessary
196−decodeEndPointAddress :: EndPointAddress -> Maybe (N.HostName, N.ServiceName, EndPointIx)
193+decodeEndPointAddress :: EndPointAddress -> Maybe (N.HostName, N.ServiceName, EndPointId)
197194decodeEndPointAddress (EndPointAddress bs) = match endPointAddressRE (BSC.unpack bs)
198195 where
199− endPointAddressRE :: RE Char (N.HostName, N.ServiceName, EndPointIx)
196+ endPointAddressRE :: RE Char (N.HostName, N.ServiceName, EndPointId)
200197 endPointAddressRE = (,,) <$> few anySym <*> (sym ':' *> few anySym) <*> (sym ':' *> (read <$> many anySym))
network-transport-2/tests/TestTCP.hs+48−61
@@ -2,13 +2,15 @@ module Main where
22
33import Network.Transport
44import Network.Transport.Internal (encodeInt16, decodeInt16)
5−import Network.Transport.Internal.TCP (sendWithLength, recvExact, sendMany)
5+import Network.Transport.Internal.TCP ( sendWithLength
6+ , recvExact
7+ , sendInt32
8+ )
9+import Network.Transport.TCP (createTransport, decodeEndPointAddress, EndPointId)
610import TestTransport (testTransport)
7−import Network.Transport.TCP (createTransport, decodeEndPointAddress)
811import System.Exit (exitFailure, exitSuccess)
9−import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar, readMVar)
12+import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar, readMVar)
1013import Control.Concurrent (forkIO)
11−-- import Control.Monad.IO.Class (liftIO)
1214import Control.Monad.Trans.Maybe (runMaybeT)
1315import Data.Maybe (fromJust)
1416import qualified Network.Socket as N ( getAddrInfo
@@ -21,68 +23,53 @@ import qualified Network.Socket as N ( getAddrInfo
2123 , defaultProtocol
2224 , setSocketOption
2325 , sClose
26+ , HostName
27+ , ServiceName
2428 )
2529
2630testEarlyDisconnect :: IO ()
2731testEarlyDisconnect = do
28− serverAddr <- newEmptyMVar
29− serverDone <- newEmptyMVar
30− transport <- createTransport "127.0.0.1" "8081"
32+ serverAddr <- newEmptyMVar
33+ serverDone <- newEmptyMVar
34+ transport <- createTransport "127.0.0.1" "8081"
35+
36+ forkIO $ server transport serverAddr serverDone
37+ forkIO $ client transport serverAddr
38+
39+ takeMVar serverDone
40+ where
41+ server :: Transport -> MVar (N.HostName, N.ServiceName, EndPointId) -> MVar () -> IO ()
42+ server transport serverAddr serverDone = do
43+ Right endpoint <- newEndPoint transport
44+ putMVar serverAddr (fromJust . decodeEndPointAddress . address $ endpoint)
45+ ConnectionOpened _ _ _ <- receive endpoint
46+ ConnectionClosed _ <- receive endpoint
47+ putMVar serverDone ()
48+
49+ client :: Transport -> MVar (N.HostName, N.ServiceName, EndPointId) -> IO ()
50+ client transport serverAddr = do
51+ Right endpoint <- newEndPoint transport
52+ let EndPointAddress myAddress = address endpoint
3153
32− -- Server
33− forkIO $ do
34− Right endpoint <- newEndPoint transport
35− putMVar serverAddr (fromJust . decodeEndPointAddress . address $ endpoint)
36−
37− putStrLn "Server waiting for client to connect"
38−
39− -- Wait for an incoming connection
40− ConnectionOpened _ _ _ <- receive endpoint
41−
42− putStrLn "Server waiting for client to disconnect"
43−
44− -- Wait for the client to disconnect
45− ConnectionClosed _ <- receive endpoint
46−
47− putStrLn "Server exiting"
48−
49− putMVar serverDone ()
50−
51− -- Client
52− --
53− -- We manually connect to the server to request a new connection,
54− -- but then close the socket before the server gets the chance to
55− -- respond
56− forkIO $ do
57− Right endpoint <- newEndPoint transport
58− let EndPointAddress myAddress = address endpoint
59−
60− -- Connect to the server
61− addr:_ <- N.getAddrInfo Nothing (Just "127.0.0.1") (Just "8081")
62− sock <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
63− N.setSocketOption sock N.ReuseAddr 1
64− N.connect sock (N.addrAddress addr)
65−
66− (_, _, endPointIx) <- readMVar serverAddr
67− endPointBS <- encodeInt16 (fromIntegral endPointIx)
68− runMaybeT $ sendWithLength sock (Just endPointBS) [myAddress]
69−
70− -- Request a new connection
71− zero <- encodeInt16 0
72− runMaybeT $ sendMany sock [zero]
73−
74− -- Request a new connection
75− connIx <- runMaybeT $ do
76− [connBs] <- recvExact sock 2
77− decodeInt16 connBs
78− putStrLn $ "Client got connection ID " ++ show connIx
79−
80− -- Close the socket without closing the connection explicitly
81− -- The server should still receive a ConnectionClosed message
82− N.sClose sock
83−
84− -- Wait for the server to finish
85− takeMVar serverDone
54+ -- Connect to the server
55+ addr:_ <- N.getAddrInfo Nothing (Just "127.0.0.1") (Just "8081")
56+ sock <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
57+ N.setSocketOption sock N.ReuseAddr 1
58+ N.connect sock (N.addrAddress addr)
59+
60+ (_, _, endPointIx) <- readMVar serverAddr
61+ endPointBS <- encodeInt16 (fromIntegral endPointIx)
62+ runMaybeT $ sendWithLength sock (Just endPointBS) [myAddress]
63+
64+ -- Request a new connection
65+ connIx <- runMaybeT $ do
66+ sendInt32 sock 0
67+ [connBs] <- recvExact sock 2
68+ decodeInt16 connBs
69+
70+ -- Close the socket without closing the connection explicitly
71+ -- The server should still receive a ConnectionClosed message
72+ N.sClose sock
8673
8774main :: IO ()
8875main = do