c语言 - 在 linux 中使用 read()、write() 和 open() 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: %dn", 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): %dn", 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: %dn", movie_wrfd); //line to be deleted
int C2 = write(movie_wrfd, Save, fseek(movie_rdfd, 0, SEEK_END));
printf("Result of Read (C2): %dn", C2); //line to be deleted
close(movie_rdfd);
close(movie_wrfd);
return 0;
}

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

在 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 手册页中找到。

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为单位的进度,剩余量未知。

这里有一些批评,然后是我该怎么做:

这很好:

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

这是,嗯,不太好:

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

fseek()适用于使用fopen()打开的基于stdioFILE *的指针,而不是openint文件描述符。 要获取使用open()打开的文件的大小,请使用fstat()

struct stat sb;
int rc = fstat( movie_rdfd, &sb );

现在您知道文件有多大了。 但是如果它是一个非常大的文件,它将无法放入内存,所以这很糟糕:

char* Save[fseek(movie_rdfd, 0, SEEK_END)];

这在很多方面也很糟糕——它应该是char Save[]的,而不是char *的。 但无论哪种方式,对于一个非常大的文件,它都不会起作用 - 它太大了,无法作为局部变量放在堆栈上。

无论如何,您不想一次阅读整个内容 - 它可能不会起作用,因为您可能会部分阅读。 根据读取标准:

read()函数应nbyte尝试从 与打开的文件描述符关联的文件...

返回值

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

请注意,它说"应尝试读取",并返回"实际读取的字节数"。 因此,无论如何,您都必须使用循环来处理部分读取。

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

#define BUFSIZE ( 32UL * 1024UL )
char buffer[ BUFSIZE ];
int in = open( nameOfInputFile, O_RDONLY );
int out = open( nameOfOutputFile, O_WRONLY | O_CREAT | O_TRUNC, 0644 );
// break loop explicitly when read fails or hits EOF
for ( ;; )
{
ssize_t bytesRead = read( in, buffer, sizeof( buffer ) );
if ( bytesRead <= 0 )
{
break;
}
write( out, buffer, bytesRead );
}

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

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

最新更新