티스토리 뷰
보통 c언어에서는 데이터 셋을 구조체를 통해 구현한다.
다른 언어가 객체와 같이 key-value 쌍으로 유연하게 속성을 추가하고, 관리할 수 있다.
그러나 c는 구조페를 선언할때 사용할 맴버를 미리 정해야하고, 구조체 배열에서 CRUD을 수행하려면 배열을 전부 순회하거나 새로 만드는 형식으로 밖에 구현이 불가능하다.
때문에 이번 프로젝트에서는 해쉬 테이블을 사용하여 CRUD를 좀 더 간편하고, 빠르게 구현할 것이다.
해쉬 테이블 구성
해쉬 테이블에는 uthash라는 라이브러리를 사용하며, UT_hash_handle라는 속성을 구조체에 추가하여 구현하였다.
struct file_list
{
char *path;
char *file_name;
unsigned long file_path_size;
unsigned long file_data_size;
unsigned char check_sum[SHA256_DIGEST_LENGTH];
long update_time;
int state;
UT_hash_handle hh;
};
typedef struct file_list file_list_t;
다음은 동기화 리스트를 읽고, 데이터를 저장할 구조체의 정의이다.
각 맴버의 속성을 설명하자면,
- char *path: 파일이 저장된 경로. 파일을 탐색할 대 사용한다.
- char *file_name : 파일명. 전송시 보내진다.
- unsigned long file_path_size : 파일명의 길이.
- unsigned long file_data_size : 파일 데이터 크기
- unsigned char check_sum[SHA256_DIGEST_LENGTH] : 파일 데이터의 체크썸. 다른 서버와의 통신에서 동기화 대상이 있는 지를 확인하기 위해 비교할 때 사용.
- long update_time : 파일의 업데이트 시간
- int state : 구조체의 상태. 0은 일반, 1은 변경, -1은 삭제를 의미한다.
- UT_hash_handle hh : uthash 함수와 매크로를 사용하기 위한 핸들러
해쉬 테이블 추가
void add_path(file_list_t **file_list, char *path)
{
if (NULL == path)
{
printf("add_path의 매개변수가 올바르지 않습니다.\n");
return;
}
file_list_t *current_file_data = NULL;
HASH_FIND_STR(*file_list, path, current_file_data);
char *file_name = NULL;
if (NULL != current_file_data)
{
printf("%s는 이미 존재하는 경로입니다.\n", path);
return;
}
struct stat file_data;
memset(&file_data, 0, sizeof(file_data));
stat(path, &file_data);
SHA256_CTX sha256;
SHA256_Init(&sha256);
file_name = strrchr(path, '/');
file_name++;
current_file_data = (file_list_t *)malloc(sizeof(file_list_t));
current_file_data->path = strndup(path, MAX_LENGTH);
current_file_data->file_name = strndup(file_name, MAX_LENGTH);
current_file_data->file_path_size = strlen(file_name) + 1;
current_file_data->update_time = file_data.st_mtime;
current_file_data->file_data_size = file_data.st_size;
current_file_data->state = 1;
check_sum_generater(path, current_file_data->check_sum, &sha256);
HASH_ADD_KEYPTR(hh, *file_list, current_file_data->path, strlen(path), current_file_data);
printf("%s가 추가되었습니다.\n", path);
}
해쉬 테이블에 요소를 추가하는 함수이다. 우선 path에 있는 경로를 키로 사용하는 데, 이 키를 통해 특정 경로의 구조체를 탐색할 수 있다.
우선 path를 키로 사용하는 요소, 즉 이미 추가된 경로인지 확인하고 없다면 계속 진행한다.
우선 file_list 구조체를 동적할당하고, 경로와 파일명을 할당한다.
stat으로 파일의 데이터를 가져와 다른 file_list 맴버의 값에 할당한다.
state의 경우, 이후 해쉬 테이블에서 변경된 파일만 전송하거나 특정 요소들만 제거할때 사용한다.
add_path 함수가 사용되는 경우, 클라이언트에 전송을 하기에 초기값은 1로 만든다.
이 후,전송이 끝나면 0으로 전부 초기화 시킨다.
체크썸의 경우, 다른 서버의 file_list와 비교하기 위해서 만든 맴버로 sha256으로 해싱하여 저장한다.
전부 끝난 후,HASH_ADD_KEYPTR 매크로로 경로를 키로 등록해 해쉬 테이블에 추가한다.
해쉬 테이블 탐색
file_list_t *find_file_data(file_list_t *file_list, char *path)
{
if (NULL == path)
{
printf("find_file_data의 매개변수가 올바르지 않습니다.\n");
return NULL;
}
file_list_t *current_file_data = NULL;
HASH_FIND_STR(file_list, path, current_file_data);
if (NULL == current_file_data)
{
printf("%s를 찾을 수 없습니다.\n", path);
}
return current_file_data;
}
HASH_FIND_STR 매크로로 경로ㄹ를 통해 특정 file_list 데이터를 탐색하여 리턴한다.
주로 변경사항을 확인하거나, 데이터를 삭제할 때 사용된다.
주의할 점은 HASH_ADD_KEYPTR으로 등록된 키의 값이외에 키의 크기도 같이 저장하는데,
값이 같아도 크기가 다르다면 기존의 요소라고 인식하지 않는다.
때문에 문자열을 키로 삼을 때, 주의해야한다.
해쉬 테이블 삭제
void delete_file_data(file_list_t **file_list, char *path)
{
if (NULL == path)
{
printf("delete_file_data의 매개변수가 올바르지 않습니다.\n");
return;
}
if (2 == relative_path_check(path))
{
path = absolute_path_change(path);
}
file_list_t *current_file_data = find_file_data(*file_list, path);
if (NULL != current_file_data)
{
printf("%s가 삭제되었습니다.\n", path);
free(current_file_data->path);
free(current_file_data->file_name);
free(current_file_data);
HASH_DEL(*file_list, current_file_data);
return;
}
printf("%s를 삭제하지 못했습니다.\n", path);
}
단일 테이블 삭제 함수로, 인자로 받은 경로로 탐색하여 해당 요소만 제거하는 함수이다.
이후 경로와 파일명,그리고 구조체는 동적할당을 하였기에 free로 해제한다.
이 함수를 사용해, 아래와 같이 -state가 -1인 요소만 삭제하거나, 전체 테이블을 삭제하는 함수에 이용한다.
void delete_state_clear(file_list_t **file_list)
{
if (NULL == *file_list)
{
printf("delete_state_clear의 매개변수가 올바르지 않습니다.\n");
return;
}
file_list_t *current_file_data = NULL;
file_list_t *tmp = NULL;
unsigned long total_size = 0;
int file_count = 0;
HASH_ITER(hh, *file_list, current_file_data, tmp)
{
if (-1 == current_file_data->state)
{
printf("제거 경로: %s\n", current_file_data->path);
delete_file_data(file_list, current_file_data->path);
}
}
}
void clear_file_list(file_list_t **file_list)
{
file_list_t *current_file_data, *tmp;
HASH_ITER(hh, *file_list, current_file_data, tmp)
{
delete_file_data(file_list, current_file_data->path);
}
printf("file_list가 삭제되었습니다.\n");
}
HASH_ITER은 테이블 전체를 순회하는 일종의 for문이다.
해쉬 테이블 변경
void change_state(file_list_t *file_list, int state)
{
file_list_t *current_file_data = NULL;
file_list_t *tmp = NULL;
HASH_ITER(hh, file_list, current_file_data, tmp)
{
current_file_data->state = state;
}
}
각 테이블의 상태를 일괄적으로 변경시키는 함수이다.
프로젝트에서는 변경사항 확인시, 모든 테이블의 상태를 -1로 변경하였다.
이후 아래에 함수에서, 변경되지 않은 요소는 0으로, 변경된 요소는 1로 변경하였다.
int check_path(file_list_t *current_file_data, char *path)
{
struct stat file_data;
memset(&file_data, 0, sizeof(file_data));
stat(path, &file_data);
printf("path: %s", current_file_data->path);
SHA256_CTX sha256;
SHA256_Init(&sha256);
if (current_file_data->update_time == file_data.st_mtime)
{
printf("유지\n");
current_file_data->state = 0;
}
else
{
printf("변경\n");
current_file_data->update_time = file_data.st_mtime;
current_file_data->file_data_size = file_data.st_size;
check_sum_generater(path, current_file_data->check_sum, &sha256);
current_file_data->state = 1;
}
return current_file_data->state;
}
이후에, 상태가 -1인 요소는 기존 동기화 리스트에서 제거된 요소로 취급한다.
위에 있는 delete_state_clear함수로 -1인 요소를 테이블에서 제거한다.
이후 상태가 1인 요소들만 클라이언트에 전송하고, 모든 상태를 다시 0으로 변경한다.
'프로젝트 > 파일 동기화' 카테고리의 다른 글
프로젝트: 파일 동기화 - 스레드 (0) | 2024.02.17 |
---|---|
프로젝트: 파일 동기화 - 동기화 리스트와 파일 변경 감지 (0) | 2024.02.15 |
프로젝트: 파일 동기화 - TCP 소켓 통신 (1) | 2024.02.14 |
프로젝트: 파일 동기화 - 동기화 리스트.txt 읽기 (1) | 2024.02.10 |
프로젝트: 파일 동기화 - 요구사항 (0) | 2024.02.07 |