2012-04-09 9 views
7

Aggiornamento: la risposta di Mr. Nemo ha aiutato a risolvere il problema! Il codice qui sotto contiene la correzione! Vedere le chiamate nb False e nb True di seguito.Utilizzo della chiamata di sistema GNU/Linux `splice` per copia da zero a socket Socket data transfer in Haskell

C'è anche un nuovo pacchetto Haskell chiamato splice (che ha implementazioni OS-specifici e portatili di presa più noto per presa trasferimento dati loops) .

Ho il seguente (Haskell) Codice:

#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 

Nota:Il codice di cui sopra ora funziona! Qui sotto non è più valido grazie a Nemo!

chiamo splice come sopra definito con due socket aperti e connessi (che sono già utilizzati per trasmettere quantità minima di dati handshake utilizzando sia il socket API send e recv chiamate o convertiti in maniglie e utilizzati con hGetLine e hPut) e Continuo a ricevere:

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

al primo sito c_splice chiamata: c_splice rendimenti -1 e imposta alcune errno ad un valore (probabilmente EAGAIN) che legge resource exhausted | resource temporarily unavailable wh it alzò gli occhi.

Ho provato a chiamare splice con diversi valori Length: 1024, 8192.

+1

La versione corrente crea una nuova pipe ogni volta che si chiama splice(). Questo è OK se si spostano sempre blocchi di grandi dimensioni, ma per piccoli blocchi che possono imporre un grande sovraccarico. Generalmente creo un oggetto "Splicer" per possedere il pipe, quindi lo chiamo ripetutamente da + a descrittori per spostare i dati. – Nemo

+0

@Nemo 'splice' (non' c_splice') è in realtà un ciclo infinito dovuto a 'per sempre'. Credo che dovrei rinominare 'splice' in qualcosa come' loopSplice' per renderlo chiaro. Così attualmente crea una pipe per ogni connessione proxy non per ogni chiamata 'c_splice'. –

+0

Ho ancora molto da testare con l'implementazione portatile su Windows, quindi avrò sicuramente abbastanza tempo per pensare a un nome migliore. Aperto anche per i tuoi suggerimenti :) –

risposta

12

Non conosco Haskell, ma "risorsa temporaneamente non disponibile" è EAGAIN.

e sembra che Haskell sets its sockets to non-blocking mode per impostazione predefinita. Quindi se provi a leggere da uno quando non ci sono dati, o prova a scrivere su uno quando il suo buffer è pieno, fallirai con EAGAIN.

Capire come modificare le prese di modalità di blocco, e scommetto che sarà risolvere il tuo problema.

[aggiornamento]

alternativa, chiamare select o poll prima di tentare di leggere o scrivere la presa. Ma hai ancora bisogno di gestire EAGAIN, perché ci sono rari casi d'angolo in cui Linux select indica che un socket è pronto quando in realtà non lo è.

+0

Grazie per il suggerimento! Sto vedendo fino a che punto aiuta :) –

+2

Wow non sai Haskell, ma puoi individuare la linea esatta per risolvere il mio problema, risposta sorprendente !! –

+0

Stava impostando la modalità non bloccante in modo appropriato attorno alle chiamate 'splice' che risolvevano il problema. Sono veramente grato per la tua risposta di classe. :) –

0

Il syscall sendfile() sarebbe adatto a voi? In tal caso, è possibile utilizzare sendfile package.

+1

Grazie. In base a questo: http://kerneltrap.org/node/6505 'splice' è il modo giusto per andare se entrambi i lati sono socket. Ho sbagliato? La risposta di Btw Nemo ha risolto il problema per me quindi mi limiterò alla mia implementazione di 'splice'! –

+0

La sezione commentata nel mio codice contiene un pezzo portatile di codice spaziale utente usando 'mallocBytes',' hGetBufSome' e 'hPut' che funzionano meglio di' network-bytestring' anche 'recv',' sendAll'. Lo luciderò e lo metto su Hackage abbastanza presto. Sto lavorando a un'applicazione proxy ad alte prestazioni, estremamente semplice, piccola e pulita e uno dei suoi principi di progettazione è: ** non dipendere da pacchetti esterni a meno che non sia assolutamente necessario ** e anche se l'elenco delle dipendenze di 'sendfile' è troppo pulito rispetto alla maggior parte degli altri pacchetti, ho visto il software proxy rivale usando anche 'splice'. :) –

+1

'sendfile' invia un file. Non funziona se l'origine non è un file o se la destinazione non è un socket. 'splice' è quello che ti serve per file-to-file o socket-to-socket zero-copy. (Anche se di solito non è davvero una copia zero ... lunga storia) – Nemo