티스토리 뷰

C | C++

입출력 대기와 검사

야라바 2018. 6. 17. 16:59
728x90

☞ select, FD_ZERO, FD_SET, FD_CLR, FD_ISSET

<stdio.h>
<sys/types.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
void FD_ZERO(fd_set *set);
void FD_SET(int filedes, fd_set *set);
void FD_CLR(int filedes, fd_set *set);
int FD_ISSET(int filedes, const fd_set *set);

아파치 웹서버처럼 여러 클라이언트의 동시 접속 요구를 대응해야 하거나 직렬 통신 서비스를 처리하는 프로그램의 경우 일반적인 프로그램 흐름을 방해 받지 않으면서 입력 처리할 데이터가 있는지, 쓰기 처리한 데이터가 제대로 전송되었는지 등을 확인할 필요가 있습니다. 입력 대기의 경우 가장 기본적인 read() 함수를 사용하여 일부를 처리할 수도 있겠지만 nonblocking 모드로 설정하지 않는 다면 프로그램이 입력 대기에 완전히 중단되어 버리고 nonblocking 모드를 사용하더라도 상당히 비효율적인 처리를 할 수 밖에 없습니다. 이런 경우에는 select() 함수를 사용할 수 있습니다.

fd_set 구조체에 확인할 파일 디스크립터를 설정(FD_SET)하여 입력 준비를 확인할 경우에는 *readfds에 쓰기 준비가 되었는지를 확인할 경우에는 *writefds에 예외 상황이 있는지 확인할 경우에는 *exceptfds에 fd_set 구조체의 내용을 전달합니다. 확인하지 않을 부문은 NULL로 전달합니다. 예를 들어 입력 데이터가 있는지 확인할 경우에는 *readfds에만 전달하고 나머지는 NULL로 합니다. select() 함수는 지정한 파일 스크립트 중의 하나에 대해서 합치하는 이벤트가 있으면 이벤트가 발생한 파일 디스크립터의 개수를 리턴합니다. 정상적인 대기라면 0보다 큰 값을 리턴할 것입니다. timeout으로 지정한 제한 시간 안에 이벤트가 없으면 0을 리턴합니다. 오류가 발생했거나 시그널에 의해 대기가 중단된 경우에는 -1을 리턴합니다. 실제 오류인지, 인터럽트인지등을 확인해야 합니다.



☞ 예제1

static int setup_listeners(server_rec *s)
{
    ap_listen_rec *lr;
    int sockdes;

    if (ap_setup_listeners(s) < 1 ) {
        ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00222)
            "no listening sockets available, shutting down");
        return -1;
    }

    listenmaxfd = -1;
    FD_ZERO(&listenfds);
    for (lr = ap_listeners; lr; lr = lr->next) {
        apr_os_sock_get(&sockdes, lr->sd);
        FD_SET(sockdes, &listenfds);
        if (sockdes > listenmaxfd) {
            listenmaxfd = sockdes;
        }
    }
    return 0;
}
......
int sockdes;
int srv;
struct timeval tv;
int wouldblock_retry;

osthd = apr_os_thread_current();
apr_os_thread_put(&thd, &osthd, pmain);

tv.tv_sec = 1;
tv.tv_usec = 0;

apr_allocator_create(&allocator);
apr_allocator_max_free_set(allocator, ap_max_mem_free);

apr_pool_create_ex(&ptrans, pmain, NULL, allocator);
apr_allocator_owner_set(allocator, ptrans);
apr_pool_tag(ptrans, "transaction");

bucket_alloc = apr_bucket_alloc_create_ex(allocator);

atomic_inc (&worker_thread_count);

while (!die_now) {
    /*
    * (Re)initialize this child to a pre-connection state.
    */
    current_conn = NULL;
    apr_pool_clear(ptrans);

    if ((ap_max_requests_per_child > 0
        && requests_this_child++ >= ap_max_requests_per_child)) {
        DBPRINT1 ("\n**Thread slot %d is shutting down", my_worker_num);
        clean_child_exit(0, my_worker_num, ptrans, bucket_alloc);
    }

    ap_update_child_status_from_indexes(0, my_worker_num, WORKER_READY,
                                        (request_rec *) NULL);

    /*
    * Wait for an acceptable connection to arrive.
    */

    for (;;) {
        if (shutdown_pending || restart_pending || (ap_scoreboard_image->servers[0][my_worker_num].status == WORKER_IDLE_KILL)) {
            DBPRINT1 ("\nThread slot %d is shutting down\n", my_worker_num);
            clean_child_exit(0, my_worker_num, ptrans, bucket_alloc);
        }
        memcpy(&main_fds, &listenfds, sizeof(fd_set));
        srv = select(listenmaxfd + 1, &main_fds, NULL, NULL, &tv);

        if (srv <= 0) {
            if (srv < 0) {
                ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00217)
                    "select() failed on listen socket");
                apr_thread_yield();
            }
            continue;
        }

        /* remember the last_lr we searched last time around so that
        we don't end up starving any particular listening socket */
        if (last_lr == NULL) {
            lr = ap_listeners;
        }
        else {
            lr = last_lr->next;
            if (!lr)
                lr = ap_listeners;
        }
        first_lr = lr;
        do {
            apr_os_sock_get(&sockdes, lr->sd);
            if (FD_ISSET(sockdes, &main_fds))
                goto got_listener;
            lr = lr->next;
            if (!lr)
                lr = ap_listeners;
        } while (lr != first_lr);
......

위의 코드는 아파치 웹서버의 메인 루프로 접속 대기를 위한 메인 소켓 설정과 확인 및 에러 처리 과정등을 참조할 수 있습니다. 메인 루프의 최대 대기 시간은 1초로 설정(tv.tv_sec = 1;)하여 주기적으로 전체 프로그램 운용에 필요한 작업들을 수행하고 있음을 확인할 수 있습니다. 예제처럼 이벤트가 발생한 파일 디스크립터가 무엇인지는 FD_ISSET()로 확인합니다.




728x90

'C | C++' 카테고리의 다른 글

디렉토리 및 파일 관리  (0) 2018.06.17
파일의 제어 조작  (0) 2018.06.17
저수준 입출력(Low-Level Input/Output)  (0) 2018.06.17
스트림 위치 및 버퍼 관리  (0) 2018.06.04
스트림 에러 처리  (0) 2018.06.04