最近想学习一下libevent,就先翻译一下libevent的官方文档吧.

英文原文链接:http://www.wangafu.net/~nickm/libevent-book/01_intro.html

大部分编程初学者都是从阻塞IO开始的。何谓阻塞IO?,即你进行一个IO调用时,除非这个操作完成,或者超时网络协议栈放弃了,否则这个调用是不返回的.比如你对TCP连接调用“connect()”时,你的操作系统将发送一个SYN包给TCP连接的对端,除非收到对端发送的SYN ACK包或者是超时了,否则connect()将不会返回。

这里是一个简单的使用阻塞网络调用的客户端例子.客户端连接到www.google.com,发起一个HTTP请求,把响应打印到标准输出.

  1. /* For sockaddr_in */
  2. #include <netinet/in.h>
  3. /* For socket functions */
  4. #include <sys/socket.h>
  5. /* For gethostbyname */
  6. #include <netdb.h>
  7.  
  8. #include <unistd.h>
  9. #include <string.h>
  10. #include <stdio.h>
  11.  
  12. int main(int c, char **v)
  13. {
  14. const char query[] =
  15. "GET / HTTP/1.0\r\n"
  16. "Host: www.google.com\r\n"
  17. "\r\n";
  18. const char hostname[] = "www.google.com";
  19. struct sockaddr_in sin;
  20. struct hostent *h;
  21. const char *cp;
  22. int fd;
  23. ssize_t n_written, remaining;
  24. char buf[];
  25.  
  26. /* Look up the IP address for the hostname. Watch out; this isn't
  27. threadsafe on most platforms. */
  28. h = gethostbyname(hostname);
  29. if (!h) {
  30. fprintf(stderr, "Couldn't lookup %s: %s", hostname, hstrerror(h_errno));
  31. return ;
  32. }
  33. if (h->h_addrtype != AF_INET) {
  34. fprintf(stderr, "No ipv6 support, sorry.");
  35. return ;
  36. }
  37.  
  38. /* Allocate a new socket */
  39. fd = socket(AF_INET, SOCK_STREAM, );
  40. if (fd < ) {
  41. perror("socket");
  42. return ;
  43. }
  44.  
  45. /* Connect to the remote host. */
  46. sin.sin_family = AF_INET;
  47. sin.sin_port = htons();
  48. sin.sin_addr = *(struct in_addr*)h->h_addr;
  49. if (connect(fd, (struct sockaddr*) &sin, sizeof(sin))) {
  50. perror("connect");
  51. close(fd);
  52. return ;
  53. }
  54.  
  55. /* Write the query. */
  56. /* XXX Can send succeed partially? */
  57. cp = query;
  58. remaining = strlen(query);
  59. while (remaining) {
  60. n_written = send(fd, cp, remaining, );
  61. if (n_written <= ) {
  62. perror("send");
  63. return ;
  64. }
  65. remaining -= n_written;
  66. cp += n_written;
  67. }
  68.  
  69. /* Get an answer back. */
  70. while () {
  71. ssize_t result = recv(fd, buf, sizeof(buf), );
  72. if (result == ) {
  73. break;
  74. } else if (result < ) {
  75. perror("recv");
  76. close(fd);
  77. return ;
  78. }
  79. fwrite(buf, , result, stdout);
  80. }
  81.  
  82. close(fd);
  83. return ;
  84. }

上面代码中所有的网络调用都是阻塞的,gethostbyname()在解析www.google.com成功或失败前不会返回,connect()在链路建立链接之前不会返回,recv()在接收到数据或是链接关闭请求之前不会返回,send()在数据发送到内核的写缓冲区之前不会返回.

阻塞IO也不是完全有害.如果你的程序在上述函数阻塞期间没啥想干的,用阻塞IO也没啥问题.但是想象一下,你现在需要写一个同时处理多个链接的程序,比如你要从2条链接里读数据,但是你并不知道哪条链接会先来数据.你可以写这样一个程序:

Bad Example

  1. /* This won't work. */
  2. char buf[];
  3. int i, n;
  4. while (i_still_want_to_read()) {
  5. for (i=; i<n_sockets; ++i) {
  6. n = recv(fd[i], buf, sizeof(buf), );
  7. if (n==)
  8. handle_close(fd[i]);
  9. else if (n<)
  10. handle_error(fd[i], errno);
  11. else
  12. handle_input(fd[i], buf, n);
  13. }
  14. }

为什么说这个程序很不好呢?因为如果fd[2]上数据先来了,程序在fd[0]和fd[1]上数据来了并处理完成之前,根本就不会去尝试从fd[2]读数据,因为这时候还阻塞在recv(fd[0], buf, sizeof(buf),0)这里呢。

有时候我们通过多线程或者多进程解决这个问题.最简单的一种处理方式就是每一个链接用一个进程(或线程)来处理.由于每条链接都有自己的进程,所以一条链接上的阻塞IO阻塞了并不会影响到别的链接处理进程.

下面是另一个例子。这是一个比较繁琐的服务器程序,在端口40713上等待tcp链接,从到来的数据中每次读一行,并将这一行的ROT13加密(其实就是简单的字符变换,比如把'a'变成'n',‘b’变成'o')数据输出.程序用了UNIX下的fork()来为每一条链接创建一个进程.

Example: Forking ROT13 serve

  1. /* For sockaddr_in */
  2. #include <netinet/in.h>
  3. /* For socket functions */
  4. #include <sys/socket.h>
  5.  
  6. #include <unistd.h>
  7. #include <string.h>
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10.  
  11. #define MAX_LINE 16384
  12.  
  13. char
  14. rot13_char(char c)
  15. {
  16. /* We don't want to use isalpha here; setting the locale would change
  17. * which characters are considered alphabetical. */
  18. if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
  19. return c + ;
  20. else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
  21. return c - ;
  22. else
  23. return c;
  24. }
  25.  
  26. void
  27. child(int fd)
  28. {
  29. char outbuf[MAX_LINE+];
  30. size_t outbuf_used = ;
  31. ssize_t result;
  32.  
  33. while () {
  34. char ch;
  35. result = recv(fd, &ch, , );
  36. if (result == ) {
  37. break;
  38. } else if (result == -) {
  39. perror("read");
  40. break;
  41. }
  42.  
  43. /* We do this test to keep the user from overflowing the buffer. */
  44. if (outbuf_used < sizeof(outbuf)) {
  45. outbuf[outbuf_used++] = rot13_char(ch);
  46. }
  47.  
  48. if (ch == '\n') {
  49. send(fd, outbuf, outbuf_used, );
  50. outbuf_used = ;
  51. continue;
  52. }
  53. }
  54. }
  55.  
  56. void
  57. run(void)
  58. {
  59. int listener;
  60. struct sockaddr_in sin;
  61.  
  62. sin.sin_family = AF_INET;
  63. sin.sin_addr.s_addr = ;
  64. sin.sin_port = htons();
  65.  
  66. listener = socket(AF_INET, SOCK_STREAM, );
  67.  
  68. #ifndef WIN32
  69. {
  70. int one = ;
  71. setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
  72. }
  73. #endif
  74.  
  75. if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < ) {
  76. perror("bind");
  77. return;
  78. }
  79.  
  80. if (listen(listener, )<) {
  81. perror("listen");
  82. return;
  83. }
  84.  
  85. while () {
  86. struct sockaddr_storage ss;
  87. socklen_t slen = sizeof(ss);
  88. int fd = accept(listener, (struct sockaddr*)&ss, &slen);
  89. if (fd < ) {
  90. perror("accept");
  91. } else {
  92. if (fork() == ) {
  93. child(fd);
  94. exit();
  95. }
  96. }
  97. }
  98. }
  99.  
  100. int
  101. main(int c, char **v)
  102. {
  103. run();
  104. return ;
  105. }

那么我们现在对多条链接的处理有了完美的解决方案吗?我是不是可以不写这本书而去干点别的了呢?不是这样的。首先,创建进程(线程)在某些平台上会是一笔不小的开销。在实际应用中,你可能更想使用一个线程池来代替它。但是从根本上讲,多线程并不能够达到你所期望的那种扩展性。如果你程序需要同时处理成千上万个连接,对于每个CPU仅能处理很少的线程的情况,处理成千上万个线程效率并不高。

如果线程不是处理多条链接的答案,那什么才是呢?在Unix范例中,你可以设置socket为noblocking(非阻塞)。Unix中完成这个设置的调用如下:

  1. fcntl(fd, F_SETFL, O_NONBLOCK);

fd是代表socket的文件描述符.一旦你设置fd(也就是socket)为非阻塞的,你在fd上进行的网络调用要么立刻完成,要么返回一个特定的错误码,告诉你“我现在没法处理,再试一遍吧”.基于此,我们的处理两条socket的程序可以这样写:
Bad Example: busy-polling all sockets

  1. /* This will work, but the performance will be unforgivably bad. */
  2. int i, n;
  3. char buf[1024];
  4. for (i=0; i < n_sockets; ++i)
  5. fcntl(fd[i], F_SETFL, O_NONBLOCK);
  6.  
  7. while (i_still_want_to_read()) {
  8. for (i=0; i < n_sockets; ++i) {
  9. n = recv(fd[i], buf, sizeof(buf), 0);
  10. if (n == 0) {
  11. handle_close(fd[i]);
  12. } else if (n < 0) {
  13. if (errno == EAGAIN)
  14. ; /* The kernel didn't have any data for us to read. */
  15. else
  16. handle_error(fd[i], errno);
  17. } else {
  18. handle_input(fd[i], buf, n);
  19. }
  20. }
  21. }

现在我们使用了非阻塞socket,上述代码可以工作...但也仅限于此了。上面代码性能很差,有两点原因:一.如果两个连接上都没有数据,那么就相当于在执行一个死循环,占据了所有的cpu。二.如果你通过这种方法处理多条以上的链接,那么对每一条链接来说,都需要执行内核调用(译者注:也就是上述代码中的recv),不管链接上有没有数据.所以说我们需要的是这样一种机制来告诉内核:“一直等着,直到某条链接上有数据了就告诉我是哪条链接上来数据了”。

下面是一个使用select的例子:
Example: Using select

  1. /* If you only have a couple dozen fds, this version won't be awful */
  2. fd_set readset;
  3. int i, n;
  4. char buf[1024];
  5.  
  6. while (i_still_want_to_read()) {
  7. int maxfd = -1;
  8. FD_ZERO(&readset);
  9.  
  10. /* Add all of the interesting fds to readset */
  11. for (i=0; i < n_sockets; ++i) {
  12. if (fd[i]>maxfd) maxfd = fd[i];
  13. FD_SET(fd[i], &readset);
  14. }
  15.  
  16. /* Wait until one or more fds are ready to read */
  17. select(maxfd+1, &readset, NULL, NULL, NULL);
  18.  
  19. /* Process all of the fds that are still set in readset */
  20. for (i=0; i < n_sockets; ++i) {
  21. if (FD_ISSET(fd[i], &readset)) {
  22. n = recv(fd[i], buf, sizeof(buf), 0);
  23. if (n == 0) {
  24. handle_close(fd[i]);
  25. } else if (n < 0) {
  26. if (errno == EAGAIN)
  27. ; /* The kernel didn't have any data for us to read. */
  28. else
  29. handle_error(fd[i], errno);
  30. } else {
  31. handle_input(fd[i], buf, n);
  32. }
  33. }
  34. }
  35. }
  1.  下面是一个使用select()重新实现ROT13 server的例子
    Example: select()-based ROT13 server
  1. /* For sockaddr_in */
  2. #include <netinet/in.h>
  3. /* For socket functions */
  4. #include <sys/socket.h>
  5. /* For fcntl */
  6. #include <fcntl.h>
  7. /* for select */
  8. #include <sys/select.h>
  9.  
  10. #include <assert.h>
  11. #include <unistd.h>
  12. #include <string.h>
  13. #include <stdlib.h>
  14. #include <stdio.h>
  15. #include <errno.h>
  16.  
  17. #define MAX_LINE 16384
  18.  
  19. char
  20. rot13_char(char c)
  21. {
  22. /* We don't want to use isalpha here; setting the locale would change
  23. * which characters are considered alphabetical. */
  24. if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
  25. return c + 13;
  26. else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
  27. return c - 13;
  28. else
  29. return c;
  30. }
  31.  
  32. struct fd_state {
  33. char buffer[MAX_LINE];
  34. size_t buffer_used;
  35.  
  36. int writing;
  37. size_t n_written;
  38. size_t write_upto;
  39. };
  40.  
  41. struct fd_state *
  42. alloc_fd_state(void)
  43. {
  44. struct fd_state *state = malloc(sizeof(struct fd_state));
  45. if (!state)
  46. return NULL;
  47. state->buffer_used = state->n_written = state->writing =
  48. state->write_upto = 0;
  49. return state;
  50. }
  51.  
  52. void
  53. free_fd_state(struct fd_state *state)
  54. {
  55. free(state);
  56. }
  57.  
  58. void
  59. make_nonblocking(int fd)
  60. {
  61. fcntl(fd, F_SETFL, O_NONBLOCK);
  62. }
  63.  
  64. int
  65. do_read(int fd, struct fd_state *state)
  66. {
  67. char buf[1024];
  68. int i;
  69. ssize_t result;
  70. while (1) {
  71. result = recv(fd, buf, sizeof(buf), 0);
  72. if (result <= 0)
  73. break;
  74.  
  75. for (i=0; i < result; ++i) {
  76. if (state->buffer_used < sizeof(state->buffer))
  77. state->buffer[state->buffer_used++] = rot13_char(buf[i]);
  78. if (buf[i] == '\n') {
  79. state->writing = 1;
  80. state->write_upto = state->buffer_used;
  81. }
  82. }
  83. }
  84.  
  85. if (result == 0) {
  86. return 1;
  87. } else if (result < 0) {
  88. if (errno == EAGAIN)
  89. return 0;
  90. return -1;
  91. }
  92.  
  93. return 0;
  94. }
  95.  
  96. int
  97. do_write(int fd, struct fd_state *state)
  98. {
  99. while (state->n_written < state->write_upto) {
  100. ssize_t result = send(fd, state->buffer + state->n_written,
  101. state->write_upto - state->n_written, 0);
  102. if (result < 0) {
  103. if (errno == EAGAIN)
  104. return 0;
  105. return -1;
  106. }
  107. assert(result != 0);
  108.  
  109. state->n_written += result;
  110. }
  111.  
  112. if (state->n_written == state->buffer_used)
  113. state->n_written = state->write_upto = state->buffer_used = 0;
  114.  
  115. state->writing = 0;
  116.  
  117. return 0;
  118. }
  119.  
  120. void
  121. run(void)
  122. {
  123. int listener;
  124. struct fd_state *state[FD_SETSIZE];
  125. struct sockaddr_in sin;
  126. int i, maxfd;
  127. fd_set readset, writeset, exset;
  128.  
  129. sin.sin_family = AF_INET;
  130. sin.sin_addr.s_addr = 0;
  131. sin.sin_port = htons(40713);
  132.  
  133. for (i = 0; i < FD_SETSIZE; ++i)
  134. state[i] = NULL;
  135.  
  136. listener = socket(AF_INET, SOCK_STREAM, 0);
  137. make_nonblocking(listener);
  138.  
  139. #ifndef WIN32
  140. {
  141. int one = 1;
  142. setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
  143. }
  144. #endif
  145.  
  146. if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
  147. perror("bind");
  148. return;
  149. }
  150.  
  151. if (listen(listener, 16)<0) {
  152. perror("listen");
  153. return;
  154. }
  155.  
  156. FD_ZERO(&readset);
  157. FD_ZERO(&writeset);
  158. FD_ZERO(&exset);
  159.  
  160. while (1) {
  161. maxfd = listener;
  162.  
  163. FD_ZERO(&readset);
  164. FD_ZERO(&writeset);
  165. FD_ZERO(&exset);
  166.  
  167. FD_SET(listener, &readset);
  168.  
  169. for (i=0; i < FD_SETSIZE; ++i) {
  170. if (state[i]) {
  171. if (i > maxfd)
  172. maxfd = i;
  173. FD_SET(i, &readset);
  174. if (state[i]->writing) {
  175. FD_SET(i, &writeset);
  176. }
  177. }
  178. }
  179.  
  180. if (select(maxfd+1, &readset, &writeset, &exset, NULL) < 0) {
  181. perror("select");
  182. return;
  183. }
  184.  
  185. if (FD_ISSET(listener, &readset)) {
  186. struct sockaddr_storage ss;
  187. socklen_t slen = sizeof(ss);
  188. int fd = accept(listener, (struct sockaddr*)&ss, &slen);
  189. if (fd < 0) {
  190. perror("accept");
  191. } else if (fd > FD_SETSIZE) {
  192. close(fd);
  193. } else {
  194. make_nonblocking(fd);
  195. state[fd] = alloc_fd_state();
  196. assert(state[fd]);/*XXX*/
  197. }
  198. }
  199.  
  200. for (i=0; i < maxfd+1; ++i) {
  201. int r = 0;
  202. if (i == listener)
  203. continue;
  204.  
  205. if (FD_ISSET(i, &readset)) {
  206. r = do_read(i, state[i]);
  207. }
  208. if (r == 0 && FD_ISSET(i, &writeset)) {
  209. r = do_write(i, state[i]);
  210. }
  211. if (r) {
  212. free_fd_state(state[i]);
  213. state[i] = NULL;
  214. close(i);
  215. }
  216. }
  217. }
  218. }
  219.  
  220. int
  221. main(int c, char **v)
  222. {
  223. setvbuf(stdout, NULL, _IONBF, 0);
  224.  
  225. run();
  226. return 0;
  227. }

用select()就解决了我们之前提到的问题吗?不,还没完呢.当socket的数量很大时,select()调用的性能会很差.因为生成与读取select()的位数组的时间正比于你向select()提供的最大的fd值。

不同的操作系统提供了不同的select()的替代函数。包括poll(),epoll(),kqueue(),evports以及/dev/poll.上述所有函数都比select()的性能要好,并且除了poll(),对增加一条socket,移除一条socket,通知一条socket已经做好IO准备而言,这些函数的时间复杂度都是O(1)。

然而不幸地是,这些更为有效的接口并没有一个统一的标准.Linux有epoll(),BSDs(包括Darwin)有kqueue(),Solaris有evports和/dev/poll...而且这些操作系统没一个自身之外的系统的上述接口. 所以你想写一个可移植(跨平台)的高性能异步应用程序的话,你就需要一个包含上述所有接口的抽象。

这正是Libevent API可以为你提供的最基本的功能.Libevent根据你的系统,选择最高效的select()的替代函数,并提供统一的接口.

下面是异步ROT13 server的另一个版本.这一次我们使用Libevent 2替代select().请注意现在fd_sets没啦,取而代之的是,我们使用结构event_base与events进行关联以及解除关联.event_base根据select(),poll(),epoll(),kqueue()等实现.

Example: A low-level ROT13 server with Libevent

  1. /* For sockaddr_in */
  2. #include <netinet/in.h>
  3. /* For socket functions */
  4. #include <sys/socket.h>
  5. /* For fcntl */
  6. #include <fcntl.h>
  7.  
  8. #include <event2/event.h>
  9.  
  10. #include <assert.h>
  11. #include <unistd.h>
  12. #include <string.h>
  13. #include <stdlib.h>
  14. #include <stdio.h>
  15. #include <errno.h>
  16.  
  17. #define MAX_LINE 16384
  18.  
  19. void do_read(evutil_socket_t fd, short events, void *arg);
  20. void do_write(evutil_socket_t fd, short events, void *arg);
  21.  
  22. char
  23. rot13_char(char c)
  24. {
  25. /* We don't want to use isalpha here; setting the locale would change
  26. * which characters are considered alphabetical. */
  27. if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
  28. return c + 13;
  29. else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
  30. return c - 13;
  31. else
  32. return c;
  33. }
  34.  
  35. struct fd_state {
  36. char buffer[MAX_LINE];
  37. size_t buffer_used;
  38.  
  39. size_t n_written;
  40. size_t write_upto;
  41.  
  42. struct event *read_event;
  43. struct event *write_event;
  44. };
  45.  
  46. struct fd_state *
  47. alloc_fd_state(struct event_base *base, evutil_socket_t fd)
  48. {
  49. struct fd_state *state = malloc(sizeof(struct fd_state));
  50. if (!state)
  51. return NULL;
  52. state->read_event = event_new(base, fd, EV_READ|EV_PERSIST, do_read, state);
  53. if (!state->read_event) {
  54. free(state);
  55. return NULL;
  56. }
  57. state->write_event =
  58. event_new(base, fd, EV_WRITE|EV_PERSIST, do_write, state);
  59.  
  60. if (!state->write_event) {
  61. event_free(state->read_event);
  62. free(state);
  63. return NULL;
  64. }
  65.  
  66. state->buffer_used = state->n_written = state->write_upto = 0;
  67.  
  68. assert(state->write_event);
  69. return state;
  70. }
  71.  
  72. void
  73. free_fd_state(struct fd_state *state)
  74. {
  75. event_free(state->read_event);
  76. event_free(state->write_event);
  77. free(state);
  78. }
  79.  
  80. void
  81. do_read(evutil_socket_t fd, short events, void *arg)
  82. {
  83. struct fd_state *state = arg;
  84. char buf[1024];
  85. int i;
  86. ssize_t result;
  87. while (1) {
  88. assert(state->write_event);
  89. result = recv(fd, buf, sizeof(buf), 0);
  90. if (result <= 0)
  91. break;
  92.  
  93. for (i=0; i < result; ++i) {
  94. if (state->buffer_used < sizeof(state->buffer))
  95. state->buffer[state->buffer_used++] = rot13_char(buf[i]);
  96. if (buf[i] == '\n') {
  97. assert(state->write_event);
  98. event_add(state->write_event, NULL);
  99. state->write_upto = state->buffer_used;
  100. }
  101. }
  102. }
  103.  
  104. if (result == 0) {
  105. free_fd_state(state);
  106. } else if (result < 0) {
  107. if (errno == EAGAIN) // XXXX use evutil macro
  108. return;
  109. perror("recv");
  110. free_fd_state(state);
  111. }
  112. }
  113.  
  114. void
  115. do_write(evutil_socket_t fd, short events, void *arg)
  116. {
  117. struct fd_state *state = arg;
  118.  
  119. while (state->n_written < state->write_upto) {
  120. ssize_t result = send(fd, state->buffer + state->n_written,
  121. state->write_upto - state->n_written, 0);
  122. if (result < 0) {
  123. if (errno == EAGAIN) // XXX use evutil macro
  124. return;
  125. free_fd_state(state);
  126. return;
  127. }
  128. assert(result != 0);
  129.  
  130. state->n_written += result;
  131. }
  132.  
  133. if (state->n_written == state->buffer_used)
  134. state->n_written = state->write_upto = state->buffer_used = 1;
  135.  
  136. event_del(state->write_event);
  137. }
  138.  
  139. void
  140. do_accept(evutil_socket_t listener, short event, void *arg)
  141. {
  142. struct event_base *base = arg;
  143. struct sockaddr_storage ss;
  144. socklen_t slen = sizeof(ss);
  145. int fd = accept(listener, (struct sockaddr*)&ss, &slen);
  146. if (fd < 0) { // XXXX eagain??
  147. perror("accept");
  148. } else if (fd > FD_SETSIZE) {
  149. close(fd); // XXX replace all closes with EVUTIL_CLOSESOCKET */
  150. } else {
  151. struct fd_state *state;
  152. evutil_make_socket_nonblocking(fd);
  153. state = alloc_fd_state(base, fd);
  154. assert(state); /*XXX err*/
  155. assert(state->write_event);
  156. event_add(state->read_event, NULL);
  157. }
  158. }
  159.  
  160. void
  161. run(void)
  162. {
  163. evutil_socket_t listener;
  164. struct sockaddr_in sin;
  165. struct event_base *base;
  166. struct event *listener_event;
  167.  
  168. base = event_base_new();
  169. if (!base)
  170. return; /*XXXerr*/
  171.  
  172. sin.sin_family = AF_INET;
  173. sin.sin_addr.s_addr = 0;
  174. sin.sin_port = htons(40713);
  175.  
  176. listener = socket(AF_INET, SOCK_STREAM, 0);
  177. evutil_make_socket_nonblocking(listener);
  178.  
  179. #ifndef WIN32
  180. {
  181. int one = 1;
  182. setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
  183. }
  184. #endif
  185.  
  186. if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
  187. perror("bind");
  188. return;
  189. }
  190.  
  191. if (listen(listener, 16)<0) {
  192. perror("listen");
  193. return;
  194. }
  195.  
  196. listener_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
  197. /*XXX check it */
  198. event_add(listener_event, NULL);
  199.  
  200. event_base_dispatch(base);
  201. }
  202.  
  203. int
  204. main(int c, char **v)
  205. {
  206. setvbuf(stdout, NULL, _IONBF, 0);
  207.  
  208. run();
  209. return 0;
  210. }

(代码中需要注意的:我们没把socket定义为"int"型,我们用了evutil_socket_t.我们没有使用fcntl(O_NONBLOCK)去设置socket为非阻塞的,我们使用了evutil_make_socket_nonblocking.上述这些改变使得我们的代码兼容Win32的网络API)

What about convenience? (and what about Windows?)

你可能注意到了,在我们的代码变得更高效的同时,也变得更复杂了.回顾一下我们使用fork的版本,我们不必为每一条链接都管理一个缓冲区:我们为每一个进程都有一个独立的栈上分配的缓冲区.

我们不必精确地知道哪一个socket在读或者写:这是隐含在代码中的.(that was implicit in our location in the code).我们也不需要一个结构来跟踪每一个操作都完成了多少:我们使用循环和栈变量就好了.

此外,如果你对windows网络编程非常有经验的话,你会意识到,我们在上述例子中对libevent的用法并不会有最好的性能.在windows上,最快的异步IO方式使用的不是select()-like的接口:它用的是IOCP(IO Completion Ports:IO完成端口) API.不像别的高效的网络API,IOCP并不在一个socket已经对你的程序需要做的某些操作做好准备的时候就通知你的程序.取而代之的是,你的程序告诉windows网络栈开始网络操作,当操作完成的时候IOCP再通知程序.

幸运的是,Libevent2的"bufferevents"接口解决了上述这些问题:这使得我们的程序更易于编写,并且可以高效地在Windows和Unix下运行.

下面是我们使用bufferevents API的最新的ROT13 server。

Example: A simpler ROT13 server with Libevent

  1. /* For sockaddr_in */
  2. #include <netinet/in.h>
  3. /* For socket functions */
  4. #include <sys/socket.h>
  5. /* For fcntl */
  6. #include <fcntl.h>
  7.  
  8. #include <event2/event.h>
  9. #include <event2/buffer.h>
  10. #include <event2/bufferevent.h>
  11.  
  12. #include <assert.h>
  13. #include <unistd.h>
  14. #include <string.h>
  15. #include <stdlib.h>
  16. #include <stdio.h>
  17. #include <errno.h>
  18.  
  19. #define MAX_LINE 16384
  20.  
  21. void do_read(evutil_socket_t fd, short events, void *arg);
  22. void do_write(evutil_socket_t fd, short events, void *arg);
  23.  
  24. char
  25. rot13_char(char c)
  26. {
  27. /* We don't want to use isalpha here; setting the locale would change
  28. * which characters are considered alphabetical. */
  29. if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
  30. return c + 13;
  31. else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
  32. return c - 13;
  33. else
  34. return c;
  35. }
  36.  
  37. void
  38. readcb(struct bufferevent *bev, void *ctx)
  39. {
  40. struct evbuffer *input, *output;
  41. char *line;
  42. size_t n;
  43. int i;
  44. input = bufferevent_get_input(bev);
  45. output = bufferevent_get_output(bev);
  46.  
  47. while ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF))) {
  48. for (i = 0; i < n; ++i)
  49. line[i] = rot13_char(line[i]);
  50. evbuffer_add(output, line, n);
  51. evbuffer_add(output, "\n", 1);
  52. free(line);
  53. }
  54.  
  55. if (evbuffer_get_length(input) >= MAX_LINE) {
  56. /* Too long; just process what there is and go on so that the buffer
  57. * doesn't grow infinitely long. */
  58. char buf[1024];
  59. while (evbuffer_get_length(input)) {
  60. int n = evbuffer_remove(input, buf, sizeof(buf));
  61. for (i = 0; i < n; ++i)
  62. buf[i] = rot13_char(buf[i]);
  63. evbuffer_add(output, buf, n);
  64. }
  65. evbuffer_add(output, "\n", 1);
  66. }
  67. }
  68.  
  69. void
  70. errorcb(struct bufferevent *bev, short error, void *ctx)
  71. {
  72. if (error & BEV_EVENT_EOF) {
  73. /* connection has been closed, do any clean up here */
  74. /* ... */
  75. } else if (error & BEV_EVENT_ERROR) {
  76. /* check errno to see what error occurred */
  77. /* ... */
  78. } else if (error & BEV_EVENT_TIMEOUT) {
  79. /* must be a timeout event handle, handle it */
  80. /* ... */
  81. }
  82. bufferevent_free(bev);
  83. }
  84.  
  85. void
  86. do_accept(evutil_socket_t listener, short event, void *arg)
  87. {
  88. struct event_base *base = arg;
  89. struct sockaddr_storage ss;
  90. socklen_t slen = sizeof(ss);
  91. int fd = accept(listener, (struct sockaddr*)&ss, &slen);
  92. if (fd < 0) {
  93. perror("accept");
  94. } else if (fd > FD_SETSIZE) {
  95. close(fd);
  96. } else {
  97. struct bufferevent *bev;
  98. evutil_make_socket_nonblocking(fd);
  99. bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
  100. bufferevent_setcb(bev, readcb, NULL, errorcb, NULL);
  101. bufferevent_setwatermark(bev, EV_READ, 0, MAX_LINE);
  102. bufferevent_enable(bev, EV_READ|EV_WRITE);
  103. }
  104. }
  105.  
  106. void
  107. run(void)
  108. {
  109. evutil_socket_t listener;
  110. struct sockaddr_in sin;
  111. struct event_base *base;
  112. struct event *listener_event;
  113.  
  114. base = event_base_new();
  115. if (!base)
  116. return; /*XXXerr*/
  117.  
  118. sin.sin_family = AF_INET;
  119. sin.sin_addr.s_addr = 0;
  120. sin.sin_port = htons(40713);
  121.  
  122. listener = socket(AF_INET, SOCK_STREAM, 0);
  123. evutil_make_socket_nonblocking(listener);
  124.  
  125. #ifndef WIN32
  126. {
  127. int one = 1;
  128. setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
  129. }
  130. #endif
  131.  
  132. if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
  133. perror("bind");
  134. return;
  135. }
  136.  
  137. if (listen(listener, 16)<0) {
  138. perror("listen");
  139. return;
  140. }
  141.  
  142. listener_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
  143. /*XXX check it */
  144. event_add(listener_event, NULL);
  145.  
  146. event_base_dispatch(base);
  147. }
  148.  
  149. int
  150. main(int c, char **v)
  151. {
  152. setvbuf(stdout, NULL, _IONBF, 0);
  153.  
  154. run();
  155. return 0;
  156. }

异步IO简介的更多相关文章

  1. Libevent:0异步IO简介

    一:异步IO简介 大多数的初级编程者都是从阻塞IO调用开始网络编程的.阻塞(同步)IO调用指的是:调用会一直阻塞,不会返回,直到发生下面两种情况之一.要么操作完成,要么经历相当长的时间,网络协议栈自己 ...

  2. Python 的异步 IO:Asyncio 简介

    转载自https://segmentfault.com/a/1190000008814676 好文章 所谓「异步 IO」,就是你发起一个 IO 操作,却不用等它结束,你可以继续做其他事情,当它结束时, ...

  3. Python高级编程和异步IO并发编程

    第1章 课程简介介绍如何配置系统的开发环境以及如何加入github私人仓库获取最新源码. 1-1 导学 试看 1-2 开发环境配置 1-3 资源获取方式第2章 python中一切皆对象本章节首先对比静 ...

  4. [.NET] 利用 async & await 进行异步 IO 操作

    利用 async & await 进行异步 IO 操作 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6082673.html  序 上次,博主 ...

  5. Day11-协程/异步IO/RabbitMQ

    协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候 ...

  6. Python之路第一课Day10--随堂笔记(异步IO\数据库\队列\缓存)

    本节内容 Gevent协程 Select\Poll\Epoll异步IO与事件驱动 Python连接Mysql数据库操作 RabbitMQ队列 Redis\Memcached缓存 Paramiko SS ...

  7. 异步IO比同步阻塞IO性能更好吗?为什么?

    最近在看node.js, 介绍中提到node是异步io的方式实现, 性能比同步阻塞io的更好. 对于一个request而言, 如果我们依赖io的结果, 异步io和同步阻塞io都是要等到io完成才能继续 ...

  8. Python-09-线程、进程、协程、异步IO

    0. 什么是线程(thread)? 线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元.一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆 ...

  9. SQLite剖析之异步IO模式、共享缓存模式和解锁通知

    1.异步I/O模式    通常,当SQLite写一个数据库文件时,会等待,直到写操作完成,然后控制返回到调用程序.相比于CPU操作,写文件系统是非常耗时的,这是一个性能瓶颈.异步I/O后端是SQLit ...

随机推荐

  1. (转载)函数:mysqli_query和mysql_query有何区别?

    (转载)http://wzan315.blog.163.com/blog/static/37192636201241732045299/ Mysqli.dll是一个允许以对象的方式或者过程操作数据库的 ...

  2. Atmospheric Scattering in Unity5

    本次实践效果仅有Atmospheric Scattering和AA,并无其他post-processing,看到类似depth of field等的效果全部是Atmospheric Scatterin ...

  3. 任意轴算法 ( Arbitrary Axis Algorithm )

    已知三维空间中任意单位向量,求以该向量为Z轴的local正交坐标系: 如上图,每个模型都有自己local 坐标系,已知其中一个朝向求另外两个方向. 在autodesk中采用的是Arbitrary Ax ...

  4. HDOJ/HDU 2539 点球大战(String.endsWith()方法的一个应用~)

    Problem Description 在足球比赛中,有不少赛事,例如世界杯淘汰赛和欧洲冠军联赛淘汰赛中,当比赛双方经过正规比赛和加时赛之后仍然不分胜负时,需要进行点球大战来决定谁能够获得最终的胜利. ...

  5. HDU 4720 Naive and Silly Muggles 2013年四川省赛题

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4720 题目大意:给你四个点,用前三个点绘制一个最小的圆,而这三个点必须在圆上或者在圆内,判断最一个点如 ...

  6. Android学习笔记(一)Android应用程序的组成部分

    Android应用程序由松散耦合的组件组成,并使用应用程序Manifest绑定到一起:应用程序Manifest描述了每一组件和它们之间的交互方式,还用于指定应用程序元数据.其硬件和平台要求.外部库以及 ...

  7. U3D物理碰撞总结

    OnCollisionEnter的触发条件: 1.都有boxcollider组件并且IsTrigger为false 2.主动碰撞的物体要有非运动学刚体组件,被动碰撞的物体有木有都行 3.如果主动碰撞的 ...

  8. centos平台openstack spice配置

    配置过程只涉及控制节点(192.168.209.11)和计算节点(192.168.209.31),根据情况修改为实际环境的IP地址.     修改控制节点 安装软件包 yum install spic ...

  9. 爬虫技术实战 | WooYun知识库

    爬虫技术实战 | WooYun知识库 爬虫技术实战 大数据分析与机器学习领域Python兵器谱-大数据邦-微头条(wtoutiao.com) 大数据分析与机器学习领域Python兵器谱

  10. MediaInfo源代码分析 3:Open()函数

    我们来看一下MediaInfo中的Open()函数的内部调用过程 首先open函数封装了MediaInfo_Internal类中的open()函数 //打开文件 size_t MediaInfo::O ...