我正在编写网络文件传输应用程序。使用Lazy ByteString作为中间
import qualified Data.ByteString.Lazy as BSL
从本地文件构造BSL时,将BSL放入套接字句柄:
BSL.readFile filename >>= BSL.hPut remoteH -- OK
这很好用。内存使用量不变。但是要从Socket接收数据,请写入本地文件:
BSL.hGet remoteH size >>= BSL.hPut fileH bs -- starts swapping in 1 second
我可以看到内存使用率持续上升,BSL占用 size 字节的内存。 更糟糕的是,对于超出我的物理内存大小的 size ,操作系统会立即开始交换。
我必须递归地接收ByteStrings的片段。没关系。
为什么 BSL 表现得那样?
答案 0 :(得分:4)
hGet
是严格的 - 它会立即要求您请求的字节数。它这样做是为了便于数据包级读取。
但是,hGetContentsN
是懒惰的,而readFile
是以hGetContentsN
来实现的。
考虑两个实现:
hGetContentsN :: Int -> Handle -> IO ByteString
hGetContentsN k h = lazyRead -- TODO close on exceptions
where
lazyRead = unsafeInterleaveIO loop
loop = do
c <- S.hGetSome h k -- only blocks if there is no data available
if S.null c
then do hClose h >> return Empty
else do cs <- lazyRead
return (Chunk c cs)
和
hGet :: Handle -> Int -> IO ByteString
hGet = hGetN defaultChunkSize
hGetN :: Int -> Handle -> Int -> IO ByteString
hGetN k h n | n > 0 = readChunks n
where
STRICT1(readChunks)
readChunks i = do
c <- S.hGet h (min k i)
case S.length c of
0 -> return Empty
m -> do cs <- readChunks (i - m)
return (Chunk c cs)
关键的魔力是hGetContentsN
中的懒惰。
答案 1 :(得分:2)
我无法权威地回答延迟字节串的行为,但我建议您研究某种流式方法,例如conduit或enumerator。使用管道,您可以编写如下内容:
import Data.Conduit
import Data.Conduit.Binary
main = do
let filename = "something"
remoteH <- getRemoteHandle
runResourceT $ sourceHandle remoteH $$ sinkFile filename
如果您希望使用network-conduit等等,您也可以完全绕过Handle
抽象:
runResourceT $ sourceSocket socket $$ sinkFile filename