- Почему эта программа работает только тогда, когда введена задержка?
Потому что FIFO ориентированы на потоки, а не на сообщения. Небезопасно предполагать, что данные, записанные одним вызовом write()
, будут считаны как единое целое любым вызовом read()
. В вашем конкретном случае, если ваша функция write_to_fifo()
вызывается несколько раз в быстрой последовательности, вы можете получить две или более записи, происходящие между чтениями, и в этом случае одно чтение может получить все данные из обеих записей, а не только из первого.
Кроме того, если это произойдет, то это будет скрыто от вас, потому что ваши записи (преднамеренно, кажется) включают терминаторы строк. Сколько бы считыватель ни читал, его printf()
будет выводить данные только до первого терминатора. То есть не вижу оснований думать, что вы теряете сообщения в fifo; скорее, я уверен, что вы теряете их в читателе.
- Это так и должно быть?
Поведение, которое вы описываете, кажется мне последовательным, как описано выше.
- Если нет, то как мне это преодолеть?
Даже если поведение в некотором смысле является таким, каким оно должно быть, это не означает, что вы не можете получить поведение, которое вам больше нравится. Поскольку (почти наверняка) именно читатель теряет сообщения, вы можете исправить это, сделав читатель умнее. Вы должны быть в состоянии сделать это даже без модификации модуля записи, но модификация модуля записи может упростить задачу, и есть другие причины, по которым вы можете захотеть изменить его.
Во-первых, вы должны всегда учитывать возвращаемое значение функций read()
и write()
даже в большей степени, чем большинство функций. Возвращаемое значение не только сообщает об условиях ошибки и (для read()
) условиях конца файла, но также сообщает вам, сколько байтов было передано при каждом вызове. Это важная информация для обеих сторон, потому что число, переданное любой функцией, может быть меньше запрошенного числа. Вообще говоря, нужно быть готовым вызвать write()
или read()
в цикле, чтобы убедиться, что все нужные байты переданы. В вашем конкретном случае вам не обязательно читать определенное количество байтов, но обращая внимание на то, сколько байтов было фактически прочитано, вы можете распознать, когда вы получили части нескольких сообщений с помощью одного и того же вызова чтения.
Во-вторых, хотя строки, заканчивающиеся нулем, являются хорошим представлением в памяти, они не особенно хороши для представления в сети. Поскольку вы, по-видимому, хотите печатать каждое сообщение, за которым следует новая строка, вы можете вместо этого рассмотреть данные, заканчивающиеся новой строкой. В этом случае читателю, возможно, даже не нужно беспокоиться о границах сообщений - если все, что вы хотите, это вывести сообщения на стандартный вывод, тогда он может просто прочитать из fifo и выгрузить все (используя счетчик байтов) в выходной файл, включая символы новой строки в данных, не беспокоясь о границах сообщения.
Но если вы хотите обрабатывать сообщения переменной длины в единицах сообщения, вам поможет лучший протокол. Например, отправляйте сообщения в виде сообщения фиксированной длины, за которым следует указанное количество байтов сообщения. Таким образом, программа чтения всегда знает, сколько байтов нужно прочитать (даже если для их получения необходимо использовать несколько вызовов read()
).
В-третьих, читатели и писатели не должны постоянно открывать и закрывать fifo. Каждый процесс должен открыть его один раз и держать открытым столько времени, сколько необходимо. Любой из них или оба могут создать fifo один раз, хотя, если вы оба делаете это, вам нужно быть готовым к тому, что по крайней мере один из них не сможет этого сделать (из-за того, что другой сделал это первым). Несколько потоков записи могут совместно использовать один и тот же дескриптор файла, и на самом деле это часто предпочтительнее, чем несколько потоков, каждый из которых открывает один и тот же файл по отдельности. Вы могли бы подумать о том, чтобы потоки записи вызывали fsync()
после каждого сообщения вместо закрытия файла, но это, вероятно, не нужно, если все они используют один и тот же FD.
В многопоточном случае, используя один и тот же FD для всех потоков, вам не нужно беспокоиться о том, что данные из одного вызова записи одного потока чередуются с данными из вызова записи другим потоком. Тем не менее, вы делаете должны знать, что если исходящее сообщение в конечном итоге будет разделено на более чем один вызов write
, вы можете иногда получить промежуточную запись другого потока. Вы можете использовать мьютекс, чтобы гарантировать, что сообщения не будут разделены таким образом, когда они распределяются по нескольким записям.
Однако если есть только один читатель, то мне неясно, что дает использование мьютекса на этой стороне.
person
John Bollinger
schedule
10.08.2020