Computer >> 컴퓨터 >  >> 프로그래밍 >> C 프로그래밍

C언어 select() 시스템 콜로 구현하는 논블로킹(Non-blocking) I/O 완벽 가이드

C언어 select() 시스템 콜로 구현하는 논블로킹(Non-blocking) I/O 완벽 가이드

파일을 읽고 쓰는 I/O 연산은 프로그래밍에서 매우 중요한 역할을 합니다. 하지만 전통적인(블로킹 방식의) I/O 연산은 작업이 완료될 때까지 프로그램의 실행을 멈추게 만들어 지연을 초래할 수 있습니다. 이러한 문제를 해결하기 위해 사용되는 것이 바로 논블로킹(non-blocking) I/O 기법입니다. 논블로킹 I/O를 사용하면 I/O 작업이 진행되는 동안에도 프로그램이 계속 실행될 수 있습니다.

C언어에서 논블로킹 I/O를 구현할 때 가장 널리 쓰이는 도구가 바로 select() 함수입니다. 이 함수는 소켓(socket)이나 파일 핸들(file handle)과 같은 여러 개의 파일 디스크립터(file descriptor)를 동시에 감시하여, 읽기/쓰기 준비 상태나 오류 발생 여부를 확인할 수 있게 해줍니다. 덕분에 우리는 프로그램의 실행을 멈추지 않으면서도 여러 I/O 작업을 효율적으로 관리하고, 다수의 I/O 자원 상태를 지속적으로 점검할 수 있습니다.

이 글에서는 C언어에서 select() 함수를 활용해 논블로킹 I/O를 구현하는 방법을 살펴봅니다. 기본적인 사용법과 함께 실제 예제 코드를 통해 그 적용 방식까지 자세히 설명하겠습니다.

select() 함수란 무엇인가?

select() 함수는 C언어에서 논블로킹 I/O를 구현할 수 있게 해주는 강력한 도구입니다. 이 함수를 사용하면 소켓이나 파일 핸들처럼 여러 개의 파일 디스크립터를 감시하여, 각각이 읽기 또는 쓰기 준비가 되었는지 확인할 수 있습니다.

select() 함수는 세 가지 파일 디스크립터 집합(set)을 인자로 받습니다.

  • 읽기 집합(read set): 읽기 가능 여부를 감시할 디스크립터 목록
  • 쓰기 집합(write set): 쓰기 가능 여부를 감시할 디스크립터 목록
  • 예외 집합(exception set): 오류나 예외 상황을 감시할 디스크립터 목록

이 집합들을 통해 어떤 디스크립터를 어떤 연산 기준으로 감시할지 지정할 수 있습니다. 또한 타임아웃(timeout) 값을 함께 전달하여, 이벤트를 기다릴 최대 시간을 설정할 수 있습니다. 감시 중인 디스크립터 중 하나라도 이벤트가 발생하거나 타임아웃이 만료되면 select()는 반환되며, 준비된 디스크립터에 대한 정보를 알려줍니다. 이러한 방식으로 프로그램 실행을 막지 않고 여러 I/O 작업을 효율적으로 처리할 수 있습니다.

select() 함수의 장점과 단점

장점:

  • 연결(connection)마다 별도의 스레드를 생성하지 않고도 여러 I/O 작업을 처리할 수 있어 리소스 소모를 크게 줄일 수 있습니다.
  • 단일 스레드에서 다중 I/O 이벤트를 관리할 수 있어 서버 애플리케이션 구현에 유리합니다.

단점:

  • 감시할 수 있는 파일 디스크립터의 최대 개수가 운영체제에 의해 제한되는 경우가 많습니다(일반적으로 FD_SETSIZE, 1024개).
  • 파일 디스크립터의 수가 많아질수록 성능이 저하될 수 있습니다.

프로그래밍 예제

다음은 select() 함수를 사용하여 두 개의 파일을 감시하는 예제 코드입니다.

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h> // strlen을 위해 string.h 헤더 포함

int main ()
{
// 감시할 파일 디스크립터
int fd1, fd2;
// 파일을 열거나 생성하고 논블로킹 모드로 설정
fd1 = open ("file1.txt", O_RDONLY | O_NONBLOCK);
fd2 = open ("file2.txt", O_WRONLY | O_NONBLOCK);
fd_set read_fds, write_fds; // 파일 디스크립터 집합
struct timeval timeout; // select의 타임아웃

while (1)
{
FD_ZERO (&read_fds); // 읽기 집합 초기화
FD_ZERO (&write_fds); // 쓰기 집합 초기화

FD_SET(fd1, &read_fds); // read 집합에 fd1 추가
FD_SET(fd2, &write_fds); // write 집합에 fd2 추가
timeout.tv_sec = 4; // 타임아웃 4초 설정
timeout.tv_usec = 0;

int ready_fds = select (fd2 + 1, &read_fds, &write_fds, NULL, &timeout);
if (ready_fds == -1) {
perror("select");
exit (EXIT_FAILURE);
}
else if (ready_fds == 0) {
printf ("Timeout occurred\n");
}
else
{
if (FD_ISSET(fd1, &read_fds)) {
// fd1이 읽기 준비 상태인 경우
char buffer [100]; // 읽기용 버퍼 생성
ssize_t bytesRead = read(fd1, buffer, sizeof (buffer) - 1);
if (bytesRead > 0) {
buffer [bytesRead] = '\0'; // 문자열 NULL 종결 처리
printf ("Read from file1.txt: %s \n", buffer);
}
}

if (FD_ISSET (fd2, &write_fds)) {
// fd2가 쓰기 준비 상태인 경우
const char* message = "Good morning";
ssize_t bytesWritten = write (fd2, message, strlen (message));
if (bytesWritten > 0) {
printf ("Wrote to file2.txt: %s \n", message);
}
}
}
}
// 파일 디스크립터 닫기
close (fd1);
close (fd2);
return 0;
}

실행 결과

Wrote to file2.txt: Good morning
Wrote to file2.txt: Good morning
Wrote to file2.txt: Good morning
Wrote to file2.txt: Good morning
Timeout occurred

코드 설명

위 프로그램은 C언어에서 select()를 활용해 논블로킹 I/O를 구현하여 "file1.txt"와 "file2.txt" 두 개의 파일을 감시합니다. 파일들이 논블로킹 모드(O_NONBLOCK)로 설정되었기 때문에, 프로그램은 파일의 읽기나 쓰기가 완전히 끝날 때까지 기다리지 않고 계속 실행될 수 있습니다.

예제 코드는 select() 함수를 사용하여 지정된 타임아웃 시간(4초) 내에 파일에 어떤 활동이 있는지 검사합니다. 타임아웃 동안 아무런 활동이 없으면 단순히 "Timeout occurred"를 출력합니다. 반대로 활동이 감지되면 어느 파일에서 활동이 발생했는지 확인합니다.

  • "file1.txt"에 활동이 있다면 → 파일 내용을 읽어 화면에 출력합니다.
  • "file2.txt"에 활동이 있다면 → 파일에 "Good morning" 메시지를 기록(write)합니다.

프로그램은 종료될 때까지 무한히 파일들을 감시하며, 마지막으로 파일 디스크립터를 닫아(close) 시스템 자원을 해제합니다.

마무리

C언어의 select() 함수는 논블로킹 I/O 연산을 구현하기 위한 훌륭한 솔루션입니다. 여러 파일 디스크립터를 동시에 감시할 수 있게 함으로써, 프로그램의 실행을 차단하지 않고 다수의 I/O 작업을 효율적으로 처리할 수 있습니다.

다만, 감시 가능한 파일 디스크립터의 최대 개수 제한이나 디스크립터 수가 많아질 때 발생할 수 있는 성능 문제 같은 단점도 함께 고려해야 합니다. 이러한 한계에도 불구하고 select() 함수는 여전히 C 프로그램에서 논블로킹 I/O를 관리하기 위한 좋은 선택지입니다.

저자 소개

C언어 select() 시스템 콜로 구현하는 논블로킹(Non-blocking) I/O 완벽 가이드

Bamdeb Ghosh

Bamdeb Ghosh는 무선 네트워킹 분야에서 풍부한 실무 경험을 보유한 전문가입니다. 유선 및 무선 네트워크 환경에서의 Wireshark 패킷 캡처 분석 전문가이며, Android, Bluetooth, Linux 명령어, Python 등에 대한 폭넓은 지식을 갖추고 있습니다. 그의 사이트 wifisharks.com에서 더 많은 정보를 확인할 수 있습니다.