Блокировка текстового файла, пока другой дочерний процесс записывает в него

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

    FILE *Ufptr;
    Ufptr = fopen("Unsorted_busy_list.txt","a+");

    fprintf(Ufptr, "%s:%d\n",inet_ntoa(newAddr.sin_addr),ntohs(newAddr.sin_port));
    fclose(Ufptr);

Мне сказали, что можно использовать блокировку fcntl и mutex для файла, но я новичок в этом и не знаю, как реализовать это в процессе записи файла. Любая помощь


person Community    schedule 21.07.2018    source источник
comment
Это может помочь: stackoverflow.com/questions/7552451/   -  person Ruud Helderman    schedule 22.07.2018
comment
Большинство блокировок файлов в системах POSIX являются «рекомендательными», а не «обязательными». Вам понадобится межпроцессный мьютекс, а не мьютекс внутри процесса (потому что у вас есть процессы, предоставленные fork(), а не потоки).   -  person Jonathan Leffler    schedule 22.07.2018
comment
Вместо обычного файла вам следует рассмотреть возможность использования пары сокетов дейтаграммы домена Unix (созданной для int fd[2]; через socketpair(AF_UNIX, SOCK_DGRAM, 0, fd)), возможно, по одной паре сокетов на каждого потомка. Сокет дейтаграммы домена Unix сохраняет границы сообщений (поэтому каждый успешный send() принимается с использованием только одного recv(). Вы даже можете отправлять/получать двоичные сообщения, скажем, пары struct in_addr или struct in6_addrs. Блокировка не требуется, а простое неблокирующее select()/poll()+ recv() цикл в родительском элементе может без проблем получать данные от нескольких клиентов.   -  person Nominal Animal    schedule 22.07.2018


Ответы (1)


Как я уже упоминал в комментарии, если родитель получает вывод от дочерних процессов, обычно проще использовать пару сокетов дейтаграммы домена Unix (или пару для каждого дочернего процесса). Сокеты дейтаграмм домена Unix сохраняют границы сообщений, так что каждая дейтаграмма, успешно отправленная с использованием send(), принимается в одном recv(). Вы даже можете отправлять данные в виде двоичных структур. Никаких замков не нужно. Если вы используете пару сокетов для каждого дочернего элемента, вы можете легко установить неблокирующую родительскую сторону и использовать select() или poll() для чтения дейтаграмм со всех в одном цикле.


Вернемся к собственно вопросу.

Вот пример реализации append_file(filename, format, ...), использующей POSIX.1-2008 vdprintf()< /a> для записи в файл с использованием рекомендуемых блокировок записей на основе fcntl():

#define  _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>

int append_file(const char *filename, const char *format, ...)
{
    struct flock  lock;
    va_list       args;
    int           fd, cause;

    /* Sanity checks. */
    if (!filename || !*filename)
        return errno = EINVAL;

    /* Open the file for appending. Create if necessary. */
    fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, 0666);
    if (fd == -1)
        return errno;

    /* Lock the entire file exclusively.
       Because we use record locks, append_file() to the same
       file is NOT thread safe: whenever the first descriptor
       is closed, all record locks to the same file in the process
       are dropped. */
    lock.l_type = F_WRLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    if (fcntl(fd, F_SETLKW, &lock) == -1) {
        cause = errno;
        close(fd);
        return errno = cause;
    }

    if (format && *format) {
        cause = 0;
        va_start(args, format);
        if (vdprintf(fd, format, args) < 0)
            cause = errno;
        va_end(args);
        if (cause) {
            close(fd);
            return errno = cause;
        }
    }

    /* Note: This releases ALL record locks to this file
             in this process! */
    if (close(fd) == -1)
        return errno;

    /* Success! */
    return 0;
}

int main(int argc, char *argv[])
{
    int arg = 1;

    if (argc < 3) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s FILENAME STRING [ STRING ... ]\n", argv[0]);
        fprintf(stderr, "\n");
    }

    for (arg = 2; arg < argc; arg++)
        if (append_file(argv[1], "%s\n", argv[arg])) {
            fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
            return EXIT_FAILURE;
        }

    return EXIT_SUCCESS;
}

Если все писатели используют указанный выше append_file() для добавления к файлу, можно безопасно переименовать файл в любой момент. (Обратите внимание, что один или несколько процессов могут выполнить последнее добавление к файлу после переименования, если они ожидали снятия блокировки записи во время переименования.)

Чтобы обрезать файл, сначала установите на него монопольную блокировку, а затем вызовите ftruncate(fd, 0).

Чтобы прочитать файл, используйте общую блокировку на основе fcntl() F_RDLCK (разрешая другим читателям одновременно; или F_WRLCK, если вы намерены «атомарно» обрезать файл после того, как прочитали текущее содержимое), или вы можете увидеть частичная итоговая запись в конце.

person Nominal Animal    schedule 22.07.2018
comment
Спасибо @Norminal Animal. Но действительно ли это вышеприведенное решение также применимо к Linux? - person ; 22.07.2018
comment
@MikeM: Да, это так. (На самом деле я написал и протестировал его на Linux.) - person Nominal Animal; 22.07.2018
comment
Спасибо @Norminal Animal - person ; 26.07.2018