为什么80%的码农都做不了架构师?>>>   

A Tiny Introduction Asynchronous IO

大部初学程序员都是从阻塞IO调用开始的。如果一个IO调用是同步的,当你调用它,它不会返回,直到这个操作完成或者过去足够多的时间使你的网络栈自动放弃。 当你在一个TCP连接上调用connect(),例如,你的操作系统队列一个SYN包到达主机上TCP连接的另一边。它不会把控制权交给你,直到你接收到对面的一个SYN ACK包或者直到过去了足够多的时间以至于它决定放弃。

这有一个非常简单的例子使用阻塞网络调用。它去打开一个www.google.com的连接,发送一个简单的HTTP请求,然后打印响应到标准输出.(google大陆被墙,主机可以换成www.baidu.com)

例子:一个简单的阻塞HTTP 客户端

/* 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\n""Host: www.google.com\r\n""\r\n";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'tthreadsafe 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;
}

上述的代码所有的网络调用都是阻塞的:gethostbyname函数直到www.google.com解析成功或者失败后才会返回;connect函数直到连接成功才返回;recv函数直到接收到数据或者一个关闭才会返回;send函数直到最后刷新它的输出到内核写缓冲区。

现在,IO阻塞并不是不幸的。在此期间如果你的程序不去做其他事情,那么对你来说阻塞IO将工作的很好。 但是,假设你需要写一个程序去处理同时处理多个连接。让我们来具体的举一个例子:加入你想从两个连接中读取输入,但是你不知道那个连接将第一个输入。你不能说

坏例子

/*这些代码不能工作*/
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);elsehandle_input(fd[i], buf, n);}
}

当有数据在fd[2]上到来时,你的程序不能读取fd[2]上的数据,在fd[0]和fd[1]上的数据读完之前。

有时候人们为了解决这个问题,采用多线程,或者多进程服务。其中一个最简单的方法就是用多线程,每一个线程去处理一个连接。这样每一个连接都有一个自己的进程,一个连接的IO阻塞调用等待不会影响其他连接的进程阻塞。

这还有另一个例子程序。这是一个微不足道的服务程序,监听TCP连接端口为40713,从输入一行,读取数据,经过ROT13处理后的数据写出。这里为每一个到来的连接调用Unix的fork()来创建一个新的进程。

例子: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 16384char 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;elsereturn 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 == '\n') {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));}
#endifif (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处理器只能处理几个线程。

但是线程没有解决多个连接,怎么办? 在Unix套接字中,设置你的sockets非阻塞。在Unix中通过下面函数设置。

fcntl(fd, F_SETFL, O_NONBLOCK)

文件描述符fd就是socket函数创建的。一旦你设置了socket 描述符fd为非阻塞,当你让网络去调用fd,调用操作将立即完成或者返回一个错误标明"我不能现在无法取得任何进展,请重试"。所以我们两个socket例子可以写成这样:

坏例子:忙轮询所有套接字

/* 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. */elsehandle_error(fd[i], errno);} else {handle_input(fd[i], buf, n);}}
}

现在,我们使用非阻塞的套接字,上面的代码将会工作,但是那只是勉强的工作。性能将会很糟糕,主要有两个原因。第一,当连接上没有数据去读的,将会一直轮询下去,你的CPU将整个被占用。第二,如果使用这种方法试着处理一个或者两个连接时,你将为每一个做一个内核调用,不管它是不是有数据给你。所以我们需要一种告诉内核"等待那些套接字有数据给我,并告诉我那些已经准备好了"。

旧的解决方案是人们一直使用select()函数解决这个问题.select()函数调用三套fds(以位数组方式实现):一个读,一个写,另一个异常处理。它等待,直到一个套接字从其中一个集合准备好,并且设置了集合包含准备使用的套接字。

这我们还有一个例子,使用select实现:

例子:使用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(fd[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. */elsehandle_error(fd[i], errno);} else {handle_input(fd[i], buf, n);}}}
}

这有一个用select实现的POT13 服务端

例子:select()实现的POT13服务器

/* 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 16384char
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;elsereturn 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] == '\n') {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 = 0;state->writing = 0;return 0;
}void
run(void)
{int listener;struct fd_state *state[FD_SETSIZE];struct sockaddr_in sin;int i, 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));}
#endifif (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);}}}if (select(maxfd+1, &readset, &writeset, &exset, NULL) < 0) {perror("select");return;}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;
}

但是我们并没有做完。因为生成和读select()位数组所消耗的事件将与select提供的最大的fd成正比。当有大量的套接字的时候调用'select()'是很糟糕的。

不同的操作系统提供不同替换函数让你选择,这些包括poll(),epoll(),kqueue(),evports/dev/poll.这些的比select的性能更佳,除poll()之外,其他的在添加一个套接字、删除一个套接字和通知一个套接字IO已经准备就绪的时间复杂度均为O(1)。

不幸的是,没有一个有效的接口作为一个标准。Linux 有epoll(),BSD系统中有kqueue(),Solaris系统中有evports 和 /dev/poll等,不同的系统有不同的实现。所以,如果你想写一个便捷的性能高的异步应用程序,你需要有一个包含这些抽象接口的统一接口,来根据不同的平台提供有效的解决。

这里有一个底层的Libevent API可以为你提供这个统一接口。它提供一个统一的接口来为各种select()替代,使用最有效的版本在任何计算机上运行。

这里还有另一个版本的异步POT13服务器实现.现在我们用libevent 2 来取代select().请注意,fd_sets 结构现在已经消失了:相反的,我们连接和分离事件通过一个event_base结构,那些可能根据select(),poll(),epoll(),kqueue()等 实现的。

例子: 一个底层的libevent 实现的POT13 服务器

/* 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 16384void 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;elsereturn 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] == '\n') {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 macroreturn;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 macroreturn;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));}
#endifif (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;
}

(代码中有些地方需要注意: sockets的类型'int', 我们使用evutil_socket_t类型来替代。 调用evutil_make_socket_nonblocking 来替代fcntl(O_NONBLOCK) 设置socket非阻塞. 这些变化是我们的代码兼容Win32 的网络API)

怎么样,方便吧?(在windows 上会怎么样呢?)

你可能已经注意到了,我们的代码开始变的高效,也变得比较复杂了。我们不需要为每个连接管理缓冲区,每一个进程会单独分配一个堆栈。我们不需要明确的跟踪哪一个套接字是正在读还是正在写:这些隐含在我们代码里。我们不需要一个设计去跟踪多少操作已经完成:我们仅仅使用循环和栈变量。

此外,如果你在Windows上有丰富的网络编程经验,你会发现使用上面的例子不会达到很好的性能。在Windows 上,最快的异步IO方式不是使用select()这样的接口:它是使用IOCP(IO Completion Ports[IO完成端口])API.不同其他的最快的网络API,IOCP不会通知你的程序,当一个套接字已经准备去操作,而是当你的操作执行完成以后才通知你。相反的,程序告诉了Windows 网络栈,去开始一个网络操作,IOCP在程序操作完成后会通知。

幸运是 Libevent 2 的bufferevents接口决绝了这些缺陷:它让程序写起来非常的简单,提供一个接口可以高效的运行在Windows 和 Unix上。

这最后一次展示POT13服务器,通过bufferevents API

例子:一个用libevent实现的很简单的POT13服务端

/* 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 16384void 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;elsereturn 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, "\n", 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, "\n", 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));}
#endifif (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;
}

这一切真的很有效吗?

在这里写一段XXX的效率,对于libevnet来说真的已经过时了。


英文原文链接,出于学习的目的翻译所以翻译此文。在翻译过程中限于个人水平有限,有些地方有些缺陷,还请发现后及时与我联系(mjrao@foxmail.com)或者fork 提交您的pull request。 谢谢!

转载于:https://my.oschina.net/mjRao/blog/666724

libevent -简单的异步IO介绍相关推荐

  1. oracle数据库同步异步优劣点,ORACLE数据库异步IO介绍

    异步IO概念 Linux 异步 I/O (AIO)是 Linux 内核中提供的一个增强的功能.它是Linux 2.6 版本内核的一个标准特性,当然我们在2.4 版本内核的补丁中也可以找到它.AIO 背 ...

  2. ORACLE数据库异步IO介绍

    异步IO概念 Linux 异步 I/O (AIO)是 Linux 内核中提供的一个增强的功能.它是Linux 2.6 版本内核的一个标准特性,当然我们在2.4 版本内核的补丁中也可以找到它.AIO 背 ...

  3. linux mysql 开启异步io_Linux 异步IO介绍

    使用范例: epoll最多的用途就是socket编程,可以大大提高服务器的性能,此处我们实现一个简单的http服务器. #define MAXFDS 128 #define EVENTS 100 #d ...

  4. day36 fullstack gevent模块 IO阻塞和非阻塞 IO多路复用 异步IO介绍 其他的补充

    按时打算发放飞 转载于:https://www.cnblogs.com/number1994/p/8250564.html

  5. 2021年大数据Flink(四十六):扩展阅读 异步IO

    目录 扩展阅读  异步IO 介绍 异步IO操作的需求 使用Aysnc I/O的前提条件 Async I/O API 案例演示 扩展阅读 原理深入 AsyncDataStream 消息的顺序性 扩展阅读 ...

  6. Flink教程(22)- Flink高级特性(异步IO)

    文章目录 01 引言 02 异步IO 2.1 异步IO介绍 2.2 使用Aysnc I/O的前提条件 2.3 Async I/O API 03 案例演示 04 原理深入 4.1 AsyncDataSt ...

  7. 阻塞IO, 非阻塞IO, 同步IO,异步IO

    阻塞IO, 非阻塞IO, 同步IO,异步IO 介绍 先说明几个概念 用户空间与内核空间 为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间(内存)划分为两部分,一部分 ...

  8. 大数据Flink(四十六):扩展阅读 异步IO

    文章目录 扩展阅读  异步IO 介绍 异步IO操作的需求 使用Aysnc I/O的前提条件 Async I/O API

  9. linux下异步IO的简单例子

    首先,贴一下异步IO中用的的一些结构体,因为平常很少用,整理起来方便查看. aio.h中的struct aiocb struct aiocb{int aio_fildes; /* File desri ...

最新文章

  1. Android学习笔记(一) - 如果我们来设计Android
  2. 网站优化之尽量避免重定向(301/302)
  3. Python-Matplotlib动态曲线图(linechart)绘制
  4. [css] 举例说明background-repeat的新属性值:round和space的作用是什么?
  5. C# 定义了 7 种变量类别:静态变量、实例变量、数组元素、值参数、引用参数、输出参数和局部变量
  6. poj 1966 Cable TV Network 顶点连通度
  7. 关于C#打包部署文件夹问题
  8. 【推荐】SQL Server 2008 R2 中英文 开发版/企业版/标准版 下载
  9. java同步三线程打印abc_java多线程打印ABC
  10. web api解决序列化后返回标准时间带T问题
  11. 这 8 个 Python 技巧让你的数据分析提升数倍!
  12. java中session对象及其常用方法
  13. 上班族中流行以貌取人 汉王人脸通变普及
  14. 无人机土方算量-Civil3D版
  15. SecKill学习初步框架时报错记录
  16. cadz轴归零命令_CAD图形如何Z轴归0?
  17. 对账 java 龙果支付,龙果支付开源项目对账接口介绍
  18. 机器学习 --- 逻辑回归
  19. 瑞波基因XAG聚合CEC系统细节举例说明
  20. 武汉 华为 android,【武汉华为手机大全】武汉华为手机报价及图片大全-列表版-ZOL中关村在线...

热门文章

  1. JavaSE(二十三)——JVM
  2. 人工智能 | SLAM与Visual Odometry技术综述(浙江大学智能系统和控制研究所)
  3. 树莓派4B设置静态IP
  4. 作文第一次用计算机350,第一次考试作文350字
  5. 【转】VC窗口刷新InvalidateRect和UpdateWindow RedrawWindow
  6. python wand安装_Python Wand posterize()用法及代码示例
  7. Python-OpenCV 笔记4 -- 形态学操作(Morphological Operations)
  8. 时间序列预测:I概述
  9. AI理论知识基础(22)-逻辑斯蒂映射-伪随机数
  10. 【CV】OpenCV 入门之旅