前言
用fcntl检测锁文件实现单进程启动比用flock好。
flock在NFS服务器上不好使。
如果是在普通的linux服务器上,用fcntl和flock都行。
TPLI中讲了fcntl的用法,整理一下。封装一下,以后用。
实验
运行效果
Jan 5 17:51:18 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.48 : show_version()] : ================================================================================
Jan 5 17:51:18 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.49 : show_version()] : test_file_locker_fcntl
Jan 5 17:51:18 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.50 : show_version()] : 1.0.0.1
Jan 5 17:51:18 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.51 : show_version()] : 2018-11-11 14:04
Jan 5 17:51:18 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.52 : show_version()] : ================================================================================
Jan 5 17:51:18 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.33 : main()] : ok, lock /var/lock/my_prog/my_prog_was_exist, i was the first process
Jan 5 17:51:21 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.48 : show_version()] : ================================================================================
Jan 5 17:51:21 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.49 : show_version()] : test_file_locker_fcntl
Jan 5 17:51:21 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.50 : show_version()] : 1.0.0.1
Jan 5 17:51:21 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.51 : show_version()] : 2018-11-11 14:04
Jan 5 17:51:21 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.52 : show_version()] : ================================================================================
Jan 5 17:51:21 debian9 test_file_locker_fcntl: [MY_LOGD : ../../component/file_locker/file_locker_by_fcntl.cpp.84 : lock_file()] : lock file '/var/lock/my_prog/my_prog_was_exist' was failed : EAGAIN / Resource temporarily unavailable
Jan 5 17:51:21 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.29 : main()] : can't lock /var/lock/my_prog/my_prog_was_exist, maybe i wasn't first process
Jan 5 17:51:21 debian9 test_file_locker_fcntl: [MY_LOGD : main.cpp.41 : main()] : bye
工程下载点
工程预览
// @file linux_prj\component\file_locker\file_locker_by_fcntl.h
#ifndef __LINUX_PRJ__COMPONENT__FILE_LOCKER__FILE_LOCKER_BY_FCNTL_H__
#define __LINUX_PRJ__COMPONENT__FILE_LOCKER__FILE_LOCKER_BY_FCNTL_H__
class cls_file_locker_ex
{
public:
cls_file_locker_ex();
virtual ~cls_file_locker_ex();
bool lock_file(const char* psz_file_path_name);
private:
int lockReg(int fd, int cmd, int type, int whence, int start, off_t len);
int lockRegion(int fd, int type, int whence, int start, int len);
int lockRegionWait(int fd, int type, int whence, int start, int len);
pid_t regionIsLocked(int fd, int type, int whence, int start, int len);
private:
int m_i_fd;
const int m_i_lock_type;
bool m_b_lock_ok;
};
#endif // #ifndef __LINUX_PRJ__COMPONENT__FILE_LOCKER__FILE_LOCKER_BY_FCNTL_H__
// @file linux_prj\component\file_locker\file_locker_by_fcntl.cpp
// /usr/include/asm-generic/errno.h
// /usr/include/asm-generic/errno-base.h
#include <stdio.h> // for printf
#include <unistd.h> // for close
#include <sys/file.h> // for flock()
#include <fcntl.h> // for O_RDWR, struct flock
#include <errno.h> // for error
#include <string.h> // for strerror
#include "file_locker_by_fcntl.h"
#include "sys_log.h"
#include "error_proc.h"
// #define FILE_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
#define FILE_PERMS 0
cls_file_locker_ex::cls_file_locker_ex()
: m_i_fd(-1), m_i_lock_type(LOCK_EX | LOCK_NB), m_b_lock_ok(false)
{
// m_i_lock_type 为互斥锁 + 非阻塞, 此文件锁只能有一个主人
}
cls_file_locker_ex::~cls_file_locker_ex()
{
if (-1 != m_i_fd) {
if (m_b_lock_ok) {
flock(m_i_fd, LOCK_UN); // 解锁
}
close(m_i_fd);
m_i_fd = -1;
}
}
bool cls_file_locker_ex::lock_file(const char* psz_file_path_name)
{
bool b_rc = false;
char sz_buf[0x1000] = {'\0'};
std::string str_error_desc = "";
std::string str_errno_name = "";
do {
if (NULL == psz_file_path_name) {
break;
}
// O_EXCL 是独占
// 第一次打开文件,假设文件不存在
m_i_fd = open(psz_file_path_name, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, FILE_PERMS);
if (-1 == m_i_fd) {
if (EEXIST != errno) {
// 如果第一次打开,不是由于文件已经存在引起的打开错误,那就是真的错了
str_errno_name = errno_name(errno, str_error_desc);
MYLOG_D("'%s' open failed : %s / %s",
psz_file_path_name,
str_errno_name.c_str(),
str_error_desc.c_str());
break;
}
// 如果文件已经存在, 只用独占和读写去打开试试
m_i_fd = open(psz_file_path_name, O_RDWR | O_EXCL | O_CLOEXEC, FILE_PERMS);
if (-1 == m_i_fd) {
str_errno_name = errno_name(errno, str_error_desc);
MYLOG_D("'%s' open failed : %s / %s",
psz_file_path_name,
str_errno_name.c_str(),
str_error_desc.c_str());
break;
}
}
// lock file
if (-1 == lockRegion(m_i_fd, F_WRLCK, SEEK_SET, 0, 0)) {
str_errno_name = errno_name(errno, str_error_desc);
MYLOG_D("lock file '%s' was failed : %s / %s",
psz_file_path_name,
str_errno_name.c_str(),
str_error_desc.c_str());
break;
}
// set file length to 0
if (-1 == ftruncate(m_i_fd, 0)) {
MYLOG_D("Could not truncate PID file '%s', %s / %s",
psz_file_path_name,
str_errno_name.c_str(),
str_error_desc.c_str());
break;
}
// write pid to lock file
snprintf(sz_buf, sizeof(sz_buf) - 1, "%ld\n", (long) getpid());
if (write(m_i_fd, sz_buf, strlen(sz_buf)) != (int)strlen(sz_buf)) {
MYLOG_D("Writing to PID file '%s', %s / %s",
psz_file_path_name,
str_errno_name.c_str(),
str_error_desc.c_str());
}
b_rc = true;
} while (0);
return b_rc;
}
int cls_file_locker_ex::lockReg(int fd, int cmd, int type, int whence, int start, off_t len)
{
struct flock fl;
fl.l_type = type;
fl.l_whence = whence;
fl.l_start = start;
fl.l_len = len;
return fcntl(fd, cmd, &fl);
}
int cls_file_locker_ex::lockRegion(int fd, int type, int whence, int start, int len)
{
return lockReg(fd, F_SETLK, type, whence, start, len);
}
int cls_file_locker_ex::lockRegionWait(int fd, int type, int whence, int start, int len)
{
return lockReg(fd, F_SETLKW, type, whence, start, len);
}
pid_t cls_file_locker_ex::regionIsLocked(int fd, int type, int whence, int start, int len)
{
struct flock fl;
fl.l_type = type;
fl.l_whence = whence;
fl.l_start = start;
fl.l_len = len;
if (fcntl(fd, F_GETLK, &fl) == -1)
return -1;
return (fl.l_type == F_UNLCK) ? 0 : fl.l_pid;
}
// @file main.cpp
// @brief 测试后来的进程是否能打开文件锁, 如果打不开就对了
// @note
// 实验环境:
// debian9.6
#include <stdlib.h> // for EXIT_SUCCESS
#include <stdio.h> // for snprintf
#include "const_define.h" // for FILE_VAR_LOCK
#include "sys_log.h" // for MYLOG_D
#include "file_locker_by_fcntl.h" // for cls_file_locker
void show_version();
int main(int argc, char** argv)
{
int i_rc = EXIT_FAILURE;
cls_file_locker_ex locker;
char sz_buf[0x1000] = {'\0'};
do {
show_version();
snprintf(sz_buf, sizeof(sz_buf), "mkdir -p %s", DIR_VAR_LOCK);
system(sz_buf);
if (!locker.lock_file(FILE_VAR_LOCK)) {
MYLOG_D("can't lock %s, maybe i wasn't first process", FILE_VAR_LOCK);
break;
}
MYLOG_D("ok, lock %s, i was the first process", FILE_VAR_LOCK);
printf("runing..., press any key to quit");
getchar();
i_rc = EXIT_SUCCESS;
} while (0);
MYLOG_D("bye");
return i_rc;
}
void show_version()
{
MYLOG_D(TITLE_LINE80);
MYLOG_D(PROG_NAME);
MYLOG_D(PROG_VER);
MYLOG_D(RELEASE_TIME);
MYLOG_D(TITLE_LINE80);
}