티스토리 뷰
2차 목표는 서버 동기화, 즉 다른 pc와의 통신 연결이다.
이는 ip주소와 서로 간의 네트워크 설정만 제대로 이루어져 있다면, 어디서든 동기화가 가능해 진다는 것이다.
다만 로컬 환경의 전송이었던 서버-클라이언트와는 일부 로직과 함수를 공유하지만, 다른 점도 다수 존재하기에 이것을 중심으로 서술하겠다.
네트워크 설정
서버끼리의 통신 시 주의해야 할점은 프로그램이 정상적이라도 네트워크, 특히 방화벽 설정에서 연결을 거부할 수 있다는 것이다.
때문에 방화벽을 전체 비활성화 해주거나, 특정 포트만 열고 거기로만 통신을 하는 설정을 할 필요가 있다.
방화벽 전체 비활성화와 특정 포트에 요청 허용하는 ufw는 다음과 같다.
이는 우분투 20.04버전 기준이기에 다소 차이가 있을 수 있다.
#ufw 설치
sudo apt-get install ufw
#12346포트에 tcp 통신을 허용하도록 포트를 연다
sudo ufw allow 12346/tcp
#방화벽 비활성화
sudo ufw disable
인자로 서버 리스트 할당
서버는 데이터를 전송하는 마스터와 수신 받는 슬레이브로 구분된다.
server 실행시 인자를 2개까지 받을 수 있는 데, 이 두번째 인자가 있는 서버가 마스터, 없는 서버가 슬레이브이다.
char sync_server_path[MAX_LENGTH];
memset(sync_server_path, 0, sizeof(sync_server_path));
if (1 == file_path_check(argv[2]))
{
snprintf(sync_server_path, MAX_LENGTH, "%s", argv[2]);
}
// sync_server_path의 길이로 메인과 슬레이브 서버 구분
int sync_server_path_len = 0;
sync_server_path_len = strlen(sync_server_path);
위에는 2번째 인자, 서버 동기화 리스트의 주소를 저장하는 코드이다.
우선 해당 경로에 파일이 존재하는 지 file_path_check()를 사용하여 확인한다.
존재하면 해당 경로를 변수에 저장한다.
그후 변수의 길이를 측정하고 저장한다.
이 길이가 0이면 슬레이브, 1이상이면 마스터로 취급하여 이후 로직을 진행한다.
if (0 != sync_server_path_len)
{
master_server_action(file_list, sync_server_path);
}
다음과 같이 서버 동기화 리스트 경로의 길이가 0이 아니라면 마스터 서버의 로직을 실행한다.
마스터 서버 - 서버 리스트 파일 읽기
void master_server_action(file_list_t *file_list, char *sync_server_path)
{
if (NULL == file_list)
{
printf("sync_server_transfer_action 매개변수가 올바르지 않습니다.\n");
return;
}
in_addr_t ip_addresses[MAX_IPS];
memset(ip_addresses, 0, sizeof(ip_addresses));
int ip_count = 0;
ip_count = process_sync_server(sync_server_path, ip_addresses);
if (ip_count != 0)
{
master_server_thread(file_list, ip_addresses, ip_count);
}
}
먼저 파일을 읽기 전에, ip주소를 저장할 in_addr_t 구조체를 만든다.
최대 20개의 서버까지 전송하도록 하기에 배열로 선언한다.
그 후 process_sync_server에서 파일을 읽는다.
int process_sync_server(char *sync_server_path, in_addr_t *ip_addresses)
{
if (NULL == sync_server_path)
{
printf("process_sync_server의 매개변수가 올바르지 않습니다.\n");
return -1;
}
FILE *file = fopen(sync_server_path, "r");
int ip_count = 0;
char buffer[MAX_LENGTH];
char *token = NULL;
while (fgets(buffer, sizeof(buffer), file))
{
if (MAX_IPS == ip_count)
{
printf("전송가능한 서버의 갯수를 초과하였습니다.\n");
printf("한번에 최대 %d까지 가능합니다.\n", MAX_IPS);
break;
}
// 개행 문자 제거
char *token = strtok(buffer, " \n");
if (NULL == token)
{
continue;
}
in_addr_t ip = inet_addr(token);
if (INADDR_NONE == ip)
{
continue;
}
// 배열에 주소 저장
ip_addresses[ip_count] = ip;
ip_count++;
}
fclose(file);
return ip_count;
}
이전에 동기화 리스트를 읽을 때와 거의 유사하다.
2024.02.10 - [프로젝트] - 프로젝트: 파일 동기화 - 동기화 리스트.txt 읽기
프로젝트: 파일 동기화 - 동기화 리스트.txt 읽기
이번 파트는 동기화 리스트.txt 파일을 읽고, 거기에 저장된 경로를 추출하는 것이다. 때문에 유효한 경로인지 확인하는 것과 상대 경로 구분하고 베이스 설정 등을 소개할 것이다. 이번 코드는
lhs9602.tistory.com
while (fgets(buffer, sizeof(buffer), file))
{
if (MAX_IPS == ip_count)
{
printf("전송가능한 서버의 갯수를 초과하였습니다.\n");
printf("한번에 최대 %d까지 가능합니다.\n", MAX_IPS);
break;
}
// 개행 문자 제거
char *token = strtok(buffer, " \n");
if (NULL == token)
{
continue;
}
in_addr_t ip = inet_addr(token);
if (INADDR_NONE == ip)
{
continue;
}
// 배열에 주소 저장
ip_addresses[ip_count] = ip;
ip_count++;
}
다만 while동작이 다소 다른데, 우선 저장가능한 ip개수가 20개이므로 그 이상의 주소가 있을 시 중간에 종료한다.
ip주소도 라인 단위로 저장되어 있기에 하나씩 추출한다.
그리고 inet_addr()으로 in_addr_t로 변환한다.
이때 만약 ipv4주소가 아닌 유효하지 않은 주소가 저장되면 INADDR_NONE으로 이를 걸러낸다.
여기까지 통과되면 ip주소를 in_addr_t배열에 할당하고, ip_count에 1을 더한다.
이를 반복하고, 마지막에 ip_count를 반환한다.
if (ip_count != 0)
{
master_server_thread(file_list, ip_addresses, ip_count);
}
이후, ip_count가 0이 아니라면 연결할 서버가 있는 것이기에 다음 로직을 실행한다.
마스터 서버 - 스레드로 소켓 연결
void master_server_thread(file_list_t *file_list, in_addr_t *ip_addresses, int ip_count)
{
if (NULL == file_list || NULL == ip_addresses)
{
printf("master_server_thread_create 매개변수 오류\n");
return;
}
pthread_t server_threads[ip_count];
memset(server_threads, 0, sizeof(server_threads));
thread_server_data_t thread_server_data[ip_count];
memset(thread_server_data, 0, sizeof(thread_server_data));
for (int thread_index = 0; thread_index < ip_count; thread_index++)
{
thread_server_data[thread_index].file_list = file_list;
thread_server_data[thread_index].ip_addresses = ip_addresses[thread_index];
pthread_create(&server_threads[thread_index], NULL, server_send_thread, &thread_server_data[thread_index]);
}
for (int thread_index = 0; thread_index < ip_count; thread_index++)
{
pthread_join(server_threads[thread_index], NULL);
printf("스레드 %d가 종료되었습니다.\n", thread_index);
}
printf("\n");
printf("모든 스레드가 종료되었습니다.\n");
}
2024.02.17 - [프로젝트] - 프로젝트: 파일 동기화 - 스레드
프로젝트: 파일 동기화 - 스레드
c에서 여러 작업을 병렬로 처리 할때는 스레드나 프로세스 복제를 사용한다. 이때 스레드는 특정 작업을 병렬로 처리할때 많이 사용되며, 이번 프로젝트에서 클라이언트 전송에 사용하였다. 스
lhs9602.tistory.com
이전 만든 스레드와 거의 동일한 구조이다.
각 스레드에 파일 리스트와 ip를 인자로 전달하고, 유효한 ip 갯수만큼 스레드를 만들어 각 서버에 병렬로 데이터를 전송한다.
void *server_send_thread(void *arg)
{
thread_server_data_t *data;
memset(&data, 0, sizeof(data));
data = (thread_server_data_t *)arg;
file_list_t *file_list = NULL;
file_list = data->file_list;
in_addr_t ip_addresses = 0;
ip_addresses = data->ip_addresses;
// 소켓 생성 및 슬레이브 서버와 연결
int socket_fd = 0;
socket_fd = master_server_connect(ip_addresses);
if (0 < socket_fd)
{
...
}
...
}
int master_server_connect(in_addr_t ip_addresses)
{
if (0 == ip_addresses)
{
printf("master_server_action의 매개변수가 올바르지 않습니다.\n");
return -1;
}
int socket_fd = 0;
socket_fd = socket_create(AF_INET, SOCK_STREAM, PROTOCOL);
// 서버 주소 설정
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = ip_addresses;
server_addr.sin_port = htons(SERVER_PORT);
struct timeval timeout;
timeout.tv_sec = WAIT_TIME;
timeout.tv_usec = 0;
setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
// 슬레이브 서버와 연결.
int connect_result = socket_connect(socket_fd, &server_addr);
if (-1 == connect_result)
{
return -1;
}
printf("연결 성공\n");
return socket_fd;
}
각 스레드는 ip를 가지고, 각각 소켓을 새로 만든 후, 슬레이브 서버와 연결한다.
struct timeval timeout;
timeout.tv_sec = WAIT_TIME;
timeout.tv_usec = 0;
setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
// 슬레이브 서버와 연결.
int connect_result = socket_connect(socket_fd, &server_addr);
이때 setsockopt에 timeval 구조체로 일정시간 동안 통신을 요청하게 한다.
이렇게 한, 이유는 슬레이브 서버에서 다른 로직을 실행하여 서로 싱크가 맞지 않을 경우를 대비하기 위함이다.
슬레이브 서버 - 소켓과 통신 연결
// 슬레이브 서버 로직
if (0 == sync_server_path_len)
{
slave_server_socket = socket_create(AF_INET, SOCK_STREAM, PROTOCOL);
setsockopt(slave_server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in slave_server_address;
server_address_set(&slave_server_address, AF_INET, SERVER_PORT);
socket_bind(slave_server_socket, &slave_server_address);
socket_listen(slave_server_socket, 5);
}
while (TRUE)
{
...
...
// 슬레이브 서버 로직
if (0 == sync_server_path_len)
{
printf("마스터 서버 동기화\n");
if (FD_ISSET(slave_server_socket, &readfds))
{
int master_server_socket = 0;
master_server_socket = socket_accept(slave_server_socket, (struct sockaddr *)&server_socket_address, (socklen_t *)&server_socket_addrlen);
slave_server_action(master_server_socket, file_list, sync_file_path);
}
}
...
...
}
다음과 같이 서버 리스트의 경로가 0일 경우, 마스터 서버와 연결할 소켓을 새로 만든다.
그 후, selet()에 마스터 서버와 연결하고, 슬레이브 서버의 로직을 진행한다.
이후는 마스터-슬레이브 간의 데이터를 주고 받는데, 2편에서 계속하겠다
'프로젝트 > 파일 동기화' 카테고리의 다른 글
프로젝트: 파일 동기화- 유닛 테스트 (0) | 2024.02.24 |
---|---|
프로젝트: 파일 동기화 - 서버 동기화 2편 (0) | 2024.02.21 |
프로젝트: 파일 동기화 - 압축 (0) | 2024.02.20 |
프로젝트: 파일 동기화 - 데이터 직렬화 (1) | 2024.02.20 |
프로젝트: 파일 동기화 - 스레드 (0) | 2024.02.17 |