我正在慢慢地用C ++编写一个专门的Web服务器应用程序(使用C onion http server library和JSONCPP 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" ;
}
使用约定(àla.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
}
}
}
显然,我想要flock
或lockf
密码文件,以确保只有一个进程正在读取或写入它。要调用这些函数,我需要获取ifstream jsinpass
的文件描述符(用Unix术语)。但谷歌给了我大部分Kreckel's fileno(我发现它完整,但有点疯狂)来获取std::ifstream
的文件描述符,我不确定构造函数是否会预读它的一些。因此我的问题:
ifstream
(Linux,GCC 4.6)?(或者您是否找到了解决该问题的其他方法?)
由于
答案 0 :(得分:1)
您可能希望使用单独的锁文件,而不是尝试从ifstream
获取描述符。它实现起来要容易得多,你可以将ifstream
包装在一个自动化的类中。
如果您想确保原子打开/锁定,您可能希望使用this SO answer中建议的方法,open
和flock
答案 1 :(得分:1)
使用filestream API的一个缺点是您不能(至少不easily)访问fstream的文件描述符(例如,请参阅here和here)。这是因为没有要求fstream以FILE *或文件描述符的形式实现(尽管在实践中它总是如此)。 将管道用作C ++流也是必需的。
因此,“规范”答案(如对问题的评论中暗示的)是:
创建一个stream buffer(派生自std :: basic_streambuf),它使用Posix和C stdio I / O函数(即打开等),从而可以访问文件描述符。
使用基于stdio的流缓冲区而不是std :: streambuf创建自己的'LockableFileStream'(从std :: basic_iostream派生)。
您现在可以拥有类似fstream的类,您可以从中获取对文件描述符的访问权限,因此可以根据需要使用fcntl(或lockf)。
有一些库可以提供开箱即用的功能。
我原本以为这已经解决了,因为我们已经达到了C ++ 17但是我找不到链接所以我一定是梦想它。
答案 2 :(得分: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.
}
答案 3 :(得分:0)
传统的unix-y解决方案依赖于重命名()的原子性是不可接受的吗?
我的意思是,除非您的JSON序列化格式支持就地更新(使用事务日志或其他),否则更新密码数据库需要重写整个文件,不是吗?所以你不妨将它写入临时文件,然后将其重命名为真实名称,从而确保读者阅读一致的条目? (当然,为了使其工作,每个读者必须在每次想要访问数据库条目时打开()文件,保持文件打开不会削减它)