您的代码不起作用(许多人对问题发表了评论,有些人已回答),即使已修复,也可能会丢失数据,但不是因为使用了缓冲区。
考虑以下(未经测试,因此请报告您可能在评论中发现的任何错误,以便我修复它)示例:
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#ifndef COPY_BUFFER_SIZE
#define COPY_BUFFER_SIZE 262144
#endif
struct copystatus {
off_t bytes;
int error;
};
struct timeout;
extern int timeout_elapsed(struct timeout *);
int copyfd(const int tofd, const int fromfd,
struct copystatus *toinfoptr,
struct copystatus *frominfoptr,
struct timeout *limit)
{
const size_t size = COPY_BUFFER_SIZE;
char *data, *p, *q;
ssize_t n;
struct copystatus toinfo, frominfo;
int retval = 0;
toinfo.bytes = 0;
toinfo.error = 0;
frominfo.bytes = 0;
frominfo.bytes = 0;
if (toinfoptr) *toinfoptr = toinfo;
if (frominfoptr) *frominfoptr = frominfo;
if (tofd == -1 || fromfd == -1)
return errno = EINVAL;
data = malloc(size);
if (!data)
return errno = ENOMEM;
while (1) {
if (timeout_elapsed(limit)) {
retval = frominfo.error = ETIMEDOUT;
break;
}
n = read(fromfd, data, size);
if (n == -1) {
/* Interrupted by a signal? Timeout? */
if (errno == EINTR)
continue;
/* No, an error occurred. */
retval = frominfo.error = errno;
break;
} else
if (n == 0)
break; /* No more input. */
else
if (n < -1) {
/* Library/kernel error. Might occur if size > 2GB. */
retval = frominfo.error = EIO;
break;
}
frominfo.bytes += (off_t)n;
p = data;
q = data + n;
while (p < q) {
if (timeout_elapsed(limit)) {
retval = toinfo.error = ETIMEDOUT;
break;
}
n = write(tofd, p, (size_t)(q - p));
if (n == -1) {
if (errno == EINTR)
continue;
retval = toinfo.error = errno;
break;
} else
if (n < 1) {
/* Should not occur. */
retval = toinfo.error = EIO;
break;
}
p += n;
toinfo.bytes += (off_t)n;
}
if (retval)
break;
}
free(buffer);
if (toinfoptr) *toinfoptr = toinfo;
if (frominfoptr) *frominfoptr = frominfo;
return errno = retval;
}
上述代码从一个描述符读取数据,将其写入另一个描述符,并进行完整的错误检查。如果成功则返回 0,否则返回 errno 错误代码。 (请注意,将返回值分配给errno 并不常见;这只是我自己的风格,只有在使用时返回值始终是有效的errno 值。)
如果非 NULL,则两个复制状态结构将填充复制的字节数,以及特定于该描述符的错误代码。特别是,如果发生超时,则只有其中一个error 字段将设置为ETIMEDOUT。 (这是一个POSIX.1错误代码,“连接超时”,在C89/C99/C11中没有定义。)
该函数旨在与使用信号传递(到空信号处理程序)来中断阻塞 I/O 调用的超时机制一起使用。 (在这种情况下,调用将返回-1 和errno == EINTR。)它不适用于非阻塞套接字;在这种情况下,select() 循环会更好,并使用 gettimeofday() 或 POSIX.1 clock_gettime() 来获取实际时间,在内部计算复制操作的经过时间,而无需外部超时检查器功能。在单个函数中混合阻塞/非阻塞操作是可能的,但非常混乱,难以维护和理解,因此强烈建议不要这样做。 (可以使用fcntl(descriptor,F_GETFL) & O_NONBLOCK 来查看描述符是否设置为非阻塞。)
当fromfd 引用本地文件系统上的文件时,所有读取都将返回请求的大小,-1 表示错误,或者文件末尾的短计数或零。当tofd 引用本地文件系统上的文件时,所有写入都将写入并返回请求的数量,除非发生错误,在这种情况下它们将返回-1。这确实不意味着人们应该随意假设这种情况很普遍;事实上,本地文件行为是个例外。
在 Linux 和所有 POSIXy 系统中,字符设备和伪终端可以被视为文件,无需应用程序进行任何特殊处理。然而,那些返回短读的情况常见,有时甚至是短写。
此外,虽然recv*() 和send*() 系列函数通常与套接字描述符一起使用,但也可以很容易地使用read() 和write()。对套接字的读取和写入返回短计数是很常见的,有时甚至会出现错误(例如,网络中断),尽管真正的长阻塞更为常见(例如,由于默认 TCP 超时较长)。
这就是为什么上面的示例代码没有假设读写行为以任何特定方式进行。它不是最优的,因为它总是交错读取和写入,并假定阻塞描述符。如果您在现实生活中需要这样的功能,您可能会复制多个文件(并行或顺序),并使用基于select() 的非阻塞方法同时复制多个文件,这可能会更可取对于应用程序的用户,尤其是当目标文件位于不同的挂载点时。 (如果每个目标文件位于不同的挂载点,或者是字符设备或套接字,则并行方法往往更快;如果目标是文件并且位于相同的挂载点,则顺序方法更快。)