4-- Module : Streamly.Internal.Network.Socket
5-- Copyright : (c) 2018 Composewell Technologies
8-- Maintainer : streamly@composewell.com
9-- Stability : experimental
12module Streamly.Internal.Network.Socket
19 -- * Accept connections
27 -- * Read from connection
42 -- * Write to connection
57 -- reading/writing datagrams
61 , readChunksWithBufferOf
63 , writeChunksWithBufferOf
67import Control.Concurrent (threadWaitWrite, rtsSupportsBoundThreads)
68import Control.Exception (onException)
69import Control.Monad (forM_, when)
70import Control.Monad.Catch (MonadCatch, finally, MonadMask)
71import Control.Monad.IO.Class (MonadIO(..))
72import Data.Maybe (isNothing, fromJust)
73import Data.Word (Word8)
74import Foreign.Ptr (plusPtr, Ptr, castPtr)
75import Streamly.Data.MutByteArray (Unbox)
77 (Socket, SocketOption(..), Family(..), SockAddr(..),
78 ProtocolNumber, withSocketsDo, SocketType(..), socket, bind,
79 setSocketOption, sendBuf, recvBuf)
80#if MIN_VERSION_network(3,1,0)
81import Network.Socket (withFdSocket)
83import Network.Socket (fdSocket)
85import Prelude hiding (read)
87import qualified Network.Socket as Net
89import Streamly.Internal.Data.Array (Array(..))
90import Streamly.Data.Fold (Fold)
91import Streamly.Data.Stream (Stream)
92import Streamly.Internal.Data.Unfold (Unfold(..))
93-- import Streamly.String (encodeUtf8, decodeUtf8, foldLines)
94import Streamly.Internal.System.IO (defaultChunkSize)
96import qualified Streamly.Data.Array as A
97import qualified Streamly.Data.Fold as FL
98import qualified Streamly.Data.Stream as S
99import qualified Streamly.Data.Unfold as UF
100import qualified Streamly.Internal.Data.Array as A
101 ( unsafeFreeze, unsafePinnedAsPtr, byteLength, pinnedChunksOf,
102 pinnedCreateOf, unsafePinnedCreateOf, lCompactGE )
103import qualified Streamly.Internal.Data.MutArray as MArray
104 (MutArray(..), unsafePinnedAsPtr, pinnedEmptyOf)
105import qualified Streamly.Internal.Data.Stream as S (fromStreamK, Stream(..), Step(..))
106import qualified Streamly.Internal.Data.StreamK as K (mkStream)
110-- >>> import Streamly.Internal.System.IO (defaultChunkSize)
111-- >>> import qualified Streamly.Internal.Network.Socket as Socket
113-- | @'forSocketM' action socket@ runs the monadic computation @action@ passing
114-- the socket handle to it. The handle will be closed on exit from
115-- 'forSocketM', whether by normal termination or by raising an exception. If
116-- closing the handle raises an exception, then this exception will be raised
117-- by 'forSocketM' rather than any exception raised by 'action'.
119{-# INLINE forSocketM #-}
120forSocketM :: (MonadMask m, MonadIO m) => (Socket -> m ()) -> Socket -> m ()
121forSocketM f sk = finally (f sk) (liftIO (Net.close sk))
123-- | Like 'forSocketM' but runs a streaming computation instead of a monadic
126-- /Inhibits stream fusion/
129{-# INLINE withSocket #-}
130withSocket :: (MonadIO m, MonadCatch m) =>
131 Socket -> (Socket -> Stream m a) -> Stream m a
132withSocket sk f = S.finallyIO (Net.close sk) (f sk)
134-------------------------------------------------------------------------------
136-------------------------------------------------------------------------------
138-- XXX Protocol specific socket options should be separated from socket level
141-- NOTE: the socket config is specified as a record and not by composing
142-- functions because all the fields are mandatory except the sockOpts field.
144-- | Specify the socket protocol details.
145data SockSpec = SockSpec
147 sockFamily :: !Family
148 , sockType :: !SocketType
149 , sockProto :: !ProtocolNumber
150 , sockOpts :: ![(SocketOption, Int)]
153initListener :: Int -> SockSpec -> SockAddr -> IO Socket
154initListener listenQLen SockSpec{..} addr =
156 sock <- socket sockFamily sockType sockProto
157 use sock `onException` Net.close sock
163 mapM_ (\(opt, val) -> setSocketOption sock opt val) sockOpts
165 Net.listen sock listenQLen
167{-# INLINE listenTuples #-}
168listenTuples :: MonadIO m
169 => Unfold m (Int, SockSpec, SockAddr) (Socket, SockAddr)
170listenTuples = Unfold step inject
172 inject (listenQLen, spec, addr) =
173 liftIO $ initListener listenQLen spec addr
176 r <- liftIO (Net.accept listener `onException` Net.close listener)
177 return $ S.Yield r listener
179-- | Unfold a three tuple @(listenQLen, spec, addr)@ into a stream of connected
180-- protocol sockets corresponding to incoming connections. @listenQLen@ is the
181-- maximum number of pending connections in the backlog. @spec@ is the socket
182-- protocol and options specification and @addr@ is the protocol address where
183-- the server listens for incoming connections.
185{-# INLINE acceptor #-}
186acceptor :: MonadIO m => Unfold m (Int, SockSpec, SockAddr) Socket
187acceptor = fmap fst listenTuples
189{-# INLINE connectCommon #-}
190connectCommon :: SockSpec -> Maybe SockAddr -> SockAddr -> IO Socket
191connectCommon SockSpec{..} local remote = withSocketsDo $ do
192 sock <- socket sockFamily sockType sockProto
193 use sock `onException` Net.close sock
199 mapM_ (\(opt, val) -> setSocketOption sock opt val) sockOpts
200 forM_ local (bind sock)
201 Net.connect sock remote
203-- | Connect to a remote host using the given socket specification and remote
204-- address. Returns a connected socket or throws an exception.
208{-# INLINE connect #-}
209connect :: SockSpec -> SockAddr -> IO Socket
210connect spec = connectCommon spec Nothing
212-- | Connect to a remote host using the given socket specification, a local
213-- address to bind to and a remote address to connect to. Returns a connected
214-- socket or throws an exception.
218{-# INLINE connectFrom #-}
219connectFrom :: SockSpec -> SockAddr -> SockAddr -> IO Socket
220connectFrom spec local = connectCommon spec (Just local)
222-------------------------------------------------------------------------------
224-------------------------------------------------------------------------------
226{-# INLINE recvConnectionTuplesWith #-}
227recvConnectionTuplesWith :: MonadIO m
228 => Int -> SockSpec -> SockAddr -> Stream m (Socket, SockAddr)
229recvConnectionTuplesWith tcpListenQ spec addr = S.unfoldrM step Nothing
232 listener <- liftIO $ initListener tcpListenQ spec addr
233 r <- liftIO (Net.accept listener `onException` Net.close listener)
234 return $ Just (r, Just listener)
236 step (Just listener) = do
237 r <- liftIO (Net.accept listener `onException` Net.close listener)
238 return $ Just (r, Just listener)
240-- | Start a TCP stream server that listens for connections on the supplied
241-- server address specification (address family, local interface IP address and
242-- port). The server generates a stream of connected sockets. The first
243-- argument is the maximum number of pending connections in the backlog.
247accept :: MonadIO m => Int -> SockSpec -> SockAddr -> Stream m Socket
248accept tcpListenQ spec addr =
249 fst <$> recvConnectionTuplesWith tcpListenQ spec addr
251-------------------------------------------------------------------------------
253-------------------------------------------------------------------------------
255-- XXX add an API that compacts the arrays to an exact size.
257{-# INLINABLE readArrayUptoWith #-}
259 :: (h -> Ptr Word8 -> Int -> IO Int)
263readArrayUptoWith f size h = do
264 arr <- MArray.pinnedEmptyOf size
265 -- ptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: Word8))
266 MArray.unsafePinnedAsPtr arr $ \p -> do
268 let v = A.unsafeFreeze
269 $ arr { MArray.arrEnd = n, MArray.arrBound = size }
271 -- XXX shrink only if the diff is significant
275-- | Read a byte array from a file handle up to a maximum of the requested
276-- size. If no data is available on the handle it blocks until some data
277-- becomes available. If data is available then it immediately returns that
278-- data without blocking.
280{-# INLINABLE getChunk #-}
281getChunk :: Int -> Socket -> IO (Array Word8)
282getChunk = readArrayUptoWith recvBuf
284-------------------------------------------------------------------------------
286-------------------------------------------------------------------------------
288waitWhen0 :: Int -> Socket -> IO ()
289waitWhen0 0 s = when rtsSupportsBoundThreads $
290#if MIN_VERSION_network(3,1,0)
291 withFdSocket s $ \fd -> threadWaitWrite $ fromIntegral fd
292#elif MIN_VERSION_network(3,0,0)
293 fdSocket s >>= threadWaitWrite . fromIntegral
295 let fd = fdSocket s in threadWaitWrite $ fromIntegral fd
297waitWhen0 _ _ = return ()
299sendAll :: Socket -> Ptr Word8 -> Int -> IO ()
300sendAll _ _ len | len <= 0 = return ()
302 sent <- sendBuf s p len
304 -- assert (sent <= len)
305 when (sent >= 0) $ sendAll s (p `plusPtr` sent) (len - sent)
307{-# INLINABLE writeArrayWith #-}
308writeArrayWith :: Unbox a
309 => (h -> Ptr Word8 -> Int -> IO ())
313writeArrayWith _ _ arr | A.length arr == 0 = return ()
314writeArrayWith f h arr = A.unsafePinnedAsPtr arr $ \ptr -> f h (castPtr ptr) aLen
318 aLen = A.byteLength arr
320-- | Write an Array to a socket.
322{-# INLINABLE putChunk #-}
323putChunk :: Unbox a => Socket -> Array a -> IO ()
324putChunk = writeArrayWith sendAll
326-------------------------------------------------------------------------------
327-- Stream of Arrays IO
328-------------------------------------------------------------------------------
330{-# INLINE _readChunksUptoWith #-}
331_readChunksUptoWith :: (MonadIO m)
332 => (Int -> h -> IO (Array Word8))
333 -> Int -> h -> Stream m (Array Word8)
334_readChunksUptoWith f size h = S.fromStreamK go
336 -- XXX use cons/nil instead
337 go = K.mkStream $ \_ yld _ stp -> do
338 arr <- liftIO $ f size h
343-- | @readChunksWith bufsize socket@ reads a stream of arrays from @socket@.
344-- The maximum size of a single array is limited to @bufsize@.
347{-# INLINE_NORMAL readChunksWith #-}
348readChunksWith :: MonadIO m => Int -> Socket -> Stream m (Array Word8)
349-- readChunksWith = _readChunksUptoWith readChunk
350readChunksWith size h = S.Stream step ()
352 {-# INLINE_LATE step #-}
354 arr <- liftIO $ getChunk size h
360-- | Read a stream of byte arrays from a socket. The maximum size of a single
361-- array is limited to @defaultChunkSize@.
363-- >>> readChunks = Socket.readChunksWith defaultChunkSize
366{-# INLINE readChunks #-}
367readChunks :: MonadIO m => Socket -> Stream m (Array Word8)
368readChunks = readChunksWith defaultChunkSize
370-- | Unfold the tuple @(bufsize, socket)@ into a stream of 'Word8' arrays.
371-- Read requests to the socket are performed using a buffer of size @bufsize@.
372-- The size of an array in the resulting stream is always less than or equal to
375{-# INLINE_NORMAL chunkReaderWith #-}
376chunkReaderWith :: MonadIO m => Unfold m (Int, Socket) (Array Word8)
377chunkReaderWith = Unfold step return
379 {-# INLINE_LATE step #-}
381 arr <- liftIO $ getChunk size h
385 _ -> S.Yield arr (size, h)
387-- | Same as 'chunkReaderWith'
389{-# DEPRECATED readChunksWithBufferOf "Please use 'chunkReaderWith' instead" #-}
390{-# INLINE_NORMAL readChunksWithBufferOf #-}
391readChunksWithBufferOf :: MonadIO m => Unfold m (Int, Socket) (Array Word8)
392readChunksWithBufferOf = chunkReaderWith
394-- | Unfolds a socket into a stream of 'Word8' arrays. Requests to the socket
395-- are performed using a buffer of size
396-- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'. The
397-- size of arrays in the resulting stream are therefore less than or equal to
398-- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'.
400{-# INLINE chunkReader #-}
401chunkReader :: MonadIO m => Unfold m Socket (Array Word8)
402chunkReader = UF.first defaultChunkSize chunkReaderWith
404-------------------------------------------------------------------------------
405-- Read File to Stream
406-------------------------------------------------------------------------------
408{-# INLINE concatChunks #-}
409concatChunks :: (Monad m, Unbox a) => Stream m (Array a) -> Stream m a
410concatChunks = S.unfoldMany A.reader
412-- | Generate a byte stream from a socket using a buffer of the given size.
415{-# INLINE readWith #-}
416readWith :: MonadIO m => Int -> Socket -> Stream m Word8
417readWith size = concatChunks . readChunksWith size
419-- | Generate a byte stream from a socket.
421-- >>> read = Socket.readWith defaultChunkSize
425read :: MonadIO m => Socket -> Stream m Word8
426read = readWith defaultChunkSize
428-- | Unfolds the tuple @(bufsize, socket)@ into a byte stream, read requests
429-- to the socket are performed using buffers of @bufsize@.
431{-# INLINE readerWith #-}
432readerWith :: MonadIO m => Unfold m (Int, Socket) Word8
433readerWith = UF.many A.reader chunkReaderWith
435-- | Same as 'readWith'
437{-# DEPRECATED readWithBufferOf "Please use 'readerWith' instead" #-}
438{-# INLINE readWithBufferOf #-}
439readWithBufferOf :: MonadIO m => Unfold m (Int, Socket) Word8
440readWithBufferOf = readerWith
442-- | Unfolds a 'Socket' into a byte stream. IO requests to the socket are
443-- performed in sizes of
444-- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'.
447reader :: MonadIO m => Unfold m Socket Word8
448reader = UF.first defaultChunkSize readerWith
450-------------------------------------------------------------------------------
452-------------------------------------------------------------------------------
454-- | Write a stream of arrays to a handle.
456{-# INLINE putChunks #-}
457putChunks :: (MonadIO m, Unbox a)
458 => Socket -> Stream m (Array a) -> m ()
459putChunks h = S.fold (FL.drainMapM (liftIO . putChunk h))
461-- | Write a stream of arrays to a socket. Each array in the stream is written
462-- to the socket as a separate IO request.
464{-# INLINE writeChunks #-}
465writeChunks :: (MonadIO m, Unbox a) => Socket -> Fold m (Array a) ()
466writeChunks h = FL.drainMapM (liftIO . putChunk h)
468-- | @writeChunksWith bufsize socket@ writes a stream of arrays to
469-- @socket@ after coalescing the adjacent arrays in chunks of @bufsize@.
470-- Multiple arrays are coalesed as long as the total size remains below the
471-- specified size. It never splits an array, if a single array is bigger than
472-- the specified size it emitted as it is.
474{-# INLINE writeChunksWith #-}
475writeChunksWith :: (MonadIO m, Unbox a)
476 => Int -> Socket -> Fold m (Array a) ()
477writeChunksWith n h = A.lCompactGE n (writeChunks h)
479-- | Same as 'writeChunksWith'
481{-# DEPRECATED writeChunksWithBufferOf "Please use 'writeChunksWith' instead" #-}
482{-# INLINE writeChunksWithBufferOf #-}
483writeChunksWithBufferOf :: (MonadIO m, Unbox a)
484 => Int -> Socket -> Fold m (Array a) ()
485writeChunksWithBufferOf = writeChunksWith
487-- GHC buffer size dEFAULT_FD_BUFFER_SIZE=8192 bytes.
490-- Note that if you use a chunk size less than 8K (GHC's default buffer
491-- size) then you are advised to use 'NOBuffering' mode on the 'Handle' in case you
492-- do not want buffering to occur at GHC level as well. Same thing applies to
495-- | Like 'write' but provides control over the write buffer. Output will
496-- be written to the IO device as soon as we collect the specified number of
499{-# INLINE putBytesWith #-}
500putBytesWith :: MonadIO m => Int -> Socket -> Stream m Word8 -> m ()
501putBytesWith n h m = putChunks h $ A.pinnedChunksOf n m
503-- | Write a byte stream to a socket. Accumulates the input in chunks of
504-- specified number of bytes before writing.
506{-# INLINE writeWith #-}
507writeWith :: MonadIO m => Int -> Socket -> Fold m Word8 ()
508writeWith n h = FL.groupsOf n (A.unsafePinnedCreateOf n) (writeChunks h)
510-- | Same as 'writeWith'
512{-# DEPRECATED writeWithBufferOf "Please use 'writeWith' instead" #-}
513{-# INLINE writeWithBufferOf #-}
514writeWithBufferOf :: MonadIO m => Int -> Socket -> Fold m Word8 ()
515writeWithBufferOf = writeWith
517-- | Write a stream of 'Maybe' values. Keep buffering the 'Just' values in an
518-- array. Write the array to the 'Handle' as soon as a 'Nothing' is encountered
519-- or the buffer size exceeds the specified limit.
522{-# INLINE writeMaybesWith #-}
523writeMaybesWith :: (MonadIO m )
524 => Int -> Socket -> Fold m (Maybe Word8) ()
526 let writeNJusts = FL.lmap fromJust $ A.pinnedCreateOf n
527 writeOnNothing = FL.takeEndBy_ isNothing writeNJusts
528 in FL.many writeOnNothing (writeChunks h)
530-- | Write a byte stream to a file handle. Combines the bytes in chunks of size
531-- up to 'defaultChunkSize' before writing. Note that the write behavior
532-- depends on the 'IOMode' and the current seek position of the handle.
534{-# INLINE putBytes #-}
535putBytes :: MonadIO m => Socket -> Stream m Word8 -> m ()
536putBytes = putBytesWith defaultChunkSize
538-- | Write a byte stream to a socket. Accumulates the input in chunks of
539-- up to 'defaultChunkSize' bytes before writing.
541-- >>> write = Socket.writeWith defaultChunkSize
544write :: MonadIO m => Socket -> Fold m Word8 ()
545write = writeWith defaultChunkSize
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list