【问题标题】:Create a C-level file handle in RCurl for writing downloaded files在 RCurl 中创建 C 级文件句柄,用于写入下载的文件
【发布时间】:2023-06-04 03:23:01
【问题描述】:

在 RCurl 中定义了一个函数和一个类 CFILE 以使用 C 级文件句柄。来自手册:

目的是能够将这些作为选项传递给 libcurl,以便它可以读取或写入文件。我们也可以使用 R 连接来执行此操作,并指定操作这些连接的回调函数。但是对于大文件,使用 C 级 FILE 句柄可能会明显更快。

没有与下载相关的示例,所以我尝试了:

library(RCurl)
u = "http://cran.r-project.org/web/packages/RCurl/RCurl.pdf"
f = CFILE("RCurl.pdf", mode="wb")
ret= getURL(u,  write = getNativeSymbolInfo("R_curl_write_binary_data")$address,
                file  = f@ref)

我还尝试将file 选项替换为writedata = f@ref。 文件已下载,但已损坏。 为write 参数编写自定义回调仅适用于非二进制数据。

有没有办法在 RCurl 中将二进制文件直接下载到磁盘(而不将其加载到内存中)?

【问题讨论】:

    标签: r curl rcurl


    【解决方案1】:

    我想你想使用writedata 并记得关闭文件

    library(RCurl)
    filename <- tempfile()
    f <- CFILE(filename, "wb")
    url <- "http://cran.fhcrc.org/Rlogo.jpg"
    curlPerform(url = url, writedata = f@ref)
    close(f)
    

    对于更详细的写作,我不确定这是否是最好的方式,但 Linux 告诉我,来自

    man curl_easy_setopt
    

    有一个 curl 选项 CURL_WRITEFUNCTION,它是一个指向带有原型的 C 函数的指针

    size_t function(void *ptr, size_t  size, size_t nmemb, void *stream);
    

    在 ?curlPerform 末尾的 R 中有一个调用 C 函数作为“writefunction”选项的示例。所以我创建了一个文件 curl_writer.c

    #include <stdio.h>
    
    size_t
    writer(void *buffer, size_t size, size_t nmemb, void *stream)
    {
        fprintf(stderr, "<writer> size = %d, nmemb = %d\n",
                (int) size, (int) nmemb);
        return size * nmemb;
    }
    

    编译好了

    R CMD SHLIB curl_writer.c
    

    在 Linux 上产生一个文件 curl_writer.so,然后在 R

    dyn.load("curl_writer.so")
    writer <- getNativeSymbolInfo("writer", PACKAGE="curl_writer")$address
    curlPerform(URL=url, writefunction=writer)
    

    进入标准错误

    <writer> size = 1, nmemb = 2653
    <writer> size = 1, nmemb = 520
    OK 
    

    这两个想法可以整合,即使用任意函数写入任意文件,通过修改C函数使用我们传入的FILE *,如

    #include <stdio.h>
    
    size_t
    writer(void *buffer, size_t size, size_t nmemb, void *stream)
    {
        FILE *fout = (FILE *) stream;
        fprintf(fout, "<writer> size = %d, nmemb = %d\n",
                (int) size, (int) nmemb);
        fflush(fout);
        return size * nmemb;
    }
    

    编译后返回R中

    dyn.load("curl_writer.so")
    writer <- getNativeSymbolInfo("writer", PACKAGE="curl_writer")$address
    f <- CFILE(filename <- tempfile(), "wb")
    curlPerform(URL=url, writedata=f@ref, writefunction=writer)
    close(f)
    

    getURL也可以在这里使用,前提是writedata=f@ref, write=writer;我认为原始问题中的问题是R_curl_write_binary_data 确实是一个内部函数,写入由 RCurl 管理的缓冲区,而不是像CFILE 创建的文件句柄。同样,指定 writedata 而不指定 write (从源代码看来 getURL 是 writefunction 的别名)将指向文件的指针发送到期望指向其他指针的函数;对于 getURL,writedata 和 write 都需要提供。

    【讨论】:

    • 谢谢。正如我所写,我尝试过getURL(url = url, writedata = f@ref),但它不起作用。所以看起来只有listCurlOptions()中的参数子集实际上可以传递给getURL。有些只被curlPerform 接受。我认为手册中没有提到这一点。
    • @antonio 通过查看getURL 和RCurl 源代码,默认参数write 不适用于自定义文件,并且R_curl_write_binary_data 正在对内部数据结构而不是文件句柄进行操作;提供writewritedata 参数就足够了,我认为使用getURL。
    • 如您所说,必须查看源代码。手册中的更多提示可能会有所帮助。
    【解决方案2】:

    我也在研究这个问题,但还没有答案。

    但是,我确实找到了这个:

    http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTWRITEDATA

    你是在 Windows 下使用 R 吗?我是。

    该 writedata 函数的文档表明,在 Windows 上,您必须将 writefunction 与 writedata 一起使用。

    在这里阅读:http://www.omegahat.org/RCurl/RCurlJSS.pdf 我发现 RCurl 期望 writefunction 是一个 R 函数,所以我们可以自己在 windows 上实现它。它会比使用 C 函数写入数据要慢,但我敢打赌,网络链接的速度将是瓶颈。

    getURI(url="sftp://hostname/home/me/onegeebee", curl=con, write=function(x) writeChar(x, f, eos=NULL))
    Error in curlPerform(curl = curl, .opts = opts, .encoding = .encoding) : embedded nul in string: ' <`á\017_\021
    

    (这是在服务器上创建一个1GB的文件来测试传输速度之后)

    我还没有找到不会阻塞数据中 NUL 字节的答案。似乎在 RCurl 包内部的某个地方,当它将数据向上传递到 R 以执行您提供的 writefunction 时,它会尝试将数据转换为字符串。如果您使用 C 函数,则不能这样做。值得注意的是,每次使用推荐的 R_curl_write_binary_data 回调和 CFILE 都会杀死 win32 上的 rsession.exe。

    【讨论】: