2012-04-09 3 views
7

Update: Mr. Nemos Antwort half, das Problem zu lösen! Der folgende Code enthält den Fix! Siehe die folgenden Anrufe nb False und nb True.Verwendung von GNU/Linux-Systemaufruf `splice` für zero-copy Socket-Socket-Datenübertragungen in Haskell

Es gibt auch ein neues Paket Haskell genannt splice (, das hat OS-spezifische und portable Implementierungen bekanntesten Buchse Datenübertragung an die Buchse Schleifen) .

Ich habe folgendes (Haskell) Code:

#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 

Hinweis:Der obige Code jetzt funktioniert einfach! Unten ist nicht mehr gültig dank Nemo!

I splice nennen, wie oben definiert, mit zwei offenen und verbundenen Buchsen (die bereits verwendet werden, eine minimale Menge an Handshake-Daten entweder die Buchsen API send und recv Anrufe oder umgewandelt Griffen und mit hGetLine und hPut verwendet verwendet zu übertragen), und ich erhalte:

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

am ersten c_splice Aufrufort: c_splice kehrt -1 und einige errno auf einen Wert setzt (wahrscheinlich EAGAIN), die resource exhausted | resource temporarily unavailable liest wh en blickte auf.

Ich testete Aufruf splice mit verschiedenen Length Werte: 1024, 8192.

+1

Ihre aktuelle Version erstellt bei jedem Aufruf von splice() eine neue Pipe. Dies ist in Ordnung, wenn Sie immer große Blöcke verschieben, aber für kleine Blöcke, die einen großen Aufwand verursachen können. Normalerweise erstelle ich ein "Splicer" -Objekt, um die Pipe zu besitzen, und rufe es dann wiederholt mit + von Deskriptoren auf, um die Daten zu verschieben. – Nemo

+0

@Nemo 'splice' (nicht' c_splice') ist eigentlich eine Endlosschleife wegen 'forever'. Ich denke, ich sollte "splice" in etwas wie "loopSplice" umbenennen, um das klarzustellen. Daher erstellt es derzeit pro Pipe pro Proxy-Verbindung nicht pro 'c_splice'-Aufruf eine Pipe. –

+0

Ich habe noch eine Menge zu testen mit der portablen Implementierung unter Windows, aber ich werde definitiv genug Zeit haben, um über einen besseren Namen nachzudenken. Öffnen Sie für Ihre Vorschläge auch :) –

Antwort

12

Ich weiß nicht Haskell, aber "Ressource vorübergehend nicht verfügbar" ist EAGAIN.

Und es sieht wie Haskell sets its sockets to non-blocking mode standardmäßig aus. Wenn Sie also versuchen, von einem zu lesen, wenn keine Daten vorhanden sind, oder wenn Sie versuchen, auf einen zu schreiben, wenn der Puffer voll ist, werden Sie mit EAGAIN fehlschlagen.

Finden Sie heraus, wie Sie die Sockets in den Blockiermodus ändern, und ich wette, Sie werden Ihr Problem lösen.

[update]

Alternativ rufen select oder poll, bevor Sie den Sockel lesen oder zu schreiben. Aber Sie müssen immer noch mit EAGAIN umgehen, weil es seltene Fälle gibt, in denen Linux select anzeigt, dass ein Socket bereit ist, wenn es nicht wirklich ist.

+0

Danke für den Hinweis! Ich bin dran zu sehen, wie weit es hilft :) –

+2

Wow, du kennst Haskell noch nicht, du kannst die genaue Linie lokalisieren, um mein Problem zu lösen, tolle Antwort !! –

+0

Es wurde der nicht blockierende Modus entsprechend um die "Spleiß" -Anrufe eingestellt, die das Problem lösten. Ich bin wirklich dankbar für deine klassische Antwort. :) –

0

Würde der sendfile() syscall für Sie arbeiten? Wenn ja, können Sie die sendfile package verwenden.

+1

Danke. Demnach: http://kerneltrap.org/node/6505 'splice' ist der richtige Weg, wenn beide Seiten Sockets sind. Liege ich falsch? Btw Nemos Antwort löste das Problem für mich, also werde ich bei meiner Implementierung von 'Splice' bleiben! –

+0

Der auskommentierte Abschnitt in meinem Code enthält ein portables Stück Benutzer-Space-Code mit 'mallocBytes',' hGetBufSome' und 'hPut', die besser sind als' network-bytestring's 'recv',' sendAll'. Ich werde es aufpolieren und es früh genug auf Hackage stellen. Ich arbeite an einer leistungsstarken, extrem einfachen, kleinen und sauberen Proxy-Anwendung und eines seiner Designprinzipien lautet: ** nicht auf externe Pakete angewiesen, es sei denn, absolut notwendig ** und obwohl 'sendfile' Abhängigkeitsliste ist auch viel Im Vergleich zu den meisten anderen Paketen habe ich gesehen, dass rivalisierende Proxy-Software auch 'Splice' verwendet. :) –

+1

'sendfile' sendet eine Datei. Es funktioniert nicht, wenn die Quelle keine Datei ist oder wenn das Ziel kein Socket ist. 'splice' ist, was Sie für Datei-zu-Datei- oder Socket-zu-Socket-Zero-Copy benötigen. (Obwohl es normalerweise nicht wirklich Null-Kopie ist ... lange Geschichte) – Nemo