【问题标题】:flock-ing a C++ ifstream on Linux (GCC 4.6)在 Linux (GCC 4.6) 上聚集 C++ ifstream
【发布时间】:2012-01-29 20:31:11
【问题描述】:

上下文

我正在慢慢地用 C++ 编写一个专门的 Web 服务器应用程序(使用 C onion http server libraryJSONCPP library 进行 JSON 序列化,如果这很重要的话)。对于带有 GCC 4.6 编译器的 Linux 系统(我不关心到非 Linux 系统的可移植性,或 4.5 之前的 GCC 或 3.0 之前的 Clang)。

我决定将用户“数据库”(用户很少,可能只有一两个,所以性能不是问题,并且 O(n) 访问时间是可以接受的)为 JSON 格式,可能很小JSON 对象数组,如

 { "_user" : "basile" ;
   "_crypasswd" : "XYZABC123" ; 
   "_email" : "basile@starynkevitch.net" ;
   "firstname" : "Basile" ;
   "lastname" : "Starynkevitch" ;
   "privileges" : "all" ;
 }

按照惯例(.htpasswd),_crypasswd 字段是用户密码的crypt(3)“加密”,由_user 名称加盐;

我想通过 Json 对象来描述用户的原因是我的应用程序可能会在描述用户的此类 Json 对象中添加(而不是替换)一些 JSON 字段(例如上面的 privileges)。我使用 JsonCpp 作为 C++ 的 Json 解析库。这个库想要解析 ifstream

所以我正在阅读我的密码文件

extern char* iaca_passwd_path; // the path of the password file
std::ifstream jsinpass(iaca_passwd_path);
Json::Value jpassarr;
Json::Reader reader;
reader.parse(jsinpass,jpassarr,true);
jsinpass.close();
assert (jpassarr.isArray());
for (int ix=0; ix<nbu; ix++) {
  const Json::Value&jcuruser= jpassarr[ix];
  assert(jcuruser.isObject());
  if (jcuruser["_user"].compare(user) == 0) {
    std::string crypasswd = jcuruser["_crypasswd"].asString();
    if (crypasswd.compare(crypted_password(user,password)) == 0) {
         // good user
    }
  }
}

问题

显然,我想flocklockf 密码文件,以确保只有一个进程在读取或写入它。要调用这些函数,我需要获取ifstream jsinpass 的文件描述符(用 Unix 的说法)。但谷歌主要给了我Kreckel's fileno(我觉得很完整,但有点疯狂)来获取std::ifstream的文件描述符,我不确定构造函数不会预读其中的一些。因此我的问题

如何锁定 C++ ifstream(Linux、GCC 4.6)?

(或者您是否找到其他方法来解决该问题?)

谢谢

【问题讨论】:

  • 要获取文件描述符,请尝试gcc.gnu.org/onlinedocs/libstdc++/manual/ext_io.html(免责声明:我自己没有尝试过)
  • 好吧,我不确定是否会遵循,这与 Kreckel 的解决方案有何不同?还是您建议我应该从已经 open(2)-ed 的文件描述符构造一个 std::ifstream?怎么样?
  • 您的问题之一是如何获取文件描述符。我认为 GNU 明确为此目的提供的功能会很有用。如果您想从现有文件描述符构造一个流,您是否尝试查看页面为此目的提到的 stdio_filebuf 类?
  • flock() 是咨询锁,你要的是fcntl()
  • @fge: fcntl() 也是建议性的,除非你想使用 Linux 特定的强制标志(需要挂载选项,有问题等等)。

标签: c++ linux g++ flock


【解决方案1】:

我对这个问题的解决方案来源于这个答案:https://stackoverflow.com/a/19749019/5899976

我只用 GCC 4.8.5 测试过。

#include <cstring>  // for strerror()
#include <iostream> // for std::cerr
#include <fstream>
#include <ext/stdio_filebuf.h>

extern "C" {
#include <errno.h>
#include <sys/file.h>  // for flock()
}

    // Atomically increments a persistent counter, stored in /tmp/counter.txt
int increment_counter()
{
    std::fstream file( "/tmp/counter.txt" );
    if (!file) file.open( "/tmp/counter.txt", std::fstream::out );

    int fd = static_cast< __gnu_cxx::stdio_filebuf< char > * const >( file.rdbuf() )->fd();
    if (flock( fd, LOCK_EX ))
    {
        std::cerr << "Failed to lock file: " << strerror( errno ) << "\n";
    }

    int value = 0;
    file >> value;
    file.clear();   // clear eof bit.
    file.seekp( 0 );
    file << ++value;

    return value;

    // When 'file' goes out of scope, it's closed.  Moreover, since flock() is
    //  tied to the file descriptor, it gets released when the file is closed.
}

【讨论】:

    【解决方案2】:

    您可能想要使用单独的锁定文件,而不是尝试从ifstream 获取描述符。它更容易实现,您可以将ifstream 包装在一个自动执行此操作的类中。

    如果您想确保原子打开/锁定,您可能希望使用this SO answer 中建议的方法构造一个流,遵循openflock

    【讨论】:

    • 注意:您可能不想按原样使用 Sutur 的 RWLock,它似乎包含内存泄漏并且不使用 flock/lockf
    • Suter 的 RWLock 也很活泼;我提交了一个错误,该建议已从 libstdc++ 文档中删除(给它一天左右的时间传播到网页)。
    • @janneb:也很难找到。无论如何,我都将其从我的答案中删除,因为除了使用锁定文件之外,它并没有真正说明太多。
    【解决方案3】:

    filestream API 的一个缺陷是您不能(至少不是easily)访问 fstream 的文件描述符(例如,参见 herehere)。这是因为不要求 fstream 是根据 FILE* 或文件描述符实现的(尽管实际上它总是如此)。 这对于将管道用作 C++ 流也是必需的。

    因此,“规范”答案(正如问题的 cmets 所暗示的那样)是:

    创建一个stream buffer(派生自 std::basic_streambuf),它使用 Posix 和 C stdio I/O 函数(即 open 等),从而可以访问文件描述符。

    使用基于 stdio 的流缓冲区而不是 std::streambuf 创建您自己的“LockableFileStream”(源自 std::basic_iostream)。

    您现在可能有一个类似 fstream 的类,您可以从中获得对文件描述符的访问权限,从而酌情使用 fcntl(或 lockf)。

    有一些库可以开箱即用地提供此功能。

    我原以为现在我们已经达到了 C++17,但我找不到链接,所以我一定是在做梦。

    【讨论】:

      【解决方案4】:

      依赖 rename() 的原子性的传统 unix-y 解决方案是不可接受的吗?

      我的意思是,除非您的 JSON 序列化格式支持就地更新(使用事务日志或其他),否则更新您的密码数据库需要重写整个文件,不是吗?所以不如先写到一个临时文件里,然后改名改成真名,从而保证读者读到的条目一致? (当然,为了让它工作,每个读者每次想要访问数据库条目时都必须打开()文件,让文件保持打开状态并不会切断它)

      【讨论】:

      • 如果两个进程同时尝试这样做,那么第二个进程可能会覆盖第一个进程所做的更改。 rename() 是原子的不足以避免这种情况(嗯,至少它避免了文件损坏)。
      • 希望编辑文件的进程应该重命名它,编辑它,然后重命名 ut。这是没有比赛的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多