libevent - 비동기 IO 프로 그래 밍

56578 단어 libevent
원래 주소: http://www.wangafu.net/~nickm/libevent-book/01_intro.html
... 에 대하 여
이 문 서 는 libevent 2.0 을 사용 하여 효율 적 이 고 이식 가능 한 비동기 네트워크 프로그램 을 만 드 는 방법 을 알려 주 는 데 목적 을 둡 니 다. 우 리 는 가정 합 니 다.
  • 당신 은 C 언어 를 잘 알 고 있 습 니 다.
  • C 언어 기본 네트워크 인터페이스 (예 를 들 어 socket (), connect () 등) 를 잘 알 고 있 습 니 다.

  • 예제 프로그램
    이 문 서 는 모든 루틴 을 Linux, FreeBSD, OpenBSD, NetBSD, Mac OS X, Solaris, Android 플랫폼 에서 실행 할 수 있 습 니 다. 그러나 그 중 일 부 는 Windows 에서 정상적으로 컴 파일 할 수 없 을 수도 있 습 니 다.
     
    비동기 IO 프로 그래 밍
    대부분의 프로그래머 들 은 처음에는 차단 식 IO 프로 그래 밍 을 사용 했다.동기 화 IO 호출 이란 일반적으로 IO 호출 작업 을 시작 하거나 프로그램 이 완 료 된 후에 돌아 오 거나 일정 시간 을 기 다 렸 다가 돌아 오 는 것 을 말한다.예 를 들 어 connect 함 수 를 호출 하여 TCP 연결 을 시작 합 니 다. 쌍 끝 에서 SYN ACK (연결 성공) 메 시 지 를 받 거나 오래 기다 리 지 않 으 면 이 함수 가 돌아 오지 않 습 니 다. 
    다음은 www. google. com 호스트 에 연결 하여 간단 한 http 요청 을 보 내 고 결 과 를 인쇄 하 는 간단 한 차단 IO 루틴 입 니 다.
    Example: A simple blocking HTTP client
    /* For sockaddr_in */
    #include <netinet/in.h>
    /* For socket functions */
    #include <sys/socket.h>
    /* For gethostbyname */
    #include <netdb.h>
    
    #include <unistd.h>
    #include <string.h>
    #include <stdio.h>
    
    int main(int c, char **v)
    {
        const char query[] =
            "GET / HTTP/1.0\r
    " "Host: www.google.com\r
    " "\r
    "; const char hostname[] = "www.google.com"; struct sockaddr_in sin; struct hostent *h; const char *cp; int fd; ssize_t n_written, remaining; char buf[1024]; /* Look up the IP address for the hostname. Watch out; this isn't threadsafe on most platforms. */ h = gethostbyname(hostname); if (!h) { fprintf(stderr, "Couldn't lookup %s: %s", hostname, hstrerror(h_errno)); return 1; } if (h->h_addrtype != AF_INET) { fprintf(stderr, "No ipv6 support, sorry."); return 1; } /* Allocate a new socket */ fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); return 1; } /* Connect to the remote host. */ sin.sin_family = AF_INET; sin.sin_port = htons(80); sin.sin_addr = *(struct in_addr*)h->h_addr; if (connect(fd, (struct sockaddr*) &sin, sizeof(sin))) { perror("connect"); close(fd); return 1; } /* Write the query. */ /* XXX Can send succeed partially? */ cp = query; remaining = strlen(query); while (remaining) { n_written = send(fd, cp, remaining, 0); if (n_written <= 0) { perror("send"); return 1; } remaining -= n_written; cp += n_written; } /* Get an answer back. */ while (1) { ssize_t result = recv(fd, buf, sizeof(buf), 0); if (result == 0) { break; } else if (result < 0) { perror("recv"); close(fd); return 1; } fwrite(buf, 1, result, stdout); } close(fd); return 0; }

    위의 모든 네트워크 호출 은 차단 식 입 니 다. gethostby name 은 웹 사이트 www. google. com 을 분석 하 는 데 성공 하거나 실패 한 후에 만 돌아 갑 니 다.connect 는 연결 이 성공 한 후에 만 돌아 갑 니 다.recv 는 데 이 터 를 받 거나 연결 을 닫 은 후에 만 돌아 갑 니 다.send 는 최소한 데이터 가 커 널 의 쓰기 버퍼 에 새로 고 쳐 진 후에 야 돌아 올 수 있 습 니 다.
    현재 로 서 는 차단 식 IO 가 나 쁘 지 않다.이러한 기능 만 완성 한다 면 차단 식 IO 는 좋 은 선택 이다.하지만 여러 연결 을 처리 하기 위해 프로그램 을 만들어 야 한다 면 어떻게 하 시 겠 습 니까?구체 적 으로 두 연결 에서 데 이 터 를 읽 고 싶 지만 어떤 연결 데이터 가 먼저 도착 할 지 모 르 겠 습 니 다. 아래 와 같이 코드 를 작성 해 서 는 안 됩 니 다.
    Bad Example
    /* This won't work. */
    char buf[1024];
    int i, n;
    while (i_still_want_to_read()) {
        for (i=0; i<n_sockets; ++i) {
            n = recv(fd[i], buf, sizeof(buf), 0);
            if (n==0)
                handle_close(fd[i]);
            else if (n<0)
                handle_error(fd[i], errno);
            else
                handle_input(fd[i], buf, n);
        }
    }

    설명자 fd [2] 의 데이터 가 먼저 도착 하 더 라 도 fd [0] 와 fd [1] 의 데이터 가 완 료 된 후에 야 fd [2] 의 데 이 터 를 읽 을 수 있 기 때 문 입 니 다.
    이 문 제 는 다 중 스 레 드 나 다 중 프로 세 스 서버 로 해결 할 수 있 습 니 다.가장 간단 한 방법 은 모든 연결 에 하나의 단독 프로 세 스 (또는 스 레 드) 를 만들어 데이터 읽 기와 쓰 기 를 처리 하 는 것 이다.연결 마다 프로 세 스 가 있 기 때문에 연결 을 기다 리 는 차단 호출 (accept) 은 다른 연결 을 막 지 않 습 니 다.
    다음은 간단 한 서버 프로그램 입 니 다. 포트 47013 에서 연결 을 기다 리 고 있 습 니 다. socket 의 입력 단 에서 한 줄 의 데 이 터 를 읽 고 ROT 13 변환 을 한 다음 socket 의 출력 단 에서 이 데 이 터 를 클 라 이언 트 로 되 돌려 줍 니 다. 이 프로그램 은 유 닉 스 시스템 의 fork 호출 을 사용 하여 모든 연결 에 프로 세 스 를 만 듭 니 다.
    Example: Forking ROT13 server
    /* For sockaddr_in */
    #include <netinet/in.h>
    /* For socket functions */
    #include <sys/socket.h>
    
    #include <unistd.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    #define MAX_LINE 16384
    
    char
    rot13_char(char c)
    {
     /* We don't want to use isalpha here; setting the locale would change
     * which characters are considered alphabetical. */
        if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
            return c + 13;
        else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
            return c - 13;
        else
            return c;
    }
    
    void
    child(int fd)
    {
        char outbuf[MAX_LINE+1];
        size_t outbuf_used = 0;
        ssize_t result;
    
        while (1) {
            char ch;
            result = recv(fd, &ch, 1, 0);
            if (result == 0) {
                break;
            } else if (result == -1) {
                perror("read");
                break;
            }
    
     /* We do this test to keep the user from overflowing the buffer. */
            if (outbuf_used < sizeof(outbuf)) {
                outbuf[outbuf_used++] = rot13_char(ch);
            }
    
            if (ch == '
    ') { send(fd, outbuf, outbuf_used, 0); outbuf_used = 0; continue; } } } void run(void) { int listener; struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0; sin.sin_port = htons(40713); listener = socket(AF_INET, SOCK_STREAM, 0); #ifndef WIN32 { int one = 1; setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); } #endif if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) { perror("bind"); return; } if (listen(listener, 16)<0) { perror("listen"); return; } while (1) { struct sockaddr_storage ss; socklen_t slen = sizeof(ss); int fd = accept(listener, (struct sockaddr*)&ss, &slen); if (fd < 0) { perror("accept"); } else { if (fork() == 0) { child(fd); exit(0); } } } } int main(int c, char **v) { run(); return 0; }

    이로써 우 리 는 여러 연결 을 처리 하 는 완벽 한 해결 방안 이 있 는 것 같다.그렇지 않 으 면 먼저 프로 세 스 생 성 (심지어 스 레 드 생 성) 이 일부 플랫폼 에서 의 비용 은 매우 비싸다.사실상 무제 한 프로 세 스 를 만 드 는 것 이 아니 라 스 레 드 탱크 기술 을 사용 할 수 있 습 니 다.만약 당신 의 프로그램 이 수천 개의 연결 을 한 번 에 처리 해 야 한다 면, 이것 은 매우 비효 율 적 으로 변 할 것 입 니 다. 차라리 모든 CPU 가 적은 라인 만 처리 하도록 하 는 것 이 낫 습 니 다.
    그러나 다 중 스 레 드 가 다 중 연결 문 제 를 처리 하 는 답 이 아니라면 어 떨 까?유 닉 스 플랫폼 에서 sockets 를 비 차단 모드 로 설정 할 수 있 습 니 다.아래 의 호출 은 바로 이 일 을 하 는 데 쓰 인 다.
    fcntl(fd, F_SETFL, O_NONBLOCK);

    이 때 fd 는 socket 설명자 입 니 다. [1] socket 설명자 fd 를 비 차단 모드 로 설정 하면 fd 에 대한 모든 네트워크 호출 이 완료 되 거나 즉시 작업 을 마치 고 돌아 갑 니 다.또는 오류 코드 를 되 돌려 "작업 을 계속 할 수 없습니다. 다시 시도 하 십시오" 라 고 표시 합 니 다.따라서 우리 의 더 블 socket 프로그램 은 이렇게 쓸 수 있다.
    Bad Example: busy-polling all sockets
    /* This will work, but the performance will be unforgivably bad. */
    int i, n;
    char buf[1024];
    for (i=0; i < n_sockets; ++i)
        fcntl(fd[i], F_SETFL, O_NONBLOCK);
    
    while (i_still_want_to_read()) {
        for (i=0; i < n_sockets; ++i) {
            n = recv(fd[i], buf, sizeof(buf), 0);
            if (n == 0) {
                handle_close(fd[i]);
            } else if (n < 0) {
                if (errno == EAGAIN)
                     ; /* The kernel didn't have any data for us to read. */
                else
                     handle_error(fd[i], errno);
             } else {
                handle_input(fd[i], buf, n);
             }
        }
    }

    비 차단 사 socket 을 사 용 했 기 때문에 위의 코드 는 '어렵 게' 실 행 됩 니 다.그 성능 은 두 가지 이유 로 엉망 이 될 수 있 습 니 다. 1. 두 연결 에 데이터 가 없 을 때 이 순환 은 무한 한 자전 (spin) 으로 CPU 자원 을 소모 합 니 다. 2. 이러한 방법 으로 하나 이상 의 연결 을 처리 할 때 데이터 가 도착 하 든 안 하 든 모든 연결 에 커 널 호출 이 발생 합 니 다.따라서 정확 한 방법 은 커 널 에 알려 주 는 것 이다. 기 다 려 라. 어떤 socket 에 데이터 가 준비 되 었 을 때 나 에 게 도대체 어떤 것 인지 알려 주면 된다.
    가장 오래된 방식 은 select 를 사용 하 는 것 이 고 지금도 사용 하고 있다.select () 는 3 개의 설명자 집합 fds (바이트 배열 로 구현) 를 받 습 니 다. 하 나 는 읽 기, 하 나 는 쓰기, 그리고 하 나 는 이상 을 표시 합 니 다.설명자 가 어떤 socket 에 집중 할 때 까지 기다 리 고 있 습 니 다. 그리고 이 집합 을 수정 하여 이 준 비 된 socket 만 포함 시 켜 서 나중에 사용 할 수 있 도록 합 니 다.
    다음은 select 를 사용 한 예 입 니 다.
    Example: Using select
    /* If you only have a couple dozen fds, this version won't be awful */
    fd_set readset;
    int i, n;
    char buf[1024];
    
    while (i_still_want_to_read()) {
        int maxfd = -1;
        FD_ZERO(&readset);
    
     /* Add all of the interesting fds to readset */
        for (i=0; i < n_sockets; ++i) {
             if (fd[i]>maxfd) maxfd = fd[i];
             FD_SET(i, &readset);
        }
    
     /* Wait until one or more fds are ready to read */
        select(maxfd+1, &readset, NULL, NULL, NULL);
    
     /* Process all of the fds that are still set in readset */
        for (i=0; i < n_sockets; ++i) {
            if (FD_ISSET(fd[i], &readset)) {
                n = recv(fd[i], buf, sizeof(buf), 0);
                if (n == 0) {
                    handle_close(fd[i]);
                } else if (n < 0) {
                    if (errno == EAGAIN)
                         ; /* The kernel didn't have any data for us to read. */
                    else
                         handle_error(fd[i], errno);
                 } else {
                    handle_input(fd[i], buf, n);
                 }
            }
        }
    }

    다음은 select 로 ROT 13 server 를 다시 구현 하 였 습 니 다.
    Example: select()-based ROT13 server
    /* For sockaddr_in */
    #include <netinet/in.h>
    /* For socket functions */
    #include <sys/socket.h>
    /* For fcntl */
    #include <fcntl.h>
    /* for select */
    #include <sys/select.h>
    
    #include <assert.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    
    #define MAX_LINE 16384
    
    char
    rot13_char(char c)
    {
     /* We don't want to use isalpha here; setting the locale would change
     * which characters are considered alphabetical. */
        if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
            return c + 13;
        else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
            return c - 13;
        else
            return c;
    }
    
    struct fd_state {
        char buffer[MAX_LINE];
        size_t buffer_used;
    
        int writing;
        size_t n_written;
        size_t write_upto;
    };
    
    struct fd_state *
    alloc_fd_state(void)
    {
        struct fd_state *state = malloc(sizeof(struct fd_state));
        if (!state)
            return NULL;
        state->buffer_used = state->n_written = state->writing =
            state->write_upto = 0;
        return state;
    }
    
    void
    free_fd_state(struct fd_state *state)
    {
        free(state);
    }
    
    void
    make_nonblocking(int fd)
    {
        fcntl(fd, F_SETFL, O_NONBLOCK);
    }
    
    int
    do_read(int fd, struct fd_state *state)
    {
        char buf[1024];
        int i;
        ssize_t result;
        while (1) {
            result = recv(fd, buf, sizeof(buf), 0);
            if (result <= 0)
                break;
    
            for (i=0; i < result; ++i)  {
                if (state->buffer_used < sizeof(state->buffer))
                    state->buffer[state->buffer_used++] = rot13_char(buf[i]);
                if (buf[i] == '
    ') { state->writing = 1; state->write_upto = state->buffer_used; } } } if (result == 0) { return 1; } else if (result < 0) { if (errno == EAGAIN) return 0; return -1; } return 0; } int do_write(int fd, struct fd_state *state) { while (state->n_written < state->write_upto) { ssize_t result = send(fd, state->buffer + state->n_written, state->write_upto - state->n_written, 0); if (result < 0) { if (errno == EAGAIN) return 0; return -1; } assert(result != 0); state->n_written += result; } if (state->n_written == state->buffer_used) state->n_written = state->write_upto = state->buffer_used = 1; state->writing = 0; return 0; } void run(void) { int listener; struct fd_state *state[FD_SETSIZE]; struct sockaddr_in sin; int i, n, maxfd; fd_set readset, writeset, exset; sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0; sin.sin_port = htons(40713); for (i = 0; i < FD_SETSIZE; ++i) state[i] = NULL; listener = socket(AF_INET, SOCK_STREAM, 0); make_nonblocking(listener); #ifndef WIN32 { int one = 1; setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); } #endif if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) { perror("bind"); return; } if (listen(listener, 16)<0) { perror("listen"); return; } FD_ZERO(&readset); FD_ZERO(&writeset); FD_ZERO(&exset); while (1) { maxfd = listener; FD_ZERO(&readset); FD_ZERO(&writeset); FD_ZERO(&exset); FD_SET(listener, &readset); for (i=0; i < FD_SETSIZE; ++i) { if (state[i]) { if (i > maxfd) maxfd = i; FD_SET(i, &readset); if (state[i]->writing) { FD_SET(i, &writeset); } } } n = select(maxfd+1, &readset, &writeset, &exset, NULL); if (FD_ISSET(listener, &readset)) { struct sockaddr_storage ss; socklen_t slen = sizeof(ss); int fd = accept(listener, (struct sockaddr*)&ss, &slen); if (fd < 0) { perror("accept"); } else if (fd > FD_SETSIZE) { close(fd); } else { make_nonblocking(fd); state[fd] = alloc_fd_state(); assert(state[fd]);/*XXX*/ } } for (i=0; i < maxfd+1; ++i) { int r = 0; if (i == listener) continue; if (FD_ISSET(i, &readset)) { r = do_read(i, state[i]); } if (r == 0 && FD_ISSET(i, &writeset)) { r = do_write(i, state[i]); } if (r) { free_fd_state(state[i]); state[i] = NULL; close(i); } } } } int main(int c, char **v) { setvbuf(stdout, NULL, _IONBF, 0); run(); return 0; }

    아직 안 끝났어!selection 바이트 배열 을 만 들 고 읽 는 데 걸 리 는 시간 은 들 어 오 는 최대 설명자 집합 크기 와 비례 하기 때문에 socket 의 수량 이 많 을 때 select 는 시간 이 많이 걸 립 니 다.[ 2 ]
    서로 다른 운영 체제 가 select 에 서로 다른 대체 함 수 를 제공 합 니 다.poll (), epoll (), kqueue (), evports, / dev / poll 이 있 습 니 다.이 모든 것 은 select () 보다 성능 이 좋 습 니 다. poll () 을 제외 한 나머지 함수 가 작 동 합 니 다. socket 을 추가 하고 socket 을 삭제 하 며 특정한 socketio 가 준비 되 었 을 때 O (1) 시간 복잡 도의 성능 을 얻 을 수 있 습 니 다.
    안 타 깝 게 도 이 고성능 인터페이스 들 은 통 일 된 기준 이 하나 도 없다.Linux 는 epoll (), the BSDs (including Darwin) 는 kqueue (), Solaris 는 evports 와 / dev / poll 에 대응 합 니 다. 다른 플랫폼 의 인터페이스 가 있 는 운영 체제 가 없습니다.따라서 고성능 의 이식 가능 한 비동기 프로그램 을 쓰 려 면 모든 플랫폼 에서 이러한 고성능 인터페이스의 추상 적 이 고 이 플랫폼 에서 사용 할 때 가장 효율 적 인 인 인 터 페 이 스 를 제공 해 야 한다.
    이것 은 바로 libevent 의 저급 API 가 하 는 일 입 니 다. 서로 다른 플랫폼 의 select 대체 함수 에 일치 하 는 인 터 페 이 스 를 제공 하고 응용 프로그램 이 실행 하 는 플랫폼 에서 사용 할 수 있 는 가장 효율 적 인 버 전 을 사용 할 것 을 보장 합 니 다.
    다음은 비동기 ROT 13 서버 의 또 다른 버 전 입 니 다.select () 대신 Libevent 2 를 사용 합 니 다.주의, 설명자 집합 fdsets 가 사 라 졌 습 니 다. 이제 (IO) 사건 과 구조 체 eventbase 관련 (associate and disassociate), select (), poll (), epoll (), kqueue () 등 중 하나 일 수 있 습 니 다.
    Example: A low-level ROT13 server with Libevent
    /* For sockaddr_in */
    #include <netinet/in.h>
    /* For socket functions */
    #include <sys/socket.h>
    /* For fcntl */
    #include <fcntl.h>
    
    #include <event2/event.h>
    
    #include <assert.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    
    #define MAX_LINE 16384
    
    void do_read(evutil_socket_t fd, short events, void *arg);
    void do_write(evutil_socket_t fd, short events, void *arg);
    
    char
    rot13_char(char c)
    {
     /* We don't want to use isalpha here; setting the locale would change
     * which characters are considered alphabetical. */
        if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
            return c + 13;
        else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
            return c - 13;
        else
            return c;
    }
    
    struct fd_state {
        char buffer[MAX_LINE];
        size_t buffer_used;
    
        size_t n_written;
        size_t write_upto;
    
        struct event *read_event;
        struct event *write_event;
    };
    
    struct fd_state *
    alloc_fd_state(struct event_base *base, evutil_socket_t fd)
    {
        struct fd_state *state = malloc(sizeof(struct fd_state));
        if (!state)
            return NULL;
        state->read_event = event_new(base, fd, EV_READ|EV_PERSIST, do_read, state);
        if (!state->read_event) {
            free(state);
            return NULL;
        }
        state->write_event =
            event_new(base, fd, EV_WRITE|EV_PERSIST, do_write, state);
    
        if (!state->write_event) {
            event_free(state->read_event);
            free(state);
            return NULL;
        }
    
        state->buffer_used = state->n_written = state->write_upto = 0;
    
        assert(state->write_event);
        return state;
    }
    
    void
    free_fd_state(struct fd_state *state)
    {
        event_free(state->read_event);
        event_free(state->write_event);
        free(state);
    }
    
    void
    do_read(evutil_socket_t fd, short events, void *arg)
    {
        struct fd_state *state = arg;
        char buf[1024];
        int i;
        ssize_t result;
        while (1) {
            assert(state->write_event);
            result = recv(fd, buf, sizeof(buf), 0);
            if (result <= 0)
                break;
    
            for (i=0; i < result; ++i)  {
                if (state->buffer_used < sizeof(state->buffer))
                    state->buffer[state->buffer_used++] = rot13_char(buf[i]);
                if (buf[i] == '
    ') { assert(state->write_event); event_add(state->write_event, NULL); state->write_upto = state->buffer_used; } } } if (result == 0) { free_fd_state(state); } else if (result < 0) { if (errno == EAGAIN) // XXXX use evutil macro return; perror("recv"); free_fd_state(state); } } void do_write(evutil_socket_t fd, short events, void *arg) { struct fd_state *state = arg; while (state->n_written < state->write_upto) { ssize_t result = send(fd, state->buffer + state->n_written, state->write_upto - state->n_written, 0); if (result < 0) { if (errno == EAGAIN) // XXX use evutil macro return; free_fd_state(state); return; } assert(result != 0); state->n_written += result; } if (state->n_written == state->buffer_used) state->n_written = state->write_upto = state->buffer_used = 1; event_del(state->write_event); } void do_accept(evutil_socket_t listener, short event, void *arg) { struct event_base *base = arg; struct sockaddr_storage ss; socklen_t slen = sizeof(ss); int fd = accept(listener, (struct sockaddr*)&ss, &slen); if (fd < 0) { // XXXX eagain?? perror("accept"); } else if (fd > FD_SETSIZE) { close(fd); // XXX replace all closes with EVUTIL_CLOSESOCKET */ } else { struct fd_state *state; evutil_make_socket_nonblocking(fd); state = alloc_fd_state(base, fd); assert(state); /*XXX err*/ assert(state->write_event); event_add(state->read_event, NULL); } } void run(void) { evutil_socket_t listener; struct sockaddr_in sin; struct event_base *base; struct event *listener_event; base = event_base_new(); if (!base) return; /*XXXerr*/ sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0; sin.sin_port = htons(40713); listener = socket(AF_INET, SOCK_STREAM, 0); evutil_make_socket_nonblocking(listener); #ifndef WIN32 { int one = 1; setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); } #endif if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) { perror("bind"); return; } if (listen(listener, 16)<0) { perror("listen"); return; } listener_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base); /*XXX check it */ event_add(listener_event, NULL); event_base_dispatch(base); } int main(int c, char **v) { setvbuf(stdout, NULL, _IONBF, 0); run(); return 0; }

    코드 디 테 일: "int" 형의 sockets 를 사용 하지 않 고 evutil 을 사 용 했 습 니 다.socket_t。fcntl (O NONBLOCK) 을 사용 하지 않 고 sockets 를 비 차단 모드 로 설정 하 였 으 며 evutil 을 사용 하 였 습 니 다.make_socket_nonblocking。이러한 변 화 는 코드 와 다른 플랫폼 (예 를 들 어 Win 32 networking API) 의 호환성 을 더욱 좋게 한다.
    쉬 운 데?(윈도 는?)
     
     
     
    코드 가 더 효율 적 이지 만 더 복잡 하 다 는 것 을 알 수 있 을 것 이다.이전에 우리 가 프로 세 스 를 만 들 때 모든 연결 을 대신 해서 버퍼 를 관리 할 필요 가 없 었 다. 모든 프로 세 스 는 하나의 독립 된 스 택 버퍼 (stack - allocated buffer) 가 있 었 다.socket 이 읽 을 수 있 거나 쓸 수 있 는 지 표시 할 필요 가 없습니다. 코드 에 포함 되 어 있 습 니 다.모든 작업 이 얼마나 완료 되 었 는 지 추적 하 는 구조 체 가 필요 하지 않 습 니 다. 저 희 는 순환 과 스 택 에서 분 배 된 변수 (stack variables) 만 있 으 면 됩 니 다.
    더 나 아가 windows 프로 그래 밍 베테랑 이 라면 위 에서 처럼 libevent 를 사용 하면 가장 좋 은 성능 을 얻 지 못 한 다 는 것 을 알 게 될 것 이다.윈도 에서 가장 빠 른 비동기 IO 방식 은 select () 와 유사 한 인 터 페 이 스 를 사용 하 는 것 이 아니다. 반대로 완 성 된 포트 IOCP (IO Complete Ports) API 를 사용 했다.모든 네트워크 API 와 달리 포트 IOCP 가 완료 되 었 을 때 socket 작업 이 완료 되 었 을 때 알려 주지 않 습 니 다. 이 때 프로그램 은 Windows networking stack 에 네트워크 작업 을 시작 하 라 고 알려 줍 니 다. 이 작업 이 완료 되 었 을 때 포트 IOCP 가 완료 되 어야 알림 을 보 냅 니 다.
    다행히도 Libevent 2 의 "bufferevents" 는 이 두 가지 문 제 를 해결 했다. 프로그램 을 쉽게 만 들 수 있 을 뿐만 아니 라 windows 와 유 닉 스에 도 효율 적 인 인 인 터 페 이 스 를 제공 했다.
    다음은 마지막 ROT 13 서버 입 니 다. bufferevents API 를 사용 하 세 요.
    Example: A simpler ROT13 server with Libevent
    /* For sockaddr_in */
    #include <netinet/in.h>
    /* For socket functions */
    #include <sys/socket.h>
    /* For fcntl */
    #include <fcntl.h>
    
    #include <event2/event.h>
    #include <event2/buffer.h>
    #include <event2/bufferevent.h>
    
    #include <assert.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    
    #define MAX_LINE 16384
    
    void do_read(evutil_socket_t fd, short events, void *arg);
    void do_write(evutil_socket_t fd, short events, void *arg);
    
    char
    rot13_char(char c)
    {
     /* We don't want to use isalpha here; setting the locale would change
     * which characters are considered alphabetical. */
        if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
            return c + 13;
        else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
            return c - 13;
        else
            return c;
    }
    
    void
    readcb(struct bufferevent *bev, void *ctx)
    {
        struct evbuffer *input, *output;
        char *line;
        size_t n;
        int i;
        input = bufferevent_get_input(bev);
        output = bufferevent_get_output(bev);
    
        while ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF))) {
            for (i = 0; i < n; ++i)
                line[i] = rot13_char(line[i]);
            evbuffer_add(output, line, n);
            evbuffer_add(output, "
    ", 1); free(line); } if (evbuffer_get_length(input) >= MAX_LINE) { /* Too long; just process what there is and go on so that the buffer * doesn't grow infinitely long. */ char buf[1024]; while (evbuffer_get_length(input)) { int n = evbuffer_remove(input, buf, sizeof(buf)); for (i = 0; i < n; ++i) buf[i] = rot13_char(buf[i]); evbuffer_add(output, buf, n); } evbuffer_add(output, "
    ", 1); } } void errorcb(struct bufferevent *bev, short error, void *ctx) { if (error & BEV_EVENT_EOF) { /* connection has been closed, do any clean up here */ /* ... */ } else if (error & BEV_EVENT_ERROR) { /* check errno to see what error occurred */ /* ... */ } else if (error & BEV_EVENT_TIMEOUT) { /* must be a timeout event handle, handle it */ /* ... */ } bufferevent_free(bev); } void do_accept(evutil_socket_t listener, short event, void *arg) { struct event_base *base = arg; struct sockaddr_storage ss; socklen_t slen = sizeof(ss); int fd = accept(listener, (struct sockaddr*)&ss, &slen); if (fd < 0) { perror("accept"); } else if (fd > FD_SETSIZE) { close(fd); } else { struct bufferevent *bev; evutil_make_socket_nonblocking(fd); bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev, readcb, NULL, errorcb, NULL); bufferevent_setwatermark(bev, EV_READ, 0, MAX_LINE); bufferevent_enable(bev, EV_READ|EV_WRITE); } } void run(void) { evutil_socket_t listener; struct sockaddr_in sin; struct event_base *base; struct event *listener_event; base = event_base_new(); if (!base) return; /*XXXerr*/ sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0; sin.sin_port = htons(40713); listener = socket(AF_INET, SOCK_STREAM, 0); evutil_make_socket_nonblocking(listener); #ifndef WIN32 { int one = 1; setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); } #endif if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) { perror("bind"); return; } if (listen(listener, 16)<0) { perror("listen"); return; } listener_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base); /*XXX check it */ event_add(listener_event, NULL); event_base_dispatch(base); } int main(int c, char **v) { setvbuf(stdout, NULL, _IONBF, 0); run(); return 0; }

    정말, 성능 이 어디 까지 좋아 질 수 있 을 까?
     
     
     
    XXXX write an efficiency section here. The benchmarks on the libevent page are really out of date.
    1. 파일 설명 자 는 socket 을 열 때 커 널 할당 번호 입 니 다.이 번 호 를 사용 하여 이 socket 에서 유 닉 스 호출 을 실행 할 수 있 습 니 다. 
    2. 사용자 공간 에서 바이트 배열 을 만 들 고 읽 는 데 걸 리 는 시간 은 select 함수 에 들 어 오 는 설명자 의 개수 와 정비례 하지만 커 널 공간 에서 바이트 배열 을 읽 는 데 걸 리 는 시간 은 이 바이트 배열 의 가장 큰 설명자 와 정비례 합 니 다. 이 최대 설명자 숫자 는 전체 프로그램 이 사용 하 는 설명자 총수 와 같 습 니 다.select 에서 설명자 집합 에 있 는 설명자 갯 수 와 는 무관 합 니 다.

    좋은 웹페이지 즐겨찾기