问题描述
我正在使用 tdb 尝试熟悉 Linux 上 C 中的数据库管理。每tdb's description
tdb 是一个普通的数据库。
在概念上,它非常像 GDBM 和 BSD 的 DB 除了它 允许多个同时写入并在内部使用锁定 防止作家互相践踏。 tdb也极其 小的。界面
界面与gdbm非常相似,除了以下几点:
- 不同的开放界面。 tdb_open 调用更类似于传统的 open()
- 没有 tdbm_reorganise() 函数
- 没有 tdbm_sync() 函数。无论如何,库中不会缓存任何操作
- 增加了交易支持
使用 tdb 的一般规则是调用者释放任何返回的 TDB_DATA 结构。只需调用 free(p.dptr) 即可释放 TDB_DATA 返回值 称为 p 的值。这与 gdbm 相同。
现在我想制作一个小测试程序来测试到我的数据库的多个写连接。但它失败了。
#include <tdb.h>
int main(int argc,char** argv) {
struct tdb_context * tdb = tdb_open("test.tdb",O_RDWR | O_CREAT,S_IRUSR | S_IWUSR | S_IRGRP);
if(!tdb){
printf("%s\n",tdb_errorstr(tdb));
} else {
printf("Database successfully opened!\n");
}
struct tdb_context * anothertdb = tdb_open("test.tdb",O_RDWR| O_CREAT,S_IRUSR | S_IWUSR | S_IRGRP); //why does this fail?
if(!anothertdb){
printf("%s\n",tdb_errorstr(anothertdb));
} else {
printf("Another reader successfully opened!\n");
}
if(tdb_close(anothertdb)){
printf("Error while closing database!\n");
} else {
printf("closing anothertdb-connection\n");
}
if(tdb_close(tdb)){
printf("Error while closing database!\n");
} else {
printf("closing tdb-connection\n");
}
return 0;
}
输出为:
Database successfully opened!
RUN FINISHED; Segmentation fault; core dumped; real time: 240ms; user: 0ms; system: 20ms
如果我只打开一个到数据库的单一连接,则没有问题。如何在一个数据库上打开多个阅读器?
解决方法
您遇到的第一个问题是在 tdb_errorstring
失败后调用 tdb_open
:
if(!anothertdb){
printf("%s\n",tdb_errorstr(anothertdb));
}
这行不通可能并不明显,但有充分的理由怀疑它行不通,因为当调用 anothertdb
时,NULL
肯定是 tdb_errorstr
,并且假设 tdb_errorstr
的参数必须是指向 struct tdb_context
的有效指针是合理的。事实上,这是真的。实际上,tdb_errorstring
所做的第一件事就是尝试取消引用它的参数以提取最后一个错误代码。
简而言之,无法从 tdb_open
失败的结果中提取自定义错误字符串;您所能做的就是从 NULL
返回 fopen()
:调用 perror()
,希望 errno
设置为有意义的值。如果这样做,您将看到一条错误消息而不是崩溃:
$ ./tdb_test
Database successfully opened!
open 2: Device or resource busy
该库似乎有一种方法可以将 tdb_open
错误发送到已配置的日志记录系统,但默认配置的日志记录系统只是将错误调用抛出而不执行任何操作。我没有阅读文档以查看是否有关于如何配置记录器的描述;你可能想研究一下。
顺便说一下,我不是在为这种 API 设计辩护。只是报告一下。
好的,现在开始您的实际问题:您如何打开与 TDB 数据库的多个连接?答案是,您必须从多个流程中做到这一点。单个进程可以打开任意数量的 TDB,但它只能打开每个 TDB(一次)。根据源代码中的评论,这是 fcntl()
锁定工作方式的结果,这对我来说似乎是一个合理的解释。
因此,如果您想尝试多读取器、多写入器的场景,您将不得不运行多个进程,或者从父进程派生多个子进程。线程将不起作用,因为同一进程中的两个线程也无法打开同一个 TDB。 每个进程打开一个。
另外,如果你选择fork多个子进程,记住只有在你fork之后才需要调用tdb_open
,因为如果你在父进程中打开一个TDB,它在子进程中仍然是打开的。
最后要注意的是,请养成将警告和错误消息写入 stderr
的习惯,它通常不会被缓冲,以便您在输出时更有可能看到错误消息,而不是稍后 stdio 库选择刷新 stdout 输出缓冲区。这实际上不是问题,但它可能已经存在并且您最好为此目的使用 stderr
(这是它的预期目的,因此得名)。