Попытка передать данные с сервера дочернего процесса в его родительский процесс

Я работаю над заданием для своего класса по распределенным системам. Я учусь в магистратуре по компьютерным наукам, но моя специальность в программировании — .NET, и я работаю над проектом, который требует довольно глубоких знаний Unix, что ставит меня в тупик.

Задание реализует API протокола флеш-канала. Поэтому я пишу небольшую библиотеку функций, которую другие приложения могут реализовать для использования обмена данными по каналу. Я настроил его так, что при вызове функции инициализации он разветвляет дочерний процесс, который действует как сервер для входящих сообщений. Дочерний процесс взаимодействует с родительским процессом, отправляя входящие данные родительскому процессу через канал.

Это работает нормально, если сообщения отправляются и принимаются по одному; например.,

отправить -> получить -> отправить -> получить -> и т.д.

Однако, если несколько сообщений отправлены до выполнения каких-либо приемов; например.,

отправить -> отправить -> отправить -> получить

потом запутается. Конкретно первое сообщение принимается корректно, но когда захожу получать второе сообщение, программа зависает и ее нужно убивать. Я много искал в Интернете и часами занимался этим, но не добился большого прогресса.

Программа в целом слишком велика, чтобы показывать ее здесь, но вот наиболее важные фрагменты. Вот часть, где я запускаю сервер и получаю сообщения. Обратите внимание на строку

write(fd[1], буфер, (strlen(буфер)+1));

-- Я думаю, что это хороший кандидат на роль источника проблемы здесь, но не уверен, что делать по-другому. (Пробовал fwrite(), и это вообще не сработало.)

    fd = malloc(2 * sizeof(int));
    int nbytes;
    if (pipe(fd) < 0) {
        perror("Could not create pipe");
        return -1;
    }

    pID = fork();

    if (pID < 0) {
        perror("Failed to fork");
        return -1;
    } else if (pID == 0) { // child
        close(fd[0]);  // close input side of pipe
        int cc;
        int fsize;
        struct sockaddr_in from;
        int serials[500];
        int i;
        for (i = 0; i < 500; i++) serials[i] = 0;

        char buffer[2048];

        while (1) {
            fsize = sizeof(from);
            cc = recvfrom(socketNo, buffer, 2048, 0, (struct sockaddr*)&from, &fsize);
            if (cc < 0) perror("Receive error");
            datagram data = decodeDatagram(buffer);
            if (serials[data.serial] == 0) {
                write(fd[1], buffer, (strlen(buffer)+1));
                serials[data.serial] = 1;
            }
        }
    } else { // parent
        close(fd[1]);  // close output side of pipe
        return 0;
    }

(Массив «serials» предназначен для того, чтобы не пересылать повторяющиеся сообщения, поскольку сообщения отправляются несколько раз для повышения надежности. Я знаю, что фиксированный размер для этого массива не является хорошей практикой, но тесты для этого назначения не отправляют столько сообщений, поэтому в данном контексте нормально)

Начало функции приема выглядит так:

int fRecv(int* id, char* buf, int nbytes) {

    checkDatagramTable(*id);

    char* tbuf = malloc((nbytes + 9) * sizeof(char));
    int rbytes = read(fd[0], tbuf, nbytes + 9);

«+9» предназначен для размещения дополнительной информации, которая упаковывается вместе с отправляемым сообщением для упорядочивания каналов с флешем. Это также довольно схематичная область, но выделение большего пространства для большей уверенности не помогло решить проблему.

Я знаю, что здесь довольно много посторонних вещей, ссылок на другие функции и т. д. Но проблема, безусловно, заключается в том, как я передаю данные, поэтому источник моей проблемы должен лежать где-то там.

Заранее спасибо за вашу помощь; это действительно ценится.


person user1056100    schedule 20.11.2011    source источник
comment
Вы выделяете новый buffer каждый проход по циклу while(1) — почему? Я не понимаю, почему он распределяется динамически, а не распределяется в стеке, и я не понимаю, почему он сохраняется вне этой функции.   -  person sarnold    schedule 20.11.2011
comment
Между прочим, strace(1) может быть действительно полезным при поиске проблем. Это как бесплатная printf(3) строка для каждого системного вызова.   -  person sarnold    schedule 20.11.2011
comment
Я не помню, почему я сделал это именно так, но, по-видимому, на это есть веская причина, потому что изменение его на размещение в стеке заставляет компилятор выдавать очень зловещие предупреждения. Почему? Как вы думаете, это связано с моей проблемой?   -  person user1056100    schedule 20.11.2011
comment
Мне любопытны предупреждения, которые вы получаете, но это не должно быть ошибкой, которая вас поймала, это просто утечка памяти. Вы, вероятно, даже не заметили бы этого за несколько сотен тысяч сообщений.   -  person sarnold    schedule 20.11.2011
comment
Оказывается, предупреждения были из-за того, что я испортил изменение. Редактировать код в 2:30 ночи — не такая уж хорошая идея — думаю, я пойду спать и займусь этим еще утром.   -  person user1056100    schedule 20.11.2011
comment
Ах да, и спасибо за упоминание о strace(); если я не получу эту работу в ближайшее время, я думаю, что попробую.   -  person user1056100    schedule 20.11.2011


Ответы (1)


Это выглядит подозрительно. (что находится в пакетах? Они могут быть двоичными) Где находится определение типа дейтаграммы?

fsize = sizeof(from);
        cc = recvfrom(socketNo, buffer, 2048, 0, (struct sockaddr*)&from, &fsize);
        if (cc < 0) perror("Receive error");
        datagram data = decodeDatagram(buffer);
        if (serials[data.serial] == 0) {
            write(fd[1], buffer, (strlen(buffer)+1)); // <-- ????
            serials[data.serial] = 1;
        }

Я бы попробовал вместо этого:

            write(fd[1], buffer, cc);

ОБНОВИТЬ:

Если сообщение не завершено нулем, вам придется завершить его явно:

    (if cc == 2048) cc -= 1; 
    buffer [cc] = '\0'; // <<--
    datagram data = decodedatagram(buffer);
    ...

Также рекомендуется использовать «sizeof buffer» вместо «2048».

ОБНОВЛЕНИЕ 2: Вы можете проверить, действительно ли строки в пакетах заканчиваются нулем:

        unsigned pos;
        cc = recvfrom(socketNo, buffer, 2048, 0, (struct sockaddr*)&from, &fsize);
        if (cc < 0) perror("Receive error");
        for pos=0; pos < cc; pos++) {
             if (buff[pos] == 0) break;
             }
        switch (cc-pos) {
        case 0: fprintf (stderr, "No nul byte found in packet: I lose!\n" ); break;
        default: fprintf (stderr, "Spurious nul byte found in the middle of packet\n" );
        case 1: break;
             }

        datagram data = decodeDatagram(buffer);
        if (serials[data.serial] == 0) {
            write(fd[1], buffer, cc);
            serials[data.serial] = 1;
        }
person wildplasser    schedule 20.11.2011
comment
Пакеты гарантированно являются символьными данными; это часть описания задания - извините, я должен был указать это в своем вопросе. В любом случае, после его изменения его поведение остается прежним - нормально работает, когда данные отправляются и принимаются последовательно, но когда сразу отправляется куча пакетов, прежде чем пользовательское приложение отправится на их получение, оно зависает при попытке получить второй пакет. - person user1056100; 20.11.2011
comment
Они могут быть символьными, но заканчиваются ли они нулем? Добавляет ли отправитель явно '\0' в конце каждого пакета? Кроме того: зачем вам вызывать strlen(), если вы знаете, что пакеты заканчиваются нулем? - person wildplasser; 20.11.2011
comment
Это все хорошие моменты, но, как я уже сказал, я изменил строку на ту, которую вы предложили, и это не изменило поведение. - person user1056100; 20.11.2011
comment
Вы так и не ответили на вопрос, завершаются ли полученные ascii-данные нулем? В любом случае: я обновил свой ответ. - person wildplasser; 20.11.2011
comment
Да, это так. Вот почему это было strlen + 1, а не просто strlen. - person user1056100; 20.11.2011
comment
Hexdump данные о приеме, просто для уверенности. - person wildplasser; 20.11.2011