是否可以在 C 中使用相同的文件描述符进行读写

is it possible to read and write with the same file descriptor in C

我正在尝试写入文件并显示我用另一个进程写入的内容的输出。我想出的代码:

void readLine (int fd, char *str) {
    int n;
    do { 
        n = read (fd, str, 1);
    } while (*str++ != '[=10=]');
}

int main(int argc,char ** argv){
    int fd=open("sharedFile",O_CREAT|O_RDWR|O_TRUNC,0600);
    if(fork()==0){
        char buf[1000];
        while(1) {
            readLine(fd,buf);
            printf("%s\n",buf); 
        }
    }else{
        while(1){
             sleep(1);
             write(fd,"abcd",strlen("abcd")+1);
        }
    }
}

我想要的输出(每个结果彼此间隔一秒):

abcd
abcd
abcd
....

不幸的是,这段代码不起作用,似乎子进程(文件“sharedFile”的reader)从文件中读取垃圾,因为即使文件为空,它也会以某种方式读取值。 尝试调试代码时,readLine 函数从未正确读取写入的文件,它始终读取 0 字节。 有人可以帮忙吗?

首先,当文件描述符在分叉后共享时,parent 和 child 都指向相同的 打开文件描述符 ,这特别意味着它们共享相同的文件位置。 fork() 手册页对此进行了解释。

所以每当 parent 写入时,位置都会更新到文件末尾,因此 child 总是尝试在文件末尾读取,那里没有数据.这就是为什么 read() returns 0,就像你到达文件末尾时一样正常。

(发生这种情况时,您不应尝试对缓冲区中的数据执行任何操作。这并不是说您在“阅读垃圾”,而是您没有阅读任何内容 但是然后假装缓冲区中的任何垃圾都是你刚刚读到的。特别是你的代码完全无视 read() 中的 return 值,这就是你应该告诉什么的方式你真的读过。)

如果你想让child有一个独立的文件位置,那么child需要为自己单独open()文件并得到一个指向新文件的新fd说明。

但是,当 child 读取了当前文件中的所有数据时,read() 将再次 return 0;它不会等待 parent 写更多。一些其他进程打开文件进行写入这一事实不会影响 read() 在常规文件上的语义。

所以你需要做的是,当read() returns 0时,你手动休眠一段时间,然后再试一次。当文件中有更多数据时,read() 将 return 为正数,然后您可以处理读取的数据。或者,有更优雅但更复杂的方法使用 system-specific API,如 Linux 的 inotify,它可以休眠直到文件内容更改。您可能熟悉 tail -f,它在不同的系统上结合使用了这些方法。

另一个危险的错误是,如果其他人将文本写入不包含预期的空字节的文件,您的 child 将读取比缓冲区所能容纳的更多数据,从而使缓冲区溢出。这可能是一个可利用的安全漏洞。

这是修复这些错误并适用于我的代码版本:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void readLine (int fd, char *str, size_t max) {
    size_t pos = 0;
    while (pos < max) {
        ssize_t n = read(fd, str + pos, 1);
        if (n == 0) {
            sleep(1);
        } else if (n == 1) {
            if (str[pos] == '[=10=]') {
                return;
            }
            pos++;
        } else {
            perror("read() failure");
            exit(2);
        }
    }
    fprintf(stderr, "Didn't receive null terminator in time\n");
    exit(2);
}

int main(int argc, char ** argv){
    int fd=open("sharedFile", O_CREAT|O_RDWR|O_TRUNC, 0600);
    if (fd < 0) {
        perror("parent opening sharedFile");
        exit(2);
    }
    pid_t pid = fork();
    if (pid == 0){
        int newfd = open("sharedFile", O_RDONLY);
        if (newfd < 0) {
            perror("child opening sharedFile");
            exit(2);
        }
        char buf[1000];
        while (1) {
            readLine(newfd, buf, 1000);
            printf("%s\n",buf); 
        }
    } else if (pid > 0) {
        while (1){
            sleep(1);
            write(fd,"abcd",strlen("abcd")+1);
        }
    } else {
        perror("fork");
        exit(2);
    }
    return 0;
}