我一直在学习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;
}
当我尝试查找文件大小时,它还显示分段错误
答案 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}}个文件描述符。要获取使用int
,use 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()
,nbyte
和open()
复制文件的一种非常简单的方法(请注意,它确实应该有更多错误检查 - 例如{{1}应检查结果以确保它们与读取的字节数匹配):
read()
请注意,您甚至不需要知道文件的大小。
你可以做很多事情来加快速度 - 它们通常不值得,因为上述代码可能会在大多数系统上以最大IO速率的90%运行。