C++/linux

(2) 다중 입출력 select와 poll

로파이 2022. 5. 10. 14:44

다중화된 입출력 파일 디스크립터에서 발생하는 입출력 이벤트를 확인하는 방법에 대해 알아본다.

 

동기화된 다중 입출력 메커니즘 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