2012-04-09 7 views
7

업데이트 : Mr. Nemo의 대답이 문제를 해결하는 데 도움이되었습니다. 아래 코드에는 수정 사항이 포함되어 있습니다! 아래 전화 번호는 nb Falsenb True입니다.GNU/Linux 시스템 호출을 사용하여 Haskell에서 Socket to Socket 데이터 전송을위한 'splice'호출

또한 (데이터 전송을 위해 잘 알려진 소켓 소켓 OS 특정 휴대용 구현 루프를 가지고)splice라는 새로운 하스켈 패키지가있다.

나는 다음 (하스켈) 코드를 가지고 :

#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에게 더 이상 유효하지 않습니다! (이미 소켓 API sendrecv 호출을 사용하여 데이터 교환 데이터의 최소량을 전송하는데 사용되거나 핸들로 변환 hGetLinehPut 함께 사용되는) 두 개방형 접속 소켓과 상기 정의 된 바와 같은 I가 splice 전화

및 첫 번째 c_splice 호출 사이트에서

Network.Socket.Splice.splice: resource exhausted (Resource temporarily unavailable) 

: 나는 점점 계속 c_splice 반환 -1와 어 resource exhausted | resource temporarily unavailable를 읽고 값 (아마 EAGAIN) 일부 errno을 설정 엉 위로 보았다.

다른 Length 값을 가진 splice을 호출하여 테스트했습니다. 1024, 8192.

+1

현재 버전에서는 splice()를 호출 할 때마다 새 파이프가 만들어집니다. 대용량 블록을 항상 움직이는 경우에는 큰 문제가 아니지만 작은 블록 일 경우에는 문제가 없습니다. 보통 파이프를 소유하기 위해 "Splicer"개체를 만든 다음 데이터를 이동시키기 위해 +에서 설명자로 반복적으로 호출합니다. – Nemo

+0

@nemo'splice' ('c_splice'가 아니라)는 실제로'영원히 '때문에 무한 루프입니다. 루프 스플 라이스 (loopSplice)와 같은 이름으로 스플 라이스 (splice) 이름을 변경하여 명확하게해야한다고 생각합니다. 그래서 현재는 각'c_splice' 호출마다가 아닌 프록시 연결마다 하나의 파이프를 생성합니다. –

+0

Windows에서 이식 가능한 구현을 테스트하기에는 여전히 많은 부분이 있지만 그래도 더 나은 이름을 생각하는 데 충분한 시간을 할애 할 것입니다. 당신의 제안을 위해서도 열어 둡니다. –

답변

12

하스켈에 대해 모르겠지만 "자원 일시적으로 사용할 수 없음"은 EAGAIN입니다.

기본적으로 Haskell sets its sockets to non-blocking mode처럼 보입니다. 따라서 데이터가 없을 때 읽으 려하거나 버퍼가 꽉 차있을 때 쓰기를 시도하면 EAGAIN으로 실패합니다.

소켓을 블로킹 모드로 변경하는 방법을 알아 내면 문제를 해결할 수 있습니다.

[업데이트]

는 다른 방법으로, 읽거나 소켓을 작성하기 전에 select 또는 poll를 호출합니다. 그러나 EAGAIN을 처리해야합니다. 희소 한 경우가 있기 때문에 select은 실제로 소켓이 준비되지 않았을 때 소켓이 준비되었음을 나타냅니다.

+0

힌트를 보내 주셔서 감사합니다! 나는 그것이 얼마나 도움이되는지를 보러 간다. –

+2

와우, 하스켈을 알지 못하지만, 내 문제를 해결하는 데 도움이되는 정확한 라인을 찾아 낼 수있다. –

+0

문제를 해결 한'splice' 호출 주위에 비 차단 모드를 적절히 설정했습니다. 당신의 고상한 대답에 진심으로 감사드립니다. :) –

0

sendfile() syscall이 도움이 될까요? 그렇다면 sendfile package을 사용할 수 있습니다.

+1

고마워요. 이것에 따르면 : http://kerneltrap.org/node/6505'splice'는 양쪽이 소켓 인 경우 갈 수있는 올바른 방법입니다. 내가 잘못? Btw Nemo의 대답은 나를 위해 문제를 해결했습니다. 그래서 나는'splice' 구현에 충실합니다! –

+0

내 코드의 주석 처리 된 섹션에는'network-bytestring'의'recv','sendAll'보다 나은'mallocBytes','hGetBufSome' 및'hPut'을 사용하는 휴대용 사용자 공간 코드가 들어 있습니다. 나는 그것을 닦아서 곧 충분히 Hackage에 올릴 것이다. 나는 고성능, 매우 간단하고 작고 깨끗한 프록시 애플리케이션을 개발 중이다. 디자인 원칙 중 하나는 다음과 같다 : ** 절대적으로 필요한 경우가 아니면 ** 외부 패키지에 의존하지 말라. ** 또한'sendfile'의 의존성 목록도 역시 그렇지만 다른 대부분의 패키지에 비해 깨끗한, 나는 또한'splice'를 사용하여 경쟁 프록시 소프트웨어를 보았다. :) –

+1

'sendfile'은 파일을 보냅니다. 소스가 파일이 아니거나 대상이 소켓이 아닌 경우에는 작동하지 않습니다. 'splice '는 파일 - 투 - 파일 또는 소켓 - 투 - 소켓 제로 - 복사에 필요한 것입니다. (보통은 제로 카피는 아니지만 ... 긴 이야기) – Nemo