【发布时间】:2012-04-22 06:23:45
【问题描述】:
更新:尼莫先生的回答帮助解决了这个问题!下面的代码包含修复!请参阅下面的 nb False 和 nb True 调用。
还有一个名为 splice 的新 Haskell 包(它具有最知名的套接字到套接字数据传输循环的操作系统特定和可移植实现)。
我有以下(Haskell)代码:
#ifdef LINUX_SPLICE
#include <fcntl.h>
{-# LANGUAGE CPP #-}
{-# LANGUAGE ForeignFunctionInterface #-}
#endif
module Network.Socket.Splice (
Length
, zeroCopy
, splice
#ifdef LINUX_SPLICE
, c_splice
#endif
) where
import Data.Word
import Foreign.Ptr
import Network.Socket
import Control.Monad
import Control.Exception
import System.Posix.Types
import System.Posix.IO
#ifdef LINUX_SPLICE
import Data.Int
import Data.Bits
import Unsafe.Coerce
import Foreign.C.Types
import Foreign.C.Error
import System.Posix.Internals
#else
import System.IO
import Foreign.Marshal.Alloc
#endif
zeroCopy :: Bool
zeroCopy =
#ifdef LINUX_SPLICE
True
#else
False
#endif
type Length =
#ifdef LINUX_SPLICE
(#type size_t)
#else
Int
#endif
-- | The 'splice' function pipes data from
-- one socket to another in a loop.
-- On Linux this happens in kernel space with
-- zero copying between kernel and user spaces.
-- On other operating systems, a portable
-- implementation utilizes a user space buffer
-- allocated with 'mallocBytes'; 'hGetBufSome'
-- and 'hPut' are then used to avoid repeated
-- tiny allocations as would happen with 'recv'
-- 'sendAll' calls from the 'bytestring' package.
splice :: Length -> Socket -> Socket -> IO ()
splice l (MkSocket x _ _ _ _) (MkSocket y _ _ _ _) = do
let e = error "splice ended"
#ifdef LINUX_SPLICE
(r,w) <- createPipe
print ('+',r,w)
let s = Fd x -- source
let t = Fd y -- target
let c = throwErrnoIfMinus1 "Network.Socket.Splice.splice"
let u = unsafeCoerce :: (#type ssize_t) -> (#type size_t)
let fs = sPLICE_F_MOVE .|. sPLICE_F_MORE
let nb v = do setNonBlockingFD x v
setNonBlockingFD y v
nb False
finally
(forever $ do
b <- c $ c_splice s nullPtr w nullPtr l fs
if b > 0
then c_splice r nullPtr t nullPtr (u b) fs)
else e
(do closeFd r
closeFd w
nb True
print ('-',r,w))
#else
-- ..
#endif
#ifdef LINUX_SPLICE
-- SPLICE
-- fcntl.h
-- ssize_t splice(
-- int fd_in,
-- loff_t* off_in,
-- int fd_out,
-- loff_t* off_out,
-- size_t len,
-- unsigned int flags
-- );
foreign import ccall "splice"
c_splice
:: Fd
-> Ptr (#type loff_t)
-> Fd
-> Ptr (#type loff_t)
-> (#type size_t)
-> Word
-> IO (#type ssize_t)
sPLICE_F_MOVE :: Word
sPLICE_F_MOVE = (#const "SPLICE_F_MOVE")
sPLICE_F_MORE :: Word
sPLICE_F_MORE = (#const "SPLICE_F_MORE")
#endif
注意: 上面的代码现在可以正常工作了!感谢 Nemo,下面的代码不再有效!
我调用splice 上面定义的两个打开和连接的套接字(它们已经用于使用套接字API send 和recv 调用传输最少量的握手数据,或者转换为句柄并与@ 一起使用987654329@ 和 hPut) 我不断收到:
Network.Socket.Splice.splice: resource exhausted (Resource temporarily unavailable)
在第一个c_splice 调用站点:c_splice 返回-1 并将一些errno 设置为一个值(可能是EAGAIN),当查找时读取为resource exhausted | resource temporarily unavailable。
我测试了使用不同的Length 值调用splice:1024、8192。
【问题讨论】:
-
每次调用 splice() 时,您的当前版本都会创建一个新管道。如果您总是移动大块,这是可以的,但对于可能会带来很大开销的小块。我通常创建一个“Splicer”对象来拥有管道,然后使用 from+to 描述符重复调用它来移动数据。
-
@Nemo
splice(不是c_splice)实际上是由于forever而导致的无限循环。我想我应该将splice重命名为loopSplice以明确这一点。所以目前它为每个代理连接创建一个管道,而不是每个c_splice调用。 -
啊...告诉你我实际上并不了解 Haskell :-)。 (搜索“Haskell non-blocking socket”让我找到了相关来源。)哦,在我可以用函数式语言编码的日子里......
-
@CetinSert 谢谢你的包,它肯定会有用。对于我们不熟悉这个内核特性的人,你能解释一下如何用它做一个 HTTP 1.1 反向代理吗?此示例需要读取和解释用户空间中不同长度的标头,然后将套接字拼接到套接字以获得有限的字节数,然后在用户空间中取回控制权以进行下一个请求。这可能吗?
-
@CetinSert 啊!我确实没有看正确的模块。 splice_c 具有正确的 API 来将用户空间逻辑与内核空间字节复制交错。谢谢你提供这个包,我希望我有时间玩它。
标签: linux sockets haskell network-programming splice