当多个进程对一个文件进行操作时,常常需要添加保护,这时就需要用到文件锁。
范例内容:一个进程对.tfreq.ini进行写操作,当这个进程在写的过程中,不允许其它进程对文件进行操作,当它结束写之后,另一个线程再读取其中的内容。
锁的类lock.h:
class file_locker
{
public:
bool file_lock(int file_fd) //加锁
{
struct flock my_lock;
my_lock.l_type = F_WRLCK; //F_WRLCK,F_RDLCK,F_UNLCK分别表示:写锁,读锁,解锁.只有写锁是独占锁,读锁是共享锁,允许多个进程使用
my_lock.l_whence = SEEK_SET; //SEEK_SET,SEEK_CUR,SEEK_END分别表示设置位置为:文件开头,当前指针位置,文件尾
my_lock.l_start = 0; //相对于l_whence的偏移位置
my_lock.l_len = 0; //加锁区域的长度
if ( fcntl(file_fd,F_SETLKW,&my_lock) >= 0) //F_SETLKW表示阻塞,另一个线程加锁会阻塞等待
{
return true;
}
else
{
char *error_message = strerror(errno);
printf("lock error: %s\n",error_message);
return false;
}
}
bool file_unlock(int file_fd) //解锁
{
struct flock my_lock;
my_lock.l_type = F_UNLCK;
if ( fcntl(file_fd,F_SETLKW,&my_lock) >= 0)
{
return true;
}
else
{
return false;
}
}
bool get_lock(int file_fd) //得到锁状态
{
struct flock my_lock;
fcntl(file_fd,F_GETLK,&my_lock);
if(my_lock.l_type != F_UNLCK) //被锁住
{
if (my_lock.l_type == F_RDLCK)
{
printf("Read lock already set by %d\n",my_lock.l_pid);
}
else if (my_lock.l_type == F_WRLCK)
{
printf("Write lock already set by %d\n",my_lock.l_pid);
}
}
}
};
写write.cpp:
#include "../common.h"
#include "./lock.h"
int main()
{
int fd = open(".tfreq.ini",O_RDWR|O_CREAT|O_APPEND,0666);
if(fd<0)perror(".tfreq.ini");
file_locker * test_lock = new file_locker();
test_lock->get_lock(fd);
test_lock->file_lock(fd);
int i = 0;
char buf[16]={0};
while(true)
{
sprintf(buf,"i = %d\n",i++);
if (dprintf(fd,"%s",buf)>0)
{
printf("write success:i=%d\n",i-1);
}
sleep(1);
if(i == 15)break;
}
test_lock->file_unlock(fd);
close(fd);
}
read.cpp:
#include "../common.h"
#include "./lock.h"
int main()
{
int fd = open(".tfreq.ini",O_RDWR); //注意这里是RDWR,因为我这儿用的是写锁,如果用RDONLY,下面的锁不会阻塞
if(fd < 0)
{
perror("open .tfreq.ini");
}
file_locker * test_lock = new file_locker();
test_lock->get_lock(fd);
if(test_lock->file_lock(fd) == false)
{
printf("lock error ... \n");
}
char buf[4096] = {0};
read(fd,buf,4096);
printf("buf = :%s\n",buf);
test_lock->file_unlock(fd);
close(fd);
}
运行结果:(文件内容原本为空)
可以看出,当右边的write运行结束之后,read程序才开始读取,符合我的预期结果。
注意:关闭文件会自动把锁解除。
实际上fnctl函数还有很多用法和参数,这里不再一 一例举:
https://blog.youkuaiyun.com/rl529014/article/details/51336161
http://www.hqyj.com/news/emb171.htm
另外,记录一下有关文件操作的其他函数.cpp:
#include "fileManager.h"
bool FileManager::GetIniKeyString(char *dst, const char *title, const char *key, const char *filename)
{
FILE * fp = NULL;
int flag = 0;
char sTitle[32], *wTmp;
static char sLine[1024]={0};
sprintf(sTitle, "[%s]", title);
if (NULL == (fp = fopen(filename, "r")))
{
// fclose(fp);
cout<<"fopen "<<filename<<" error"<<endl;
return false;
}
else
{
while (NULL != fgets(sLine, 1024, fp))
{
// 这是注释行
if (0 == strncmp("//", sLine, 2))
continue;
if ('#' == sLine[0])
continue;
wTmp = strchr(sLine, '=');
if ((NULL != wTmp) && (1 == flag))
{
if (0 == strncmp(key, sLine, strlen(key)))
{ // 长度依文件读取的为准
if ((sLine[strlen(sLine) - 2] == '\r')||(sLine[strlen(sLine) - 2] == '\n'))
{
sLine[strlen(sLine) - 2] = '\0';
}
else if ((sLine[strlen(sLine) - 1] == '\r') || (sLine[strlen(sLine) - 1] == '\n'))
{
sLine[strlen(sLine) - 1] = '\0';
}
fclose(fp);
strcpy(dst, wTmp + 1);
return true;
}
}
else
{
if (0 == strncmp(sTitle, sLine, strlen(sTitle)))
{ // 长度依文件读取的为准
flag = 1; // 找到标题位置
}
}
}
}
fclose(fp);
return false;
}
bool FileManager::GetIniKeyFloatArray(const char *title, const char *key, float *array, int arraySize, const char *filename)
{
FILE * fp = NULL;
int flag = 0;
char sTitle[32];
static char sLine[1024];
char * p;
int i = 0;
sprintf(sTitle, "[%s]", title);
if (NULL == (fp = fopen(filename, "r")))
{
fclose(fp);
perror("fopen(getfloat)");
cout<<"fopen "<<filename<<" error"<<endl;
return false;
}
else
{
while (NULL != fgets(sLine, 1024, fp))
{
// 这是注释行
if (0 == strncmp("//", sLine, 2))
continue;
if ('#' == sLine[0])
continue;
if ((1 == flag) && 0 == strncmp(sLine, key, strlen(key))) //找到键值位置
{
p = strtok(sLine + strlen(key) + 1, ",");
while (i < arraySize)
{
if (p != NULL && *p != '\n' && *p != '\r')
{
array[i] = atof(p);
p = strtok(NULL, ",");
i++;
}
else
{
if (NULL == fgets(sLine, 1024, fp))
{
fclose(fp);
return false;
}
else
p = strtok(sLine, ",");
}
}
fclose(fp);
return true;
}
else
{
if (0 == strncmp(sTitle, sLine, strlen(sTitle)))
{ // 长度依文件读取的为准
flag = 1; // 找到标题位置
}
}
}
}
fclose(fp);
return false;
}
long long FileManager::GetIniKeyInt(const char *title, const char *key, const char *filename)
{
char str[1024];
if (GetIniKeyString(str, title, key, filename) == true)
return strtoll(str, NULL, 0);
else
return -1;
}
double FileManager::GetIniKeyFloat(const char *title, const char *key, const char *filename)
{
char str[1024];
if (GetIniKeyString(str, title, key, filename)==true)
return atof(str);
else
return -1;
}
bool FileManager::update_param_key(const char *fileName,const char*key,const char*data)
{
long long temp = pthread_self();
//printf("------------- temp:%lld \n",temp);
FILE *fp = fopen(fileName,"r+");
char bufName[128]={0};
sprintf(bufName,"%s_%lld.ini",key,temp);
if(fp == NULL)
{
perror("fopen(up1)");
return false;
}
FILE *fp2 = fopen(bufName,"w+");
if(fp2 == NULL)
{
perror("fopen(up2)");
fclose(fp);
return false;
}
char check_buf[1024]={0};
while(fgets(check_buf,1024,fp) != NULL)
{
if(strncmp(check_buf,key,strlen(key)) == 0)
{
memset(check_buf,0,1024);
sprintf(check_buf,"%s=%s\n",key,data);
}
fprintf(fp2,"%s",check_buf);
}
fclose(fp);
fclose(fp2);
//memset(check_buf,0,1024);
//sprintf(check_buf,"rm -r %s", fileName);
//system(check_buf);
memset(check_buf,0,1024);
sprintf(check_buf,"mv %s %s", bufName,fileName);
system(check_buf);
return true;
}
bool FileManager::get_all_files(const string& dir_in, vector<string>& files)
{
if (dir_in.empty())
{
return false;
}
struct stat s;
stat(dir_in.c_str(), &s);
if (!S_ISDIR(s.st_mode))
{
return false;
}
DIR* open_dir = opendir(dir_in.c_str());
if (NULL == open_dir)
{
std::exit(EXIT_FAILURE);
}
dirent* p = NULL;
while( (p = readdir(open_dir)) != NULL)
{
struct stat st;
if (p->d_name[0] != '.')
{
string name = dir_in + string("/") + string(p->d_name);
stat(name.c_str(), &st);
if (S_ISDIR(st.st_mode))
{
get_all_files(name, files);
}
else if (S_ISREG(st.st_mode))
{
files.push_back(name);
}
}
}
closedir(open_dir);
return true;
}
bool FileManager::GetFilePath(string*filePath,const char*fileName)
{
char file_path[256]={0};
if(realpath(fileName, file_path))
{
*filePath = file_path;
return true;;
}
else
{
return false;
}
}
void FileManager::file_changed(const char*fileName)
{
int fd = 0;
char data[128] = {0};
while(file_check(fileName) == false) //当用O_TRUNC打开文件清空以后,会立马被监测到,这个时候可能还未填写数据,所以应该避免这种情况
{
sleep(2);
}
fd = inotify_init();//初始化fd
inotify_add_watch(fd, fileName, IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVE );//监听文件是否被改变
read(fd, data, 128);//没改变,那么就会阻塞,当有事件发生时,notify文件描述符会变为可读,调用read()可以读取发生的事件
}
bool FileManager::file_check(const char*file_name)
{
int ret = access(file_name,F_OK);
if(ret<0)
{
return false;
}
else
{
return true;
}
}
int64_t FileManager::getfile_size(const char*file_name)
{
struct stat m_st;
stat(file_name, &m_st);
return m_st.st_size;
}
.h:
#ifndef _FILEMANAGER_H_
#define _FILEMANAGER_H_
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "../urd_common.h"
#include <limits.h>
#include <sys/inotify.h>
class FileManager
{
public:
/**
* @description: 读取配置文件信息
* @param {dst:读取到的字符串地址; title:配置文件中的标题[]; key:关键字; filename:配置文件名; array:数组地址; arraySize:数组大小}
* @return: 成败标志/long或float值
* @author: Feng Chao
*/
bool GetIniKeyString(char *dst, const char *title, const char *key, const char *filename);
bool GetIniKeyFloatArray(const char *title, const char *key, float *array, int arraySize, const char *filename);
long long GetIniKeyInt(const char *title, const char *key, const char *filename);
double GetIniKeyFloat(const char *title, const char *key, const char *filename);
/**
* @description: 获取文件路径
* @param {filePath:文件路径; filename:需要被获取路径的文件名}
* @return: 成败标志
* @author: Feng Chao
*/
bool GetFilePath(string*filePath,const char*filename);
/**
* @description: 更新配置文件中的某个关键字的值
* @param {fileName:配置文件名; key:关键字; data:更新数据}
* @return: 成败标志
* @author: Feng Chao
*/
bool update_param_key(const char *fileName,const char*key,const char*data);
/**
* @description: 获取某文件夹下的所有文件名
* @param {dir_in:文件夹名; files:存储所有文件名的容器地址}
* @return:
* @author: Feng Chao
*/
bool get_all_files(const string& dir_in, vector<string>& files); //得取文件夹下所有文件
/**
* @description: 本函数用于判断文件是否被改变,是[阻塞函数],文件被改变阻塞结束,建议放在独立线程中使用
* @param {fileName:文件名}
* @return:
* @author: Feng Chao
*/
void file_changed(const char*fileName);
/**
* @description: 检验文件是否存在(文件名可包括路径)
* @param {fileName:文件名}
* @return: 是否存在标志
* @author: Feng Chao
*/
bool file_check(const char*file_name);
/**
* @description: 得到文件大小
* @param {fileName:文件名}
* @return:
* @author: Feng Chao
*/
int64_t getfile_size(const char*file_name);
};
#endif // !_FILEMANAGER_H_