참고로 윈도우는 리눅스와 달리 파일과 소켓을 구분하고 있다. 떄문에 별도의 데이터 송수신 함수를 참조해야 한다.
운영체제가 제공하는 파일 입출력(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는 표준 입출력에 이미 할당되었기 때문이다.!!