2012-04-09 10 views
7

Güncelleme: Bay Nemo'nun cevabı sorunun çözülmesine yardımcı oldu! Aşağıdaki kod düzeltmeyi içerir! Aşağıdaki nb False ve nb True çağrılarına bakın.GNU/Linux sistem çağrısını kullanarak sıfır kopyalı soket için 'splice' çağrısı Soket'e veri aktarımı Soket'e veri aktarımı

da (veri aktarımı soket en iyi bilinen soketin OS özgü ve taşınabilir uygulamaları halkaya)splice adlı yeni bir Haskell paket bulunmaktadır.

aşağıdaki (Haskell) kodu vardır:

#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 

Not: şimdi yukarıdakod sadece çalışıyor! Aşağıda Nemo! (Daha önce yuva API send ve recv çağrı kullanarak el sıkışma veri az miktarda iletimi için kullanılan ya da kolları dönüştürülür ve hGetLine ve hPut kullanılır) iki açık ve bağlı soketleri ile yukarıda tanımlandığı gibi splice çağrı

ve ilk c_splice çağrı sitesinde

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

: Ben almaya devam c_splice getiriler -1 ve wh resource exhausted | resource temporarily unavailable okuyan bir değere (muhtemelen EAGAIN) bazı errno setleri en yukarı baktı.

Farklı Length değerleriyle splice numaralı telefonu aramayı denedim: 1024, 8192.

+1

Geçerli sürümünüz, splice() öğesini her aradığınızda yeni bir boru oluşturur. Her zaman büyük blokları hareket ettirirseniz, ancak büyük bir yüke maruz kalabilecek küçük bloklar için bu tamamdır. Genellikle, borunun sahibi olmak için bir "Splicer" nesnesi oluşturur, sonra verileri taşımak için + ile tanımlayıcıları tekrar tekrar çağırır. – Nemo

+0

@Nemo 'splice' (' c_splice' değil) aslında sonsuza dek sonsuz bir döngüdür. Kesinleştirmek için 'splice'ı' loopSplice 'gibi bir şeye yeniden adlandırmalıyım. Yani şu anda her bir 'c_splice' çağrısı başına değil vekil başına bir boru oluşturur. –

+0

Windows üzerinde taşınabilir uygulama ile test etmek için hala çok şey var, bu yüzden kesinlikle daha iyi bir isim üzerinde düşünmek için yeterli zamana sahip olacağım. Önerileriniz için de açık :) –

cevap

12

Haskell'i bilmiyorum, ancak "kaynak geçici olarak kullanılamıyor" EAGAIN.

Ve varsayılan olarak Haskell sets its sockets to non-blocking mode gibi görünüyor. Bu nedenle, veri yokken birinden okumaya veya arabelleği dolduğunda bir tane yazmaya çalışmayı denerseniz, EAGAIN ile başarısız olursunuz.

Soketlerin engelleme moduna nasıl geçeceğini anladım ve bahse girerim, sorununuzu çözeceksiniz.

[güncelleme]

Alternatif okumak veya soket yazmaya başlamadan önce select veya poll arayın. Ama hala EAGAIN'u kullanmanız gerekiyor, çünkü Linux select'un aslında olmadığı zaman bir soketin hazır olduğunu göstereceği nadir köşe durumları var.

+0

İpucu için teşekkürler! Ben ne kadar yardımcı olduğunu görmeye devam ediyorum :) –

+2

Haskell henüz bilmiyorum Vay canına, benim sorunumu çözmeye yardımcı olmak için tam çizgiyi kesin, harika cevap! –

+0

Engelleme modunu, sorunu çözen 'ekleme' çağrıları etrafında uygun şekilde ayarlıyordu. Senin klas cevabın için gerçekten müteşekkirim. :) –

0

sendfile() syscall sizin için çalışır mı? Eğer öyleyse sendfile package'u kullanabilirsiniz.

+1

Teşekkürler. Buna göre: http://kerneltrap.org/node/6505 'splice ', her iki taraf da prizlerse gitmek için doğru yoldur. Yanlış mıyım? Btw Nemo'nun cevabı benim için problemi çözdü, bu yüzden 'splice' uygulamasına bağlı kalacağım! –

+0

Kodumdaki yorumlanmış bölüm, 'mallocBytes', 'hGetBufSome' ve' hPut' kullanarak, ağ-bytring'in 'recv',' sendAll''den de daha iyi performans gösteren taşınabilir bir kullanıcı alanı kodu içeriyor. Onu cilalayacağım ve çok geçmeden Hackage'a koyacağım. Yüksek performanslı, son derece basit, küçük ve temiz bir proxy uygulaması üzerinde çalışıyorum ve tasarım ilkelerinden biri: ** kesinlikle gerekli olmadıkça harici paketlere bağlı değil ** ve sendfile bağımlılık listesi de çok Diğer paketlerin çoğuna kıyasla temiz, ben de 'splice' kullanarak rakip vekil yazılım gördük. :) –

+1

'sendfile' bir dosya gönderir. Kaynak bir dosya değilse veya hedef bir soket değilse işe yaramaz. 'ekleme' dosyadan dosyaya veya yuvadan sokete sıfır kopya için ihtiyacınız olan şeydir. (Genellikle sıfır kopyalı olmasa da ... uzun hikaye) – Nemo

İlgili konular