2. 리눅스 기반 파일 조작하기!

heeseok·2024년 11월 27일

TCP/IP

목록 보기
2/4
  1. 리눅스에서의 소켓조작은 파일조작과 동일하게 간주되기 때문에 파일에 대해서 자세히 알 필요가 있다.
  2. 리눅스는 소켓을 파일의 일종으로 구분한다.
  3. 파일 입출력 함수를 소켓 입출력에, 다시 말해서 네트워크상에서의 데이터 송수신에 사용할 수 있다.

참고로 윈도우는 리눅스와 달리 파일과 소켓을 구분하고 있다. 떄문에 별도의 데이터 송수신 함수를 참조해야 한다.

운영체제가 제공하는 파일 입출력(low-level file Access)과 파일 디스크립터(file descriptor)
- 저 수준(low-level)은 '표준에 상관없이 운영체제가 독립적으로 제공하는' 이라는 의미!
- 이후에 설명하는 함수들은 리눅스에서 제공하는 함수이지, ANSI표준에서 정의한 함수가 아니다.
- 표준이 아니기 떄문에 운영체제에 대한 호환성이 없다.

파일 디스크립터(file descriptor)란 시스템으로부터 할당 받은 파일 또는 소켓에 부여된 정수를 의미한다.
- 리눅스는 소켓,file, handle(console) 등 모든 것을 파일로 간주한다.

일반적으로 파일과 소켓은 생성의 과정을 거쳐야 파일 디스크립터가 할당된다.
- 저 수준 파일 입출력 함수는 입출력을 목적으로 파일 디스크립터를 요구한다.
- 저 수준 파일 입출력 함수에게 소켓의 파일 디스크립터를 전달하면, 소켓을 대상으로 입출력을 진행한다.

파일 열기

#include <sys/stat.h>
#include <fcntl.h>

int open(const char *path, int flag);
	-> 성공 시 파일 드스크립터, 실패시 -1 반환.
	path : 파일 이름을 나타내는 문자열의 주소 값 전달.
	첫 번째 매개변수는 대상이 되는 파일의 이름 및 경로 정보.
	flag : 파일의 오픈 모드 정보 전달.
	두 번째 매개변수는 파일의 오픈 모드 정보(파일의 특성 정보)를 전달.


	오픈모드		|		의미
	O_CREAT		|	필요하면 파일을 생성
	O_TRUNC		|	기존 데이터 전부 삭제
	O_APPEND	|	기존 데이터 보조하고, 뒤에 이어서 저장
	O_RDONLY	|	읽기 전용으로 파일 오픈
	O_WRONLY	|	쓰기 전용으로 파일 오픈
	O_RDWR		|	읽기, 쓰기 겸용으로 파일 오픈
- 하나 이상의 정보를 비트 OR 연산자로 묶어서 전달이 가능함.

파일 닫기
#include <unistd.h>

int close(int fd);
	-> 성공 시 0, 실패 시 -1 반환
	fd : 닫고자 하는 파일 또는 소켓의 파일 디스크립터 전달
	파일 디스크립터를 인자로 전달하면 해당 파일은 닫히게 된다. 그런데 위 함수는 파일뿐만 아니라, 소켓을 닫을 떄에도 사용된다.


파일에 데이터 쓰기
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t nbytes);
	-> 성공 시 전달한 바이트 수, 실패 시 -1 반환
	fd : 데이터 전송대상을 나타내는 파일 디스크립터 전달.
	buf : 전송할 데이터가 저장된 버퍼의 주소 값 전달.
	nbytes : 전송할 데이터의 바이트 수 전달.

** 예제 **
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

void error_handling(char* message);

int main(void)
{
	int fd;
	char buf[]="Let's go!\n";
	
	fd=open("data.txt", O_CREAT|O_WRONLY|O_TRUNC); 
	// 파일 오픈 모드가 O_CREAT|O_WRONLY|O_TRUNC의 조합이니, 아무것도 저장되어있지 않은 새로운 파일이 생성되어 쓰기만 가능하게 된다.
	//	물론 이미 data.txt라는 이름의 파일이 존재한다면, 이 파일의 모든 데이터는 지워진다.
	if(fd==-1)
		error_handling("open() error!");
	printf("file descriptor: %d \n", fd);

	// fd에 저장된 파일 디스크립터에 해당하는 파일에 buf에 저장된 데이터를 전송하고 있다.
	if(write(fd, buf, sizeof(buf))==-1)
		error_handling("write() error!");

	close(fd);
	return 0;
}

void error_handling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

/*
root@com:/home/swyoon/tcpip# gcc low_open.c -o lopen
root@com:/home/swyoon/tcpip# ./lopen
file descriptor: 3 
root@com:/home/swyoon/tcpip# cat data.txt
Let's go!
root@com:/home/swyoon/tcpip# 
*/

파일 읽기
#include <unistd.h>

ssize_t read(int fd, void *buf, sizt_t nbytes);
	-> 성공 시 수신한 바이트 수(단 파일의 끝을 만나면 0), 실패 시 -1 반환
	fd : 데이터 수산대상을 나타내는 파일 디스크립터 전달.
	buf : 수신한 데이터를 저장할 버퍼의 주소 값 전달.
	nbytes : 수신할 최대 바이트 수 전달.

** 예제 **

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define BUF_SIZE 100

void error_handling(char* message);

int main(void)
{
	int fd;
	char buf[BUF_SIZE];
	
	// 파일 data.txt를 읽기 전용으로 열기.
	fd=open("data.txt", O_RDONLY);
	if( fd==-1)
		error_handling("open() error!");
	
	printf("file descriptor: %d \n" , fd);
	
	// read 함수를 이용해서 126행에 선언된 배열 buf에 읽어 들인 데이터를 저장하고 있다.
	if(read(fd, buf, sizeof(buf))==-1)
		error_handling("read() error!");

	printf("file data: %s", buf);
	
	close(fd);
	return 0;
}

void error_handling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

/*
root@com:/home/swyoon/tcpip# gcc low_read.c -o lread
root@com:/home/swyoon/tcpip# ./lread
file descriptor: 3 
file data: Let's go!
root@com:/home/swyoon/tcpip# 
*/

설명한 내용들은 소켓에도 그대로 적용된다는 사실을 기억하자!!!

파일 디스크립터와 소켓
** 예제 ** 

파일 생성 및 소켓 생성 그리고 반환되는 파일 디스크립터의 값을 정수 형태로 비교!
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>

int main(void)
{	
// 하나의 파일과 두개의 소켓을 생성
	int fd1, fd2, fd3;
	fd1=socket(PF_INET, SOCK_STREAM, 0);
	fd2=open("test.dat", O_CREAT|O_WRONLY|O_TRUNC);
	fd3=socket(PF_INET, SOCK_DGRAM, 0);
	
	앞서 생성한 파일 디스크립터의 정수 값을 출력!
	printf("file descriptor 1: %d\n", fd1);
	printf("file descriptor 2: %d\n", fd2);
	printf("file descriptor 3: %d\n", fd3);
	
	close(fd1);
	close(fd2);
	close(fd3);
	return 0;
}
리눅스 환경에서 실행해야하기 떄문에 기술함.
// 출력된 디스크립터의 정수 값을 비교해보면, 일련의 순서대로 넘버링(numbering)이 되는 것을 알 수 있으며,
// 파일 디스크립터가 3부터 시작하는 이유는 앞서 보였듯이 0,1,2는 표준 입출력에 이미 할당되었기 때문이다.!!
profile
서버공부하는 사람

0개의 댓글