【问题标题】:Locking a text file while another child process writes to it在另一个子进程写入时锁定文本文件
【发布时间】:2018-07-21 22:08:14
【问题描述】:

我在 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 和互斥锁可以做到,但我对此很陌生,不知道如何在文件写入过程中实现这一点。 任何帮助

【问题讨论】:

  • 这可能会有所帮助:stackoverflow.com/questions/7552451/…
  • POSIX 系统中的大多数文件锁定是“建议性的”而不是“强制性的”。您需要一个进程间互斥锁,而不是进程内互斥锁(因为您有由fork() 提供的进程而不是线程)。
  • 您应该考虑使用 Unix 域数据报套接字对(通过 socketpair(AF_UNIX, SOCK_DGRAM, 0, fd) 创建到 int fd[2];)而不是一个普通文件,每个孩子可能有一个套接字对。 Unix 域数据报套接字保留消息边界(因此每个成功的send() 都使用一个recv() 接收。您甚至可以发送/接收二进制消息,例如一对struct in_addrstruct in6_addrs。不需要锁定,并且父级中的简单非阻塞select()/poll()+recv() 循环可以毫无问题地从多个客户端接收。

标签: c linux fork file-writing file-locking


【解决方案1】:

正如我在评论中提到的,如果父级使用子级的输出,通常更容易使用 Unix 域数据报套接字对(或每个子进程对)。 Unix 域数据报套接字保留消息边界,因此使用send() 成功发送的每个数据报都会在单个recv() 中接收。您甚至可以将数据作为二进制结构发送。不需要锁。如果您为每个孩子使用一个套接字对,您可以轻松地将父端设置为非阻塞,并使用select()poll() 在单个循环中从所有数据报中读取数据报。


回到正题。

下面是append_file(filename, format, ...) 的示例实现,它使用POSIX.1-2008 vdprintf() 写入文件,使用基于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,如果您打算在读取文件后“原子地”截断文件当前内容),或者您可能会在末尾看到部分最终记录。

【讨论】:

  • 感谢@Norminal Animal。但是上面的这个解决方案在linux上也有效吗?
  • @MikeM:是的。 (事实上​​,我是在 Linux 上编写并测试过的。)
  • thanx @Norminal Animal
猜你喜欢
  • 2013-03-01
  • 2015-03-16
  • 2015-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-23
  • 1970-01-01
相关资源
最近更新 更多