반응형

앞서 계속 소켓 소켓 하다가 갑자기 파일을 조작하는 것이 이상하다고 생각할수도 있습니다.

그러나 리눅스(유닉스 계열 운영 체제)는 모든 것(콘솔, 소켓, 파일 등등)을 파일로 간주합니다.

우리가 생성하는 소켓도 사용하는 방식이나 내부적으로 처리되는 방식이 파일과 상당 부분 유사하기 때문에

본격적으로 소켓을 공부하기 전에 파일에 대해 이해하는 것이 도움이 됩니다.

 

저 수준 파일 입.출력(Low-Level File Access)

여기서 언급하는 Low-Level의 의미는 단순히 "시스템이 직접 제공해 주는~"의 의미로 받아 들이면 된다.

즉 이후에 설명하는 함수들은 리눅스에서 제공해 주는 함수이지 ANSI표준 C에서 정의된 함수들은 아니라는 뜻이다.

 

파일 디스크립터(File Descriptor)

앞서 socket 함수를 소개할때, 함수 호출에 성공하면 파일 디스크립터를 리턴한다고 했었죠.

파일 디스크립터란 시스템으로부터 할당받은 파일이나 소켓을 대표하는 정수를 의미합니다.

또한 표준 입력과 표준 출력도 파일 디스크립터로 표현이 되는데 이들은 프로그램이 시작되자마자기본적으로 열리고

종료시에 자동으로 닫히게 됩니다.

 

표준 입력 : Standard Input             /         파일 디스크립터 0

표준 출력 : Standard Output          /         파일 디스크립터 1

표준 에러 출력 : Standard Error      /         파일 디스크립터 2

 

파일이나 소켓의 이름이 엄청나게 길때, 이를 간편하게 사용하기 위해서 파일디스크립터를 쓴다고 이해하면 되겠네요.

그런데 디스크립터라는 용어 대신에 핸들이라는 표현도 함께 사용됩니다. 그러나 핸들이라는 표현을 주로 윈도우즈 기반에서 사용되는 용어입니다. 따라서 공부를 하면서 윈도우즈 기반 소켓 프로그래밍을 같이 공부할것이기 때문에

이 둘을 엄격하게 구분해서 사용하겠습니다. 즉 리눅스 기반에서는 디스크립터라는 용어를,

윈도우즈 기반에서는 핸들이라는 용어를 사용해서 혼돈을 최소화 시키겠습니다.

 

File 열기

읽거나 쓰기 위해 파일을 여는 함수를 소개하겠습니다. 두 개의 인자를 받는데,

하나는 열고자 하는 파일의 경로를 포함한 이름이고, 또 하나는 열게되는 파일의 모드(mode)입니다.

성공시 파일 디스크립터를 리턴하고, 실패시 -1을 리턴합니다

path : 파일의 경로를 포함한 이름을 나타내는 문자열의 포인터

flag : 파일 오픈 모드

 

flag에 인자로 넘겨줄 수 있는 값과 그 의미는 다음과 같으며 하나 이상의 모드를 Bitwise-OR('|')로 묶어서 전달 가능합니다.

 

MODE                    의미

O_CREAT              필요한 경우 파일을 생성한다.

O_TRUNC             존재하던 데이터를 모두 삭제한다.

O_APPEND           존재하던 데이터를 보존하고 뒤에 이어서 저장한다.

O_RDONLY           읽기 전영 모드로 파일을 연다.

O_WRONLY          쓰기 전용 모드로 파일을 연다.
O_RDWR              읽기 쓰기 겸용 모드로 파일을 연다.

 

File 닫기

파일은 사용 후 반드시 닫아줘야 한다는 것 정도는 C언어를 공부하면서 이미 알고 있는 내용일 것입니다.

중요한 사실은 리눅스에서는 소켓도 파일로 취급하기 때문에, 생성된 소켓을 닫아 줄 때에도 이 함수를 사용합니다.

파일을 닫는 함수 close 입니다.

 

성공시 0을 리턴하고, 실패시 -1을 리턴합니다.

 

데이터 쓰기

write 함수는 파일에 데이터를 출력하는 함수입니다. 리눅스에서는 파일과 소켓을 동일하게 취급하므로

소켓을 통해서 다른 호스트에게 데이터를 전송 할 때에도 물론 사용할 수 있습니다.

Hello World 서버 프로그램에서 "Hello World!" 메세지를 전달 하기 위해서 이 함수를 사용했었습니다.

지금 당장 확인해보세요

성공시 전달 한 바이트 수를 리턴하고, 실패시 -1을 리턴합니다.

 

fildes : 데이터 전송 영역을 나타내는 파일 디스크립터.

buf : 전송할 데이터를 가지고 있는 버퍼의 포인터.

nbytes : 전송할 데이터의 바이트 수.

 

참고로 size_t 는 unsigned int 로 정의되어 있습니다. ssize_t 의 경우 size_t 앞에 s가 하나 더 붙어있죠

이 s는 signed을 의미합니다. 그렇다면 ssize_t가 무엇을 의미하는지 알 수 있을 것입니다.

 

 

 

다음은 data.txt 라는 이름의 파일을 하나 생성해서 데이터를 저장하는 예제입니다.

 

20번째 줄에서 data.txt 라는 이름의 파일을 하나 생성하고 잇죠. 만약에 이미 data.txt라는 이름의 파일이 존재한다면

그 파일의 모든 데이터는 삭제됩니다

25번째 줄에서 write 함수를 사용하여 데이터를 파일에 전송하고 있습니다.

 

실행 후 cat 명령을 통해서 data.txt 파일의 내용을 출력하고 있습니다. 출력 내용을 보면 프로그램상에서 전송했던

데이터가 출력된 것을 알 수 있죠.

 

데이터 읽기

 

read 함수는 데이터를 입력(수신) 받는 함수입니다.

성공 시 수신한 바이트 수 리턴(단 EOF 만나면 0)하고, 실패 시 -1 리턴합니다

 

fildes : 데이터를 전송해 주는 대상을 가리키는 파일 디스크립터.

buf : 수신 한 데이터를 저장할 버퍼를 가리키는 포인터.

nbytes : 수신할 최대 바이트 수.

 

이번에 살펴 볼 예제는, 이전 예제에서 생성했던 data.txt 파일에서 데이터를 읽어서 콘솔에 출력하는 프로그램 입니다.

 

22번째 줄에서 data.txt 파일을 읽기 전용 모드로 열고, 28번째 줄에서 데이터를 읽고 있습니다.

실행결과는

 

 

파일 디스크립터와 소켓

 

이번 예제에서는 파일도 생성하고, 소켓도 함께 생성해서 리턴되는 파일 디스크립터의 값을 비교해 보자.

 

출력된 파일 디스크립터의 결과값을 보면, 소켓을 생성할때나, 파일을 생성할때나 똑같이 파일로 취급해서

생성 순서대로 디스크립터가 넘버링(Numbering)되고 있는 것을 볼 수 있습니다.

또한 프로그램이 시작되자마자 바로 열리는 파일 디스크립터 0, 1, 2 가 잇기 때문에 3부터 시작하는것도 알 수 있죠.

 

 

참고 : _t 로 끝나는 자료형

ssize_t, size_t 그리고 pid_t 다소 생소해 보이는 자료형이 몇몇 등장했지요.

이런 것들을 고전적인(primitive) 자료형이라 합니다.

일반적으로 <sys/types.h> 헤더에 선언되어 있는데, 사실 C의 typedef 선언을 통해서 정의되어 있습니다.

그러니까, 새롭게 등장한 것이 아니라, 우리가 알고있는 int, float, long과 같은 기본 자료형을 가지고,

이름만 바꾸어 정의해 놓은 것입니다.

 

이러한 자료형들은 꽤 오래 전부터 존재하고 있었는데요,

그렇다면 도대체 왜 이러한 것들을 정의해 놓고 사용하는 걸까요? int, short, float이 얼마나 익숙하고 좋습니까?

지금은 int가 32비트라고 말하죠, 보편적으로 사용되는 운영체제와 컴퓨터가 32비트이기 때문입니다.

과거 16비트 시절에는 int가 16비트였습니다. 즉, 시스템에 따라서 자료형의 표현 방식이 틀려지므로,

내가 구현한 프로그램을 다른 시스템에서 실행시키기 위해서는 코드의 수정이 불가피해집니다.

 

그러나 4바이트의 자료형이 필요한 곳에, 지금처럼 헤더파일의 unsigned int를 size_t로 정의해놓고 쓰게 되면.

나중에 시스템이 변경되어서 unsigned int 가 더이상 4바이트를 나타내지 않게 되었을 때, 4바이트를 나타내는

다른 적절한 자료형을 가지고 헤더파일에 선언되어 있는 size_t를 재정의해 주기만 하면 됩니다.

즉 소스 코드는 바꾸지 않고 헤더파일만 살짝 바꿔서 컴파일만 다시 한번 하면 새로운 시스템에서 잘 돌아가게 되는것이다.

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기