0

我正在制作一个应用程序,它一开始就分为两个进程。简化后,一个进程不断地从 fifo 文件中读取数据,另一个进程偶尔将通知写入 fifo 文件。现在,一切正常,除非写入过程 write 连续快速调用它的写入方法。然后只有第一个通知会从 fifo 文件中写入(或读取?)。

这是fifo读取过程的代码:

void logging(){
    int fd1;

    char * myfifo = "/home/jens/Desktop/CLion_projects/Labo9/logfifo";
    mkfifo(myfifo, 0666);
    char str1[20];
    while(1) {
        pthread_mutex_lock(&lock_fifo);
        fd1 = open(myfifo,O_RDONLY);                            
        read(fd1, str1, 1000);

        printf("%s\n", str1);
        close(fd1);
        pthread_mutex_unlock(&lock_fifo);
    }
}

这是写入 fifo 文件的方法(在另一个进程中):

void write_to_fifo(char * string_to_write){
    int fd;
    char * myfifo = "/home/jens/Desktop/CLion_projects/Labo9/logfifo";
//    mkfifo(myfifo, 0666);                             (should this be on or not?)

    char * arr2 = string_to_write;
    fd = open(myfifo, O_WRONLY);
    write(fd, arr2, strlen(arr2)+1);
    close(fd);
}

这是两个使用 write_to_fifo(..) 方法的调用:

    write_to_fifo("Connection to SQL server established.\n");
//    sleep(1);
    write_to_fifo("New table "TO_STRING(TABLE_NAME)" created.\n");

第一个始终正确打印,第二个仅在 sleep(1) 未注释时有效。因此,我猜它与时间有关。如果我没有同时运行多个线程,那么将 sleep(1) 留在其中不会有问题。我想运行多个线程会使时间不可预测,并且您不能在不同线程的函数调用之间添加 sleep(1) 行。

  1. 为什么这个程序只有在引入延迟时才起作用?
  2. 这是应该的吗?
  3. 如果没有,我该如何克服?
4

1 回答 1

2
  1. 为什么这个程序只有在引入延迟时才起作用?

因为 FIFO 是面向流的,而不是面向消息的。write()假设一个调用写入的数据将被任何调用作为一个完整的单元读取是不安全的read()。在您的特定情况下,如果您的write_to_fifo()函数被快速连续多次调用,那么您可能会在读取之间发生两次或多次写入,在这种情况下,一次读取可能会从两次写入中获取所有数据,而不仅仅是从第一次写入。

此外,如果发生这种情况,那么它会对您隐藏,因为您的写入(似乎是故意的)包含字符串终止符。无论阅读器读多少,它printf()都只会输出到第一个终止符的数据。也就是说,我认为没有理由认为您正在丢失 fifo 中的消息。相反,我相信你在读者中失去了它们。

  1. 这是应该的吗?

如上所述,您描述的行为对我来说似乎是一致的。

  1. 如果没有,我该如何克服?

即使行为在某种意义上是“应该如何”,但这并不意味着您无法获得您更喜欢的行为。由于(几乎可以肯定)是阅读器丢失了消息,因此您可以通过让阅读器更聪明来解决这个问题。即使不修改编写器,您也应该能够做到这一点,但是修改编写器可能会使它更容易,并且您可能想要修改它还有其他原因。

首先,您必须始终考虑read()andwrite()函数的返回值,甚至比大多数函数还要多。返回值不仅报告错误条件和 (for read()) 文件结束条件,而且还告诉您每次调用传输的字节数。这对双方都很重要,因为任一函数传输的数字都可能小于请求的数字。一般来说,一个人必须准备打电话write()read()在一个循环中以确保传输所有想要的字节。在您的特定情况下,您不一定需要读取一定数量的字节,但注意实际读取了多少字节可以让您识别何时通过同一个读取调用获得了多条消息的一部分。

其次,尽管以空字符结尾的字符串在内存中是一种不错的表示形式,但它们并不是特别好的在线表示形式。由于您显然想要打印每条消息后跟换行符,因此您可以考虑换行符终止的数据。在这种情况下,读者甚至可能不需要担心消息边界——如果你想做的只是将消息转储到标准输出,那么它可能只是从 fifo 读取并转储所有内容(使用字节数)到输出文件,包括数据中的换行符,而不用担心消息边界。

但是,如果您想以每个消息为单位处理可变长度的消息,那么更好的协议会为您提供帮助。例如,以固定长度消息长度后跟该消息字节数的形式发送消息。这样,读者总是知道要读取多少字节(即使它必须使用多次read()调用来获取它们)。

第三,读写器不应该一直打开和关闭fifo。每个进程都应该打开一次,并根据需要保持打开状态。一个或两个都可以创建fifo,一次,但如果你让他们两个都这样做,那么你需要为至少一个失败做准备(因为另一个先做了)。writer 的多个线程可以共享同一个文件描述符,实际上这通常比多个线程分别打开同一个文件更可取。您可以考虑让编写器线程fsync()在每条消息之后调用而不是关闭文件,但如果它们都使用相同的 FD,那么这可能是不必要的。

在多线程情况下,对所有线程使用相同的 FD,您无需担心来自一个线程的一次写入调用的数据与来自不同线程的写入调用的数据交错。但是,您确实需要注意,如果出站消息最终被拆分为多个write调用,那么您有时可能会在其间插入另一个线程的写入。您可以使用互斥锁来确保消息在多次写入时不会以这种方式拆分。

但是,如果只有一个读者,那么我不清楚在那一侧使用互斥锁可以获得什么。

于 2020-08-10T16:39:37.047 回答