리눅스 멀티스레드 파일 공유 프로그램 - 9 본문
작업환경의 디렉터리를 압축하여 전송하는 로직을 만들었다.
개발 환경이나 버전에 따라 안 되는 기능들이 너무 많아 힘들었다.
결국 우분투 22.04버전에서 다시 코드를 구현하기로 했다.
여러 압축 라이브러리가 있었지만 g++ 버전 및 작업 환경에 따라 오류가 발생하고
정보량이 많지가 않아서 zip.h를 이용하기로 했다.
인자값이 많이 줄었다.
zip.h을 위한 -lzip을 추가해 준다.
#include <iostream>
#include <fstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <cstring>
#include <algorithm>
#include <sstream>
#include <arpa/inet.h>
#include <filesystem>
#include <iomanip>
#include <zip.h>
#define PORT 8080
#define BUFFER_SIZE 1024
using namespace std;
namespace fs = std::filesystem;
void compressDirectory() {
string directory = "/home/sj/share_project/";
string zipFile = "/home/sj/test/output.zip";
zip_t* zip = zip_open(zipFile.c_str(), ZIP_CREATE | ZIP_TRUNCATE, NULL);
if (!zip) {
std::cerr << "압축 파일을 열 수 없습니다." << std::endl;
}
// 디렉터리 내의 파일 목록 가져오기
for (const auto& entry : fs::directory_iterator(directory)) {
if (entry.is_regular_file()) {
string filePath = entry.path().string();
zip_source_t* source = zip_source_file(zip, filePath.c_str(), 0, 0);
zip_file_add(zip, fs::path(filePath).filename().c_str(), source, ZIP_FL_OVERWRITE);
}
}
// 압축 파일 닫기
zip_close(zip);
}
void *clientHandler(void *newsocket)
{
int new_socket = *((int *)newsocket);
char buffer[BUFFER_SIZE];
string str_buffer;
int valread = recv(new_socket, buffer, BUFFER_SIZE, 0);
str_buffer = string(buffer, valread);
if(str_buffer == "1234")
{
string send_filename = "/home/sj/test/output.zip";
ifstream file(send_filename, ios::binary | ios::ate);
file.seekg(0);
std::stringstream file_stream;
file_stream << file.rdbuf();
std::string file_contents = file_stream.str();
if(send(new_socket, file_contents.c_str(), file_contents.length(), 0) < 0){
cerr << "Failed to send file" << endl;
}
}
else
{
str_buffer = "Failed";
send(new_socket, str_buffer.c_str(), str_buffer.length(), 0);
}
// 소켓 종료
close(new_socket);
}
string inToip(uint32_t ip){
struct in_addr ip_addr;
ip_addr.s_addr = ip;
return string(inet_ntoa(ip_addr));
}
int main() {
int server_fd, new_socket;
int opt = 1;
// 소켓 파일 디스크립터 생성
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
std::cerr << "Socket creation error" << std::endl;
return -1;
}
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
std::cerr << "setsockopt failed" << std::endl;
close(server_fd);
return -1;
}
// 주소 구조체 설정
struct sockaddr_in address;
int addrlen = sizeof(address);
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 소켓을 주소와 바인드
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
std::cerr << "Bind failed" << std::endl;
return -1;
}
// 클라이언트의 연결을 대기
if (listen(server_fd, 3) < 0) {
std::cerr << "Listen failed" << std::endl;
return -1;
}
// 연결 수락
compressDirectory();
while(true)
{
struct sockaddr_in client_addr;
int client_addrlen = sizeof(client_addr);
if ((new_socket = accept(server_fd, (struct sockaddr *)&client_addr, (socklen_t*)&client_addrlen)) < 0) {
std::cerr << "Accept failed" << std::endl;
return -1;
}
cout << "Client IP : " << inToip(client_addr.sin_addr.s_addr) << endl;
pthread_t thread;
int result = pthread_create(&thread, NULL, clientHandler, &new_socket);
pthread_detach(thread);
}
close(server_fd);
return 0;
}
서버의 코드이다.
압축 함수부터 살펴본다.
oid compressDirectory() {
string directory = "/home/sj/share_project/";
string zipFile = "/home/sj/test/output.zip";
zip_t* zip = zip_open(zipFile.c_str(), ZIP_CREATE | ZIP_TRUNCATE, NULL);
if (!zip) {
std::cerr << "압축 파일을 열 수 없습니다." << std::endl;
}
// 디렉터리 내의 파일 목록 가져오기
for (const auto& entry : fs::directory_iterator(directory)) {
if (entry.is_regular_file()) {
string filePath = entry.path().string();
zip_source_t* source = zip_source_file(zip, filePath.c_str(), 0, 0);
zip_file_add(zip, fs::path(filePath).filename().c_str(), source, ZIP_FL_OVERWRITE);
}
}
// 압축 파일 닫기
zip_close(zip);
}
zip_open() 함수를 이용해
새로운 zip파일을 만들어낸다.
zipFile.c_str()을 통해 경로를 넣어준다.
ZIP_CREATE는 새로운 zip파일을 생성한다는 것을 의미한다. 해당 파일이 존재하이 않을 경우 새로 만들어준다.
ZIP_TRUNCATE는 이미 파일이 존재할 경우 해당 파일의 내용을 비우고 재사용한다.
반복문에서는
#include <filesystem>의 헤더에서
std::filesystem의 directory_iterator()을 이용하여
디렉터리를 순회하여 경로를 얻어낸다.
entry.is_regular_file() 조건문으로 파일이 아닌지 판단하고
파일일 경우에만 압축 로직을 실행한다.
zip_source_t* source = zip_source_file(zip, filePath.c_str(), 0, 0);
압축파일에 추가할 파일의 소스를 생성한다.
즉 주어진 파일의 경로로부터 zip 소스를 생성하고, 이를 압축파일에 추가할 준비를 한다.
zip_file_add(zip, fs::path(filePath).filename().c_str(), source, ZIP_FL_OVERWRITE);
실제로 압축 파일에 파일을 추가하는 로직이다.
fs::path(filePath).filename().c_str() 이 부분은 path에서 파일의 이름만 추출하는 과정이다.
세 번째 매개변수는 아까 파일들의 zip 소스들을 넣는 부분이다.
ZIP_FL_OVERWRITE 이 플래그는 추가된 파일이 이미 존재하는 경우, 이플래그를 사용하여
기존 파일을 덮어쓸 것인지 여부를 지정한다.
이렇게 작업공간에 대한 하나의 압축파일을 만들었다.
if(str_buffer == "1234")
{
string send_filename = "/home/sj/test/output.zip";
ifstream file(send_filename, ios::binary | ios::ate);
file.seekg(0);
std::stringstream file_stream;
file_stream << file.rdbuf();
std::string file_contents = file_stream.str();
if(send(new_socket, file_contents.c_str(), file_contents.length(), 0) < 0){
cerr << "Failed to send file" << endl;
}
}
압축 파일을 클라이언트에게 전송하는 로직이다.
ifstream : 파일 입력 스트림을 나타내는 클래스이다. send_filename의 경로에 있는 파일을 가져온다.
ios::binary : 파일을 바이너리로 읽는다.
ios::ate : 파일을 끝에서부터 읽는다. 이 플래그는 파일의 크기를 쉽게 알 수 있다.
file.seekg(0) : 이 함수를 통해 파일 포인터를 시작 부분으로 옮긴다.
이 친구가 없으면 파일이 제대로 읽어지지 않았었다.
std::stringstream file_stream : 문자열을 스트림으로 다루는 클래스이다. 추후 file 버퍼의 데이터를 가져오기 위해 사용했다.
file_stream << file.rdbuf() : 파일 스트림의 버퍼를 file_stream으로 읽어드린다. rdbuf()를 통해 file변수의 버퍼를 읽는다.
file_contents에 file_stream의 내용을 문자열로 바꿔 저장한다.
그 후 socket으로 전송해 준다.
int valread;
ofstream output_file("/home/sj/test/recived_file.zip", ios::binary);
while ((valread = recv(sock, buffer, BUFFER_SIZE, 0)) > 0) {
output_file.write(buffer, valread);
}
output_file.close();
if(valread < 0)
cerr << "Error receving data from server" << endl;
클라이언트 코드의 데이터 받는 부분을 확인한다.
test디렉터리 경로에 압축파일을 만들어낼 것이다.
파일을 쓰기 위해 ofstream객체를 이용해서 바이너리 파일을 만들어낸다.
이제 이곳에 파일을 써 내려갈 것이다.
recv를 통해 받은 데이터를 순차적으로 써 내려간다.
recived_file.zip에 클라이언트에 의해 전송받은 압축파일이며
unzip으로 압축해제 했을 때 정상적으로 파일들이 생긴다.
버전오류로 정말 고생했지만
파일 스트림을 어떻게 읽어야 하고 쓰는지에 대해 알 수 있었고
압축파일을 전송하기 위해 stringstream객체를 이용해 데이터를 가공하는 스킬에 대해 공부할 수 있었다.
또한 파일을 안전하게 가공하고 보내기 위해 seekg함수를 이용해서 파일 포인터의 시작 부분으로 옮겨야 하는
중요한 스킬도 공부할 수 있었다.
이로써 현재는 로컬에서만 통신이 가능하지만
원한다면 포트포워딩으로 카페에서 실시간으로 작업코드들을 전송받아 개발을 진행할 수 있다.
다음은 전송되는 데이터가 중간에 탈취되지 않도록 암호학을 적용해 보고 해당 프로젝트를 끝내기로 했다.
'개인 프로젝트 공부' 카테고리의 다른 글
리눅스 멀티스레드 파일 공유 프로그램 - 10(완) (0) | 2024.06.16 |
---|---|
리눅스 멀티스레드 파일 공유 프로그램 - 8 (0) | 2024.06.12 |
리눅스 멀티스레드 파일 공유 프로그램 - 7 (0) | 2024.06.11 |
리눅스 멀티스레드 파일 공유 프로그램 - 6 (0) | 2024.06.10 |
리눅스 멀티스레드 파일 공유 프로그램 - 5 (0) | 2024.06.08 |