当前位置: 首页 > 面试题库 >

在Haskell中使用GNU / Linux系统调用`splice`进行零拷贝Socket到Socket的数据传输

沈华皓
2023-03-14
问题内容

更新:尼莫先生的回答帮助解决了这个问题! 下面的代码包含修复程序!请参见下面的nb Falsenb True呼叫。

还有一个称为的新Haskell软件包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
sendrecv调用将它们用于传输最小量的握手数据,或者已转换为句柄并与hGetLine和一起使用hPut),并且不断得到:

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

在第一个c_splice呼叫站点:c_splice返回-1并将其设置errno为一个值(可能是EAGAIN),该值resource exhausted | resource temporarily unavailable在查找时会读取。

我测试了splice使用不同Length值的呼叫:10248192


问题答案:

我不知道Haskell,但是“资源暂时不可用”是EAGAIN

看起来Haskell默认将其套接字设置为非阻塞模式。因此,如果您在没有数据的情况下尝试从其中一个读取数据,或者在缓冲区中的数据已满时尝试向其中之一进行写入操作,则会失败EAGAIN

弄清楚如何将套接字更改为阻塞模式,我敢打赌,您将解决您的问题。

[更新]

或者,在尝试读取或写入套接字之前,先调用selectpoll。但是,您仍然需要处理EAGAIN,因为在极少数情况下,Linux
select会指示套接字已准备就绪,而实际上尚未就绪。



 类似资料:
  • 前面依次讲解了三个逐渐进步的服务器: 只能服务于一个客户端的服务器 利用 fork 可以服务于多个客户端的额服务器 利用预 fork 派生进程服务于多个客户端的服务器 最后一种服务器的进程模型基本上的大概原理其实跟我们常用的 apache 是非常相似的。 其实这种模型最大的问题在于需要根据实际业务预估进程数量,依旧是需要大量进程来解决问题,可能会出现CPU浪费在进程间切换上,还有可能会出现惊群现象

  • 问题内容: 您可以使用Spring 5 WebFlux执行零拷贝上传和下载吗? 问题答案: 是的,从基于File的文件发布数据时,现在支持零拷贝。 因此,以下内容看起来正确: 现在,对于阅读部分,Spring Framework中的阅读侧目前不支持零复制;您可以为此在jira.spring.io上创建一个增强问题。 您的代码示例应如下所示: 不幸的是,读取数据不会进行零复制,因为数据将被复制到内存

  • 问题内容: 我使用的是大猩猩网络套接字,我想在本地运行它,是指使用以下chrome客户端或其他推荐的工具……当我进入调试模式时,出现错误 我用 当我在Chrome或网络套接字客户端中运行以下网址时,出现错误 websocket:不是websocket握手:在“连接”标头中找不到“升级”令牌 我想运行它 并为本地模拟提供令牌,我该怎么做? 要检查它,我使用Chrome的Simple WebSocke

  • 目前为止,我们讨论的大多数是高阶概念。 Haskell 也可以用于底层系统编程。完全可以使用 Haskell 编写使用操作系统底层接口的程序。 本章中,我们将尝试一些很有野心的东西:编写一种类似 Perl 实际上是合法的 Haskell 的“语言”,完全使用 Haskell 实现,用于简化编写 shell 脚本。我们将实现管道,简单命令调用,和一些简单的工具用于执行由 grep 和 sed 处理的

  • 系统调用 我们要想启动一个进程,需要操作系统的调用(system call)。实际上操作系统和普通进程是运行在不同空间上的,操作系统进程运行在内核态(todo: kernel space),开发者运行的进程运行在用户态(todo: user space),这样有效规避了用户程序破坏系统的可能。 如果用户态进程想执行内核态的操作,只能通过系统调用了。Linux提供了超多系统调用函数,我们关注与进程相

  • 本文向大家介绍详解Android使用Socket对大文件进行加密传输,包括了详解Android使用Socket对大文件进行加密传输的使用技巧和注意事项,需要的朋友参考一下 前言 数据加密,是一门历史悠久的技术,指通过加密算法和加密密钥将明文转变为密文,而解密则是通过解密算法和解密密钥将密文恢复为明文。它的核心是密码学。 数据加密目前仍是计算机系统对信息进行保护的一种最可靠的办法。它利用密码技术对信