Как read()/write() работает с FIFO? - Проблема в программе клиент/сервер

У меня есть программа клиент/сервер.

Клиент делает следующее в цикле.

  • записывает string1 в канал.
  • прочитать строку из другого канала.
  • записывает string2 в канал.

Сервер делает следующее в цикле.

  • читает строку.
  • записывает те же данные клиенту на другом канале.
  1. Для некоторых итераций это работает нормально, примерно после 10 итераций только строка2 читается как на клиенте, так и на сервере. Почему это так? Кроме того, в клиентской программе в цикле while(), если read() вызывается после 2-го write(), все работает нормально.

  2. В клиенте со второй итерации read() должен возвращать все данные в канале, потому что канал не имеет границ сообщения. Но он просто читает только данные, записанные 1 вызовом write() на сервере.

Ниже приведен мой код для справки.

server.c

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

int main()
{
 client_to_server;
char *myfifo = "/tmp/client_to_server_fifo";
int server_to_client;
char *myfifo2 = "/tmp/server_to_client_fifo";

char buf[BUFSIZ];

 /* create the FIFO (named pipe) */
 mkfifo(myfifo, 0666);
 mkfifo(myfifo2, 0666);

 /* open, read, and display the message from the FIFO */
 client_to_server = open(myfifo, O_RDONLY);
 server_to_client = open(myfifo2, O_WRONLY);

 printf("Server ON bufsize=%d.\n", BUFSIZ);

 while (1)
 {
   read(client_to_server, buf, BUFSIZ);
  if (strcmp("exit",buf)==0)
  {
     printf("Server OFF.\n");
     break;
  }
  else if (strcmp("",buf)!=0)
  {
     printf("Received: %s\n", buf);
     printf("Sending back...\n");
     write(server_to_client,buf,BUFSIZ);
  }

  /* clean buf from any data */
  memset(buf, 0, sizeof(buf));
 }

 close(client_to_server);
 close(server_to_client);
 unlink(myfifo);
 unlink(myfifo2);
 return 0;
}

клиент.c

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

int main()
{
   int client_to_server;
   char *myfifo = "/tmp/client_to_server_fifo";
   int server_to_client;
   char *myfifo2 = "/tmp/server_to_client_fifo";

   char str[BUFSIZ];

  /* write str to the FIFO */
  client_to_server = open(myfifo, O_WRONLY);
  server_to_client = open(myfifo2, O_RDONLY);

  char buf1[30] = "str1";
  char buf2[30] = "str2";

 while(1){

   //write first string
   write(client_to_server, buf1, sizeof(buf1));
   read(server_to_client,str,sizeof(str));
   perror("Read:"); // Very crude error check
   printf("...received from the server: %s\n",str);

   memset(str, '\0', sizeof(str));

  //write second string
  write(client_to_server, buf2, sizeof(buf2));
 }

 close(client_to_server);
 close(server_to_client);
 return 0;
}

Ниже приведен вывод: Вывод сервера: Сервер ON bufsize=8192. Получено: str1 Отправка обратно... Получено: str2 Отправка обратно... Получено: str1 Отправка обратно... Получено: str2 Отправка обратно... Получено: str1 Отправка обратно... Получено: str2 Отправка обратно... Получено: str1 Отправка обратно... Получено: str2 Отправка обратно... Получено: str1 Отправка обратно... Получено: str2 Отправка обратно... Получено: str1 Отправка обратно... Получено: str2 Отправка обратно... Получено: str1 Отправка назад... Получено: str2 Отправка обратно... Получено: str1 Отправка обратно... Получено: str2 Отправка обратно... Получено: str1 Отправка обратно... Получено: str2 Отправка обратно... Получено: str1 Отправка обратно. .. Получено: str2 Возвращается... Получено: str2 Возвращается... Получено: str2 Возвращается... Получено: str2 Возвращается... Получено: str2 Возвращается... Получено: str2 Возвращается... Получено: str2 Отправка обратно... Получено: str2

Ниже приведен вывод клиента: ...получено от сервера: str1 ...получено от сервера: str2 ...получено от сервера: str1 ...получено от сервера: str2 ...получено от сервера : str1 ...получено с сервера: str2 ...получено с сервера: str1 ...получено с сервера: str2 ...получено с сервера: str1 ...получено с сервера: str2 ... получено с сервера: str1 ...получено с сервера: str2 ...получено с сервера: str1 ...получено с сервера: str2 ...получено с сервера: str1 ...получено с сервера: str2 ...получено с сервера: str1 ...получено с сервера: str2 ...получено с сервера: str1 ...получено с сервера: str2 ...получено с сервера: str2 ...получено с сервера: str2 ...получено с сервера: str2 ...получено с сервера: str2 ...получено с сервера: str2 ...получено с сервера: str2 ...получено с сервера: str2 ...получено с сервера: str2 ...получено отсюда m сервер: str2 ...получено с сервера: str2 ...получено с сервера: str2 ...получено с сервера: str2 ...получено с сервера: str2 ...получено с сервера: str2


person aaa    schedule 21.07.2017    source источник
comment
Вы хотели использовать sizeof здесь write(client_to_server, buf1, sizeof(buf1));, поскольку потенциально вы отправляете str1 и null плюс еще 25 символов?   -  person lundman    schedule 21.07.2017
comment
Откуда вы вообще знаете, что ваши звонки read() и write() сработали?   -  person Andrew Henle    schedule 21.07.2017
comment
Вывод, который я дал, отображается бесконечно, это означает, что read() и() write работают, если бы эти вызовы не работали, они бы где-то застряли. Кроме того, в client.c в цикле, если я вызываю read() после второго write(), все работает нормально.   -  person aaa    schedule 21.07.2017
comment
Вы не проверяете ни ошибок, ни возвращаемого значения из read(2), которое может быть меньше BUFSIZ. Кроме того, вам нужно количество байтов, прочитанных в коде сервера, чтобы записать только эти байты. То, как вы это делаете, очень подвержено ошибкам, поскольку вы не проверяете ошибки, исходящие от системных вызовов, которые вы делаете.   -  person Luis Colorado    schedule 24.07.2017


Ответы (2)


Короткий ответ: клиент и сервер получают «str2\0str1\0», но печатают только до первого \0, то есть «str2».

Длинный ответ на стороне клиента, который вы отправляете не только строки «str1» и «str2», но и завершающий NUL:

write(client_to_server, buf1, sizeof(buf1));

Здесь sizeof возвращает длину строки плюс завершающий NUL.

Теперь серверная часть. Вы читаете до BUFSIZ байт. Поэтому, если клиент достаточно быстр, чтобы отправить несколько строк, то есть «str2\0str1\0», вы прочитаете все строки за один раз. Чтобы исправить это, вы должны принять во внимание то, что возвращает чтение, то есть:

ssize_t ret = read(client_to_server, buf, BUFSIZ);
printf("Received: size %zd\n", ret); // print the size
fwrite(buf, 1, ret, stdout); // print the buffer with NULs
write(server_to_client,buf,ret); // write back just the data you actually received
person Andriy Berestovskyy    schedule 21.07.2017
comment
Спасибо Андрей. Таким образом, в основном при чтении из fifo считываются все данные, доступные до размера буфера. Между данными, записанными двумя операциями записи(), нет границ. Если нам нужно передавать структуры через канал, мы должны реализовать собственный протокол, чтобы различать два сообщения. Верно ли это понимание? - person aaa; 21.07.2017
comment
@user2137306 user2137306 для структур переменного размера вам определенно нужен какой-то протокол, передающий как минимум размер сообщения в заголовке сообщения. Но если размер сообщения фиксирован, вам просто нужно читать/записывать буферы размера сообщения, а не BUFSIZ. И просто проверьте, действительно ли вы читаете/записываете этот размер. - person Andriy Berestovskyy; 21.07.2017

Вы не проверяете наличие ошибок и не используете возвращаемое значение из read(2), которое говорит, сколько символов возвращается при чтении. Это странно, так как это источник несинхронизированного сервера и клиента. Я переписал ваш код таким образом, что он не дает сбоев (хотя я не проверяю возвращаемое значение write(2), которое должно давать сбой при разрыве каналов --- при убийстве сервера или клиента). Вам остается выполнить это, как упражнение.

Сосредоточьтесь на том, как строки, не заканчивающиеся нулем, обрабатываются вызовом printf(3), указав длину строки в формате "%.*s" (это уловка, позволяющая избежать помещения конечного \0 в буфер (который следует проверять, если вы читаете до sizeof buf вместо sizeof buf - 1.

server.c

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

#define F(fmt) __FILE__":%d:%s: " fmt, __LINE__, __func__

/* file scope to share with at_exit() call */
static const char myfifo[] = "/tmp/client_to_server_fifo";
static const char myfifo2[] = "/tmp/server_to_client_fifo";

void rm_fifos(void)
{
    printf(F("deleting fifo \"%s\"\n"), myfifo);
    unlink(myfifo);
    printf(F("deleting fifo2 \"%s\"\n"), myfifo2);
    unlink(myfifo2);
}

int main()
{
    int client_to_server;
    int server_to_client;
    int res;

    char buf[BUFSIZ];

    /* create the FIFO (named pipe) */
    printf(F("creating fifo \"%s\"\n"), myfifo);
    res = mkfifo(myfifo, 0666 | O_EXCL); /* O_EXCL guarantees that no second instance is created while this is running */
    if (res < 0) {
            fprintf(stderr,
                    F("%s: %s(errno=%d)\n"),
                    myfifo, strerror(errno), errno);
            exit(EXIT_FAILURE);
    }
    printf(F("creating fifo \"%s\"\n"), myfifo);
    res = mkfifo(myfifo2, 0666 | O_EXCL);
    if (res < 0) {
            fprintf(stderr,
                    F("%s: %s(errno=%d)\n"),
                    myfifo2, strerror(errno), errno);
            unlink(myfifo); /* we successfuly created it */
            exit(EXIT_FAILURE);
    }

    atexit(rm_fifos);

    /* open, read, and display the message from the FIFO */
    client_to_server = open(myfifo, O_RDONLY);
    if (client_to_server < 0) {
            fprintf(stderr,
                    F("%s: open: %s(errno=%d)\n"),
                    myfifo, strerror(errno), errno);
            exit(EXIT_FAILURE);
    }

    server_to_client = open(myfifo2, O_WRONLY);
    if (server_to_client < 0) {
            fprintf(stderr,
                    F("%s: open: %s(errno=%d)\n"),
                    myfifo2, strerror(errno), errno);
            exit(EXIT_FAILURE);
    }

    printf(F("Server ON bufsize=%d.\n"), BUFSIZ);

    while (1) {
            ssize_t n = read(client_to_server, buf, BUFSIZ);
            if (n < 0) {
                    fprintf(stderr,
                            F("%s: read: %s(errno = %d)\n"),
                            myfifo, strerror(errno), errno);
                    exit(EXIT_FAILURE);
            }
            if (n == 0) break;  /* EOF on input */

            printf("Received: [%.*s]\n", n, buf);
            printf("Sending back...\n");
            write(server_to_client, buf, n); /* very important to write just the n read chars, and not more */

            /* no need to clean buf from any data */
    }

    close(client_to_server);
    close(server_to_client);

    /* no need to unlink, as this was prepared in the atexit() library call */
    return 0;
}

клиент.c

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

#define F(fmt) __FILE__ ":%d:%s: " fmt, __LINE__, __func__

int main()
{
    int client_to_server;
    char *myfifo = "/tmp/client_to_server_fifo";
    int server_to_client;
    char *myfifo2 = "/tmp/server_to_client_fifo";

    char str[BUFSIZ];

    /* write str to the FIFO */
    client_to_server = open(myfifo, O_WRONLY);
    if (client_to_server < 0) {
            fprintf(stderr,
                    F("%s: %s(errno = %d)\n"),
                    myfifo, strerror(errno), errno);
            exit(EXIT_FAILURE);
    }
    server_to_client = open(myfifo2, O_RDONLY);
    if (server_to_client < 0) {
            fprintf(stderr,
                    F("%s: %s(errno = %d)\n"),
                    myfifo2, strerror(errno), errno);
            exit(EXIT_FAILURE);
    }

    char buf1[30] = "str1";
    char buf1_length = strlen(buf1);
    char buf2[30] = "str2";
    char buf2_length = strlen(buf2);

    while(1) {

            ssize_t n;
            //write first string
            write(client_to_server, buf1, buf1_length);
            n = read(server_to_client, str, sizeof(str));
            if (n < 0) {
                    fprintf(stderr,
                            F("%s: %s(errno = %d)\n"),
                            myfifo2, strerror(errno), errno);
                    exit(EXIT_FAILURE);
            }
            if (n == 0) break;

            printf("...received from the server: %.*s\n", n, str);

            /* NO NEED TO CLEAN THE BUFFER */

            //write second string
            write(client_to_server, buf2, buf2_length);
    }

    close(client_to_server);
    close(server_to_client);

    return 0;
}
person Luis Colorado    schedule 24.07.2017