다중화된 입출력 파일 디스크립터에서 발생하는 입출력 이벤트를 확인하는 방법에 대해 알아본다.
동기화된 다중 입출력 메커니즘 select()
#include <sys/select.h>
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- n : 감시하는 파일 디스크립터 중 가장 큰 것에 +1을 한 값
- readfds : 읽기를 감시하는 파일 디스크립터 집합
- writefds : 쓰기를 감시하는 파일 디스크립터 집합
- exceptfds : 예외가 발생하는 지 감시하는 파일 디스크립터 집합 (주로 소켓)
- timeout : 타임 아웃 시간
성공 시 입출력이 준비된 fd 개수를 반환한다. 타임 아웃시 0을 반환, 에러 시 -1을 반환.
#include <sys/timeval.h>
struct timeval {
long tv_sec; // seconds
long tv_usec; // microseconds
};
얼마나 이벤트가 발생할때 까지 기다릴 것인가에 대한 시간을 지정하는 구조체
사용법
SELECT를 호출하기전에 해야할 것
1. 모든 감시할 fd_set*을 초기화 한다. 매번 SELECT 호출 후 fd_set 배열 상태가 변할 수 있기 때문이다.
fd_set writefds;
FD_ZERO(&writefds);
2. 감시할 파일 디스크립터를 추가한다.
FD_SET(fd, &writefds); // 집합에 추가
FD_CLR(fd, &writefds); // 집합에 삭제
SELECT 호출 이후
3. 감시되는 파일 디스크립터가 블록없이 I/O 가능한지 확인한다.
if (FD_ISSET(fd, &readfds))
// fd는 블록없이 읽기 가능하다.
간단한 SELECT를 이용한 행맨 프로그램
#include "../common.h"
#include "../utility.h"
#define TIMEOUT 5
#define BUF_LEN 1024
class HangManProgram
{
private:
int _max_trials = 20;
unsigned int _target_length;
std::string _target_string;
std::vector<bool> _matched;
vector<char> _buffer;
fd_set _read_fds;
struct timeval _wait_info;
public:
HangManProgram(unsigned int len = 3)
:
_max_trials(10),
_read_fds{},
_target_length(len),
_wait_info{}
{
}
void Start(const char* m = nullptr)
{
if (m == nullptr)
{
_target_string = generate_random_word(_target_length);
}
else
{
_target_string = m;
}
_matched.resize(_target_string.size(), false);
_buffer.resize(_target_length + 1, 0);
fill(_matched.begin(), _matched.end(), false);
printf("Enter %d-length words \n", _target_length);
bool win = false;
int try_count = 0, ret;
while (try_count++ < _max_trials && win == false)
{
Ready();
ret = select(STDIN_FILENO + 1, &_read_fds, NULL, NULL, &_wait_info);
if (ret == -1)
{
print_last_error_message();
break;
}
if (ret == 0)
{
printf("Timeout.\n");
continue;
}
if (FD_ISSET(STDIN_FILENO, &_read_fds))
{
memset(_buffer.data(), 0, _target_length + 1);
int len = read(STDIN_FILENO, _buffer.data(), _target_length);
if (len == -1)
{
print_last_error_message();
break;
}
if (len == 1 && _buffer[0] == '\n')
{
--try_count;
continue;
}
if (len == _target_length)
{
win = Check();
}
}
PrintState();
}
if (win)
{
printf("You win!\n");
}
else
{
printf("Fail\n");
}
}
private:
void Ready()
{
FD_ZERO(&_read_fds);
FD_SET(STDIN_FILENO, &_read_fds);
_wait_info.tv_sec = TIMEOUT;
_wait_info.tv_usec = 0;
}
bool Check()
{
int count = 0;
for (unsigned int i = 0; i < _target_length; ++i)
{
if (_buffer[i] == _target_string[i])
{
++count;
_matched[i] = true;
}
}
return count == _target_length;
}
void PrintState()
{
string p(_target_length, '_');
for (int i = 0; i < _target_length; ++i)
{
if (_matched[i])
{
p[i] = _target_string[i];
}
}
printf("%s\n", p.c_str());
}
};
int main()
{
HangManProgram program(3);
program.Start("abc");
return 0;
}
파일 디스크립터 집합을 사용하지 않는 poll()
#include <poll.h>
int poll (struct pollfd* fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; // file descriptor
short events; // event to observer
short revent; // event that occurs
};
- poll 함수는 감시할 파일 디스크립터와 정보를 배열로 직접 전달한다.
- nfds는 파일 디스크립터 배열의 원소 수이다.
호출이 성공하면 revents가 0이 아닌 구조체 개수를 반환한다. 0이라면 타임 아웃, -1이라면 에러 발생.
events로 설정가능한 이벤트는 다음과 같다.
- POLLIN : 읽을 데이터가 존재
- POLLOUT : 쓰기가 가능.
- POLLRDNORM : 일반 데이터를 읽기 가능
등등
poll() vs select()
- poll() 의 경우 select() 방식에서 가장 높은 파일 디스크립터 인자 + 1을 전달할 필요가 없다.
- 파일 디스크립터 비트를 사용하지 않기 때문에 poll()이 약간 더 좋다.
- select() 방식에서 항상 파일 디스크립터 배열을 초기화해야한다.
- select() 호출 이후 timeout에 해당하는 구조체를 다시 초기화 해야한다.
'C++ > linux' 카테고리의 다른 글
(6) Pthread 쓰레드 / PMutex 뮤텍스 (0) | 2022.05.12 |
---|---|
(5) 프로세스 관리 (0) | 2022.05.11 |
(4) 고급 입출력 readv/writev/epoll/mmap (0) | 2022.05.10 |
(3) 버퍼 입출력 (0) | 2022.05.10 |
(1) errno와 파일 입출력 (0) | 2022.05.10 |