libevent - 비동기 IO 프로 그래 밍
56578 단어 libevent
... 에 대하 여
이 문 서 는 libevent 2.0 을 사용 하여 효율 적 이 고 이식 가능 한 비동기 네트워크 프로그램 을 만 드 는 방법 을 알려 주 는 데 목적 을 둡 니 다. 우 리 는 가정 합 니 다.
예제 프로그램
이 문 서 는 모든 루틴 을 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 에서 설명자 집합 에 있 는 설명자 갯 수 와 는 무관 합 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
libevent의 사용 방법 - 서버의 간단한 실례다음은 각 함수와 작업에 대해 설명합니다. 1,main (): 주 함수는 연결을 감청하는 플러그인을 만들고 accept () 의 리셋 함수를 만들어서 이벤트 처리 함수를 통해 모든 연결을 처리합니다. 2、accept...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.