更新:尼莫先生的回答帮助解决了这个问题! 下面的代码包含修复程序!请参见下面的nb False和nb True呼叫。
nb False
nb True
还有一个称为的新Haskell软件包splice(具有最著名的套接字到套接字数据传输循环的特定于操作系统的可移植实现) 。
splice
我有以下(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调用将它们用于传输最小量的握手数据,或者已转换为句柄并与hGetLine和一起使用hPut),并且不断得到:
send
recv
hGetLine
hPut
Network.Socket.Splice.splice: resource exhausted (Resource temporarily unavailable)
在第一个c_splice呼叫站点:c_splice返回-1并将其设置errno为一个值(可能是EAGAIN),该值resource exhausted | resource temporarily unavailable在查找时会读取。
c_splice
-1
errno
EAGAIN
resource exhausted | resource temporarily unavailable
我测试了splice使用不同Length值的呼叫:1024,8192。
Length
1024
8192
我不知道Haskell,但是“资源暂时不可用”是EAGAIN。
看起来Haskell默认将其套接字设置为非阻塞模式。因此,如果您在没有数据的情况下尝试从其中一个读取数据,或者在缓冲区中的数据已满时尝试向其中之一进行写入操作,则会失败EAGAIN。
弄清楚如何将套接字更改为阻塞模式,我敢打赌,您将解决您的问题。
[更新]
或者,在尝试读取或写入套接字之前,先调用select或poll。但是,您仍然需要处理EAGAIN,因为在极少数情况下,Linux select会指示套接字已准备就绪,而实际上尚未就绪。
select
poll