【问题标题】:Multiple read() operations on the same file同一文件上的多个 read() 操作
【发布时间】:2013-12-01 23:48:17
【问题描述】:

我正在考虑使用 read() 函数读取整个数据结构,每个数据结构都与其他数据结构具有相同的类型,但数据不同,然后将它们放入链表中。出于某种原因,我似乎找不到任何关于如何终止循环的具体信息,其中包括read(fp, &tmp, sizeof(struct foo)),后跟new_node(tmp)

我希望能够简单地阅读直到 EOF,但我不知道如何使用 read() 函数确定 EOF 所在的位置。显然,我可以使用 write() 函数的变通方法,在写入之前我将在文件中包含结构的数量,然后在达到该数量时终止读取函数,但这似乎有点笨拙,并且避免了原来的知道文件何时终止的想法。

跟进:

感谢您的帮助,我已经实施了我所看到的。不幸的是,我相信我可能读错了信息。相关代码:

struct test_t{
    int data;
    char buf[LEN];
        struct test_t * next;
};

struct test_t * new_node(struct test_t node, struct test_t * tail)
{
    struct test_t * tmp = NULL;

    if(!(tmp = malloc(sizeof(struct test_t))))
        return NULL;

    tmp->data = node.data;
    strcpy(tmp->buf, node.buf);
    tmp->next = NULL;
    if(tail)
        tail->next = tmp;

    return tmp;
}

...

while(read(fd, &tmp, sizeof(struct test_t)) == sizeof(struct test_t)){
    printf("%d, %s\n", tmp.data, tmp.buf);
    tail = new_node(tmp, tail);
    if(head == NULL)
        head = tail;
    printf("%d, %s\n", tail->data, tail->buf);
}

...

fd = open("test.txt", O_WRONLY | O_CREAT, 0666);
iter = head;
while(iter){
    printf("%d\n", write(fd, &iter, sizeof(struct test_t)));
    printf("%d, %s\n", iter->data, iter->buf);
    iter = iter->next;
}

这是写循环的输出:

112
1, a
112
2, b
112
3, c
112
4, d
112
5, e

该文件以二进制形式保存,但我可以清楚地知道只有尾部似乎被写入了五次。我不确定这是为什么。

读取循环中诊断 printf 的输出是:

23728144, 
23728144, 
23728272, 
23728272, 
23728400, 
23728400, 
23728528, 
23728528, 
23728656, 
23728656,

输出让我觉得它将下一个指针的值放入数据 int 中。知道为什么: 1) 我可能连续五次对同一个节点进行 write()? 2)当我阅读()时我变得乱码?

【问题讨论】:

  • 也许检查来自read()的返回值?

标签: c file-io unistd.h


【解决方案1】:
while (read(fd, &tmp, sizeof(tmp)) == sizeof(tmp))
{
    ...got another one...
}

通常使用FILE *fp;int fd;(因此文件描述符的名称是fd 而不是fp)。

read() 函数返回它读取的字节数。如果没有更多数据,则返回 0。对于磁盘文件等,它将返回请求的字节数(除了最后可能没有那么多字节要读取)或没有数据时返回 0读取(或 -1,如果设备上有错误,而不仅仅是没有更多数据)。对于终端(以及套接字和管道),它将读取尽可能多的可用字节,而不是等待请求的大小(因此每次读取都可能返回不同的大小)。显示的代码仅读取完整大小的结构,并且在读取短读、EOF 或错误时阻止。


ensc 在他的answer 中的代码涵盖了所有实际情况,但不是我编写等效循环的方式。我会使用:

struct foo tmp;
ssize_t nbytes;

while ((nbytes = read(fd, &tmp, sizeof(tmp))) != 0)
{
    if ((size_t)nbytes = sizeof(tmp))
        process(&tmp);
    else if (nbytes < 0 && errno == EINTR)
        continue;
    else if (nbytes > 0)
        err_syserr("Short read of %zu bytes when %zu expected on fd %d\n",
                   nbytes, sizeof(tmp), fd);
    else
        err_syserr("Read failure on fd %d\n", fd);
}

两种正常情况——一条全长记录被读取OK和检测到EOF——在循环的顶部处理;深奥的案件在循环中进一步处理。我的err_syserr() 函数是printf()-like 并报告其参数给出的错误,以及与errno 相关的错误(如果它不为零),然后退出。您可以使用任何等效机制。我可能会也可能不会将文件描述符编号放在错误消息中;这取决于谁会看到错误。如果我知道文件名,我肯定会在消息中包含它而不是文件描述符。

与@ensc 的 cmets 不同,我认为处理 nbytes == -1 &amp;&amp; errno == EINTR 案件没有任何困难。

【讨论】:

    【解决方案2】:

    read 返回读取的字节数。如果您执行读取,并且返回值小于您请求的字节数,那么您知道它在读取期间达到了 EOF。如果它正好等于请求的字节数,那么文件没有到达 EOF,或者它已经到达,并且文件中正好剩下 0 个字节,在这种情况下,下一次调用 read() 将返回 0。

    while(read(fd, &tmp, sizeof(tmp)) > 0) {
        ...
    }
    

    【讨论】:

      【解决方案3】:

      忽略错误条件,我认为基本思路是这样的:

      while (read(fp, &tmp, sizeof(struct foo))==sizeof(struct foo))
          new_node(tmp);
      

      【讨论】:

        【解决方案4】:
        for (;;) {
            struct foo tmp;
            ssize_t l = read(fd, &tmp, sizeof tmp);
        
            if (l < 0 && errno == EINTR) {
                continue;
            } else if (l < 0) {
                perror("read()");
                abort();
            } else if (l == 0) {
                break;   /* eof condition */
            } else if ((size_t)(l) != sizeof tmp) {
                abort(); /* something odd happened */
            } else {
                handle(&tmp);
            }
        }
        

        编辑:

        在我的项目中,我使用泛型

        bool read_all(int fd, void *dst_, size_t len, bool *is_err)
        {
                unsigned char *dst = dst_;
        
                *is_err = false;
        
                while (len > 0) {
                        ssize_t l = read(fd, dst, len);
        
                        if (l > 0) {
                                dst += l;
                                len -= l;
                        } else if (l == 0) {
                                com_err("read_all", 0, "read(): EOF");
                                *is_err = (void *)dst != dst_;
                                break;
                        } else if (errno == EINTR) {
                                continue;
                        } else {
                                com_err("read_all", errno, "read()");
                                *is_err = true;
                                break;
                        }
                }
        
                return len == 0;
        }
        

        功能。 因为我更喜欢说明要读取多少元素的方法,所以EOF 在此处被视为错误。但是在非 EOF 错误情况下设置的函数中添加另一个 bool *err 参数将是微不足道的。 您可以将上述用作

        while (read_all(fd, &tmp, sizeof tmp, &is_err))
            new_node(&tmp);
        

        【讨论】:

        • read() 获取到最后一段数据时,不会返回0到sizeof(tmp)之间的数字吗?当然,除非文件大小恰好是 sizeof(tmp) 的倍数?因为如果是这样,对于 l != sizeof(tmp) 来说并不奇怪。
        • @master_latch:abort() 是否合适还有待商榷,但这涵盖了很多(如果不是全部)可能的错误情况。就个人而言,我也不会将其编码为无限循环。我可能会使用while ((l = read(fd, &amp;tmp, sizeof(tmp))) != 0) { ... },从循环体中省略EOF检测,因为循环条件检测到了这一点。我还将普通情况放在顶部:if (l == sizeof(tmp)) { handle(&amp;tmp); } else ... handle errors ...
        • 如果允许这样的文件布局,那将是一个糟糕的设计。例如。该文件应包含准确的 n 元素并立即结束,或者您将信息放入文件中,预计有多少 n 元素并且只能读取这么多。
        • @JonathanLeffler 将其置于循环条件时很难处理 errno = EINTR 情况(不使用 TEMP_FAILURE_RETRY GNU 宏)。就个人而言,我也不喜欢赋值循环条件。
        猜你喜欢
        • 2011-02-05
        • 1970-01-01
        • 1970-01-01
        • 2010-11-05
        • 1970-01-01
        • 2019-04-05
        • 2021-07-17
        • 2021-07-25
        • 1970-01-01
        相关资源
        最近更新 更多