네트워크 프로그래밍에서 갑자기 파일을 언급해서 이상하게 생각될 수도 있으나,
리눅스에서 소켓 조작은 파일 조작과 동일하게 간주되므로 파일에 대해 자세히 알 필요가 있습니다.
리눅스는 소켓을 파일의 일종으로 구분합니다. 따라서 파일 입출력 함수를 소켓 입출력에, 다시 말해서 네트워크상에서의 데이터 송수신에 사용할 수 있습니다.
저수준 파일 입출력과 파일 디스크립터
저수준이라는 말만 들어도 어렵다고 느낄 수도 있을텐데, 여기서 말하는 저수준이란 표준에 상관없이 운영체제가 독립적으로 제공한다는 의미입니다. 즉, 지금부터 설명할 함수들은 리눅스에서 제공하는 함수들이고 ANSI 표준에서 정의한 함수가 아니라는 뜻입니다.
파일 입출력 함수를 사용하려면 파일 디스크립터에 대한 개념을 알아야 합니다.
여기서 말하는 파일 디스크립터란 시스템으로부터 할당받은 파일 또는 소켓에 부여된 정수입니다.
참고로 C언어를 공부하면서 여태 입출력 대상으로 여겨왔던 표준 입출력 및 표준 에러에도 리눅스에서는 다음과 같이 파일 디스크립터를 할당하고 있습니다.
파일 디스크립터 | 대 상 |
0 | 표준입력: Standard Input |
1 | 표준출력: Standard Output |
2 | 표준에러: Standard Error |
일반적으로 파일과 소켓은 생성의 과정을 거쳐야 파일 디스크립터가 할당됩니다. 반면 위에서 보이는 세 가지 입출력 대상은 별도의 생성과정을 거치지 않아도 프로그램이 실행되면 자동으로 할당되는 파일 디스크립터입니다.
파일 열기
아래나 데이터를 읽거나 쓰기 위해서 파일을 열 때 사용하는 함수입니다.
이 함수가 전달받는 인자는 다음과 같습니다.
- const char* path: 대상이 되는 파일의 이름 및 경로 정보
- int flag: 파일의 오픈 모드 정보(파일의 특성 정보)
// 성공 시 파일 디스크립터, 실패 시 -1 반환
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char* path, int flag);
두 번째 매개변수 flag에 전달할 수 있는 값과 의미는 다음과 같고, 하나 이상의 정보를 비트 OR 연산자로 묶어 전달이 가능합니다.
오픈 모드 | 의 미 |
O_CREAT | 필요하면 파일 생성 |
O_TRUNC | 기존 데이터 전부 삭제 |
O_APPEND | 기존 데이터 보존하고, 뒤에 이어서 저장 |
O_RDONLY | 읽기 전용으로 파일 오픈 |
O_WRONLY | 쓰기 전용으로 파일 오픈 |
O_RDWR | 읽기, 쓰기 겸용으로 파일 오픈 |
파일 닫기
파일은 사용 후 반드시 닫아줘야 합니다. 아래는 파일을 닫을 때 호출하는 함수입니다.
이 함수가 전달받는 인자는 다음과 같습니다.
- int fd: 닫고자 하는 파일 또는 소켓의 파일 디스크립터
// 성공 시 0, 실패 시 -1 반환
#include <unistd.h>
int close(int fd);
파일에 데이터 쓰기
이어서 소개하는 write 함수는 파일에 데이터를 출력(전송)하는 함수입니다.
이 함수가 전달받는 인자는 다음과 같습니다.
- int fd: 데이터 전송대상을 나타내는 파일 디스크립터
- const void* buf: 전송할 데이터가 저장된 버퍼의 주소 값
- size_t nbytes: 전송할 데이터의 바이트 수
// 성공 시 전달한 바이트 수, 실패 시 -1 반환
#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t nbytes);
파일 생성 및 데이터 저장 예제 코드
open과 write를 이용해서 파일을 생성하고 데이터를 저장하는 코드입니다.
파일과 관련된 작업이 끝난 후에는 close를 이용해서 파일을 닫아줬습니다.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
void error_handling(char* message);
int main(int argc, char* argv[]) {
int fd;
char buf[] = "justweon\n";
// 아무것도 저장되어있지 않은(O_TRUNC) 새로운 파일이 생성(O_CREAT)되어 쓰기만 가능(O_WRONLY)하게
fd = open("data.txt", O_CREAT | O_WRONLY | O_TRUNC);
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);
}
결과는 아래와 같습니다.
파일에 저장된 데이터 읽기
read 함수는 데이터를 입력(수신)하는 기능의 함수입니다.
이 함수가 전달받는 인자는 다음과 같습니다.
- int fd: 데이터 수신대상을 나타내는 파일 디스크립터
- void* buf: 수신한 데이터를 저장할 버퍼의 주소 값 전달
- size_t nbytes: 수신할 최대 바이트 수 전달
// 성공 시 수신한 바이트 수(단, 파일의 끝을 만나면 0), 실패 시 -1 반환
#include <unistd.h>
ssize_t read(int fd, void* buf, size_t nbytes);
데이터 읽기 예제 코드
앞서 저장한 data.txt로부터 데이터를 read 함수를 이용해서 읽어들이는 코드입니다.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 100
void error_handling(char* message);
int main(int argc, char* argv[]) {
int fd;
char buf[BUF_SIZE];
fd = open("data.txt", O_RDONLY);
if (fd == -1)
error_handling("open() error");
printf("file descriptor: %d\n", fd);
int len;
if ((len = read(fd, buf, sizeof(buf))) == -1)
error_handling("read() error");
printf("file data: %s", buf);
printf("length: %d\n", len);
close(fd);
return 0;
}
void error_handling(char* message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
결과는 아래와 같습니다.
파일 디스크립터와 소켓 예제 코드
이번에는 파일도 생성해보고 소켓도 생성해보겠습니다.
#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_STREAM, 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;
}
결과는 아래와 같습니다.
출력된 값을 보면 일련의 순서대로 넘버링 되는 것을 알 수 있습니다.
파일 디스크립터가 3부터 할당되는 것은 이미 0, 1, 2는 표준 입출력에 쓰이기 때문입니다.
'네트워크 프로그래밍 > C' 카테고리의 다른 글
[네트워크 프로그래밍/C] 네트워크 바이트 순서와 인터넷 주소 변환 (0) | 2022.02.21 |
---|---|
[네트워크 프로그래밍/C] 주소정보의 표현 (0) | 2022.02.17 |
[네트워크 프로그래밍/C] 소켓에 할당되는 IP주소와 PORT 번호 (0) | 2022.02.17 |
[네트워크 프로그래밍/C] 소켓의 프로토콜과 그에 따른 데이터 전송 특성 (0) | 2022.02.17 |
[네트워크 프로그래밍/C] 네트워크 프로그래밍과 소켓의 이해 (0) | 2022.02.16 |