在另一个子进程写入文本文件时锁定文本文件
Locking a text file while another child process writes to it
我在 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 和互斥锁,但我是新手,不知道如何在文件写入过程中实现这一点。
任何帮助
正如我在评论中提到的,如果 parent 使用 children 的输出,通常使用 Unix 域数据报套接字对(或每对 child 过程)。 Unix 域数据报套接字保留消息边界,因此使用 send()
成功发送的每个数据报都在单个 recv()
中接收。您甚至可以将数据作为二进制结构发送。不需要锁。如果每个 child 使用套接字对,则可以轻松地将 parent 端设置为非阻塞,并使用 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
,如果你打算"atomically" 阅读当前内容后截断文件),否则您可能会在末尾看到部分最终记录。
我在 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 和互斥锁,但我是新手,不知道如何在文件写入过程中实现这一点。 任何帮助
正如我在评论中提到的,如果 parent 使用 children 的输出,通常使用 Unix 域数据报套接字对(或每对 child 过程)。 Unix 域数据报套接字保留消息边界,因此使用 send()
成功发送的每个数据报都在单个 recv()
中接收。您甚至可以将数据作为二进制结构发送。不需要锁。如果每个 child 使用套接字对,则可以轻松地将 parent 端设置为非阻塞,并使用 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
,如果你打算"atomically" 阅读当前内容后截断文件),否则您可能会在末尾看到部分最终记录。