使用linux

时间:2017-02-10 09:39:17

标签: c linux api

我一直在学习linux中的系统编程,我尝试使用read()和write()来复制视频。我面临的问题是我无法将整个文件保存到缓冲区中,因为它是一个巨大的文件。

我以为我可以循环使用,因为我使用带有追加标志的写入但是那么我将如何使用它与读取?

这是我乱糟糟的代码。我将不胜感激任何帮助:

int main() {

    int movie_rdfd = open("Suits.mp4", O_RDONLY); //fd for read
    off_t file_length = (int)(fseek(movie_rdfd, 0, SEEK_END));

    printf("This is fd for open: %d\n", movie_rdfd); //line to be deleted
    char* Save[fseek(movie_rdfd, 0, SEEK_END)];

    int C1 = read(movie_rdfd, Save, );

    printf("Result of Read (C1): %d\n", C1); //line to be deleted

    int movie_wrfd = open("Suits_Copy.mp4", O_WRONLY|O_CREAT, 0644); //fd for write

    printf("This is result of open: %d\n", movie_wrfd); //line to be deleted

    int C2 = write(movie_wrfd, Save, fseek(movie_rdfd, 0, SEEK_END));

    printf("Result of Read (C2): %d\n", C2); //line to be deleted

    close(movie_rdfd);
    close(movie_wrfd);

    return 0;
}

当我尝试查找文件大小时,它还显示分段错误

2 个答案:

答案 0 :(得分:4)

在POSIX.1系统(包括Linux)中复制文件的正确逻辑大致是

Open source file
Open target file
Repeat:
    Read a chunk of data from source
    Write that chunk to target
Until no more data to read
Close source file
Close target file

正确的错误处理会增加大量的代码,但我认为这是必要的,如果有时间的话,这不是一个可选的事情。

(我对此非常严格,即使他们的程序运行正常,我也会失败任何忽略错误检查的人。原因是基本的理智:可能在你手中炸毁的工具不是工具,这是一个炸弹。软件世界里已经有足够的炸弹,我们不需要更多的“程序员”来创造这些炸弹。我们需要的是可靠的工具。)

以下是一个具有正确错误检查的示例实现:

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#define  DEFAULT_CHUNK  262144  /* 256k */

int copy_file(const char *target, const char *source, const size_t chunk)
{
    const size_t size = (chunk > 0) ? chunk : DEFAULT_CHUNK;
    char        *data, *ptr, *end;
    ssize_t      bytes;
    int          ifd, ofd, err;

    /* NULL and empty file names are invalid. */
    if (!target || !*target || !source || !*source)
        return EINVAL;

    ifd = open(source, O_RDONLY);
    if (ifd == -1)
        return errno;

    /* Create output file; fail if it exists (O_EXCL): */
    ofd = open(target, O_WRONLY | O_CREAT | O_EXCL, 0666);
    if (ofd == -1) {
        err = errno;
        close(ifd);
        return err;
    }

    /* Allocate temporary data buffer. */
    data = malloc(size);
    if (!data) {
        close(ifd);
        close(ofd);
        /* Remove output file. */
        unlink(target);
        return ENOMEM;
    }

    /* Copy loop. */
    while (1) {

        /* Read a new chunk. */
        bytes = read(ifd, data, size);
        if (bytes < 0) {
            if (bytes == -1)
                err = errno;
            else
                err = EIO;
            free(data);
            close(ifd);
            close(ofd);
            unlink(target);
            return err;
        } else
        if (bytes == 0)
            break;

        /* Write that same chunk. */
        ptr = data;
        end = data + bytes;
        while (ptr < end) {

            bytes = write(ofd, ptr, (size_t)(end - ptr));
            if (bytes <= 0) {
                if (bytes == -1)
                    err = errno;
                else
                    err = EIO;
                free(data);
                close(ifd);
                close(ofd);
                unlink(target);
                return err;
            } else
                ptr += bytes;
        }
    }

    free(data);

    err = 0;
    if (close(ifd))
        err = EIO;
    if (close(ofd))
        err = EIO;
    if (err) {
        unlink(target);
        return err;
    }

    return 0;
}

该函数获取目标文件名(要创建),源文件名(要读取)以及可选的首选块大小。如果提供0,则使用默认块大小。在当前的Linux硬件上,256k块大小应达到最大吞吐量;较小的块大小可能会导致某些(大型和快速)系统上的复制操作变慢。

块大小应该是2的幂,或者是2的大幂的小倍数。由于调用者选择了块大小,因此使用malloc() / free()动态分配。请注意,在错误情况下会明确释放它。

因为目标文件总是被创建 - 函数将失败,如果目标文件已经存在则返回EEXIST - 如果发生错误则被删除(“取消链接”),因此没有部分文件在错误的情况下遗留下来。 (忘记在错误路径中释放动态分配的数据是一个常见的错误;这通常称为“泄漏内存”。)

可以在open()找到read()write()close()unlink()Linux man pages的确切用法。

write()返回写入的字节数,如果发生错误则返回-1。 (注意,我明确地将0和所有小于-1的负值视为I / O错误,因为它们通常不会发生。)

read()返回读取的字节数,如果发生错误则返回-1,如果没有更多数据则返回0。

read()write()都可能返回短计数;即,低于要求。 (在Linux中,大多数本地文件系统上的普通文件都不会发生这种情况,但只有白痴才会依赖上述函数来处理这些文件。处理短计数并不复杂,正如您从上面的代码中看到的那样。 )

如果您想添加进度表,例如使用回调函数,例如

void progress(const char *target, const char *source,
              const off_t completed, const off_t total);

然后在循环之前添加fstat(ifd, &info)调用是有意义的(使用struct stat info;off_t copied;,后者计算复制的字节数)。该来电也可能失败或报告info.st_size == 0,如果来源是例如命名管道而不是普通文件。这意味着total参数可能为零,在这种情况下,进度表将仅显示以字节为单位的进度(completed),剩余量未知。

答案 1 :(得分:3)

以下是一些批评,然后我会如何做:

这很好:

int movie_rdfd = open("Suits.mp4", O_RDONLY); //fd for read

这是,嗯,不太好:

off_t file_length = (int)(fseek(movie_rdfd, 0, SEEK_END));

fseek()适用于基于stdio的{​​{1}}指针,这些指针是使用FILE *打开的,而不是来自fopen()的{​​{1}}个文件描述符。要获取使用intuse fstat()打开的文件大小:

open

现在你知道文件有多大了。但如果它是一个非常大的文件,它就不适合内存,所以这很糟糕:

open()

这在很多方面都很糟糕 - 它应该是struct stat sb; int rc = fstat( movie_rdfd, &sb ); ,而不是char* Save[fseek(movie_rdfd, 0, SEEK_END)]; 。但无论哪种方式,对于一个非常大的文件,它都不会起作用 - 它太大了,不能把它作为局部变量放在堆栈上。

而且你不想一下子读完整件事 - 它可能不会起作用,因为你可能会得到部分阅读。 Per the read standard

  

char Save[]函数将尝试从中读取 char *个字节   与打开文件描述符相关联的文件...

     

返回值

     

成功完成后,这些函数将返回一个非负整数,表示实际读取的字节数。 ...

请注意,它表示“将尝试读取”并返回“实际读取的字节数”。所以你必须用循环来处理部分读取。

以下是使用read()nbyteopen()复制文件的一种非常简单的方法(请注意,它确实应该有更多错误检查 - 例如{{1}应检查结果以确保它们与读取的字节数匹配):

read()

请注意,您甚至不需要知道文件的大小。

你可以做很多事情来加快速度 - 它们通常不值得,因为上述代码可能会在大多数系统上以最大IO速率的90%运行。