jinfeng_wang

          G-G-S,D-D-U!

          BlogJava 首頁 新隨筆 聯(lián)系 聚合 管理
            400 Posts :: 0 Stories :: 296 Comments :: 0 Trackbacks
          http://blog.gslin.org/archives/2005/11/24/220/


          在課堂上學(xué)過 Unix Network Programming 後,我們知道在處理多 User 時會有幾種方法解決:
          1. 一個新的 Connection 進來,用 fork() 產(chǎn)生一個 Process 處理。
          2. 一個新的 Connection 進來,用 pthread_create() 產(chǎn)生一個 Thread 處理。
          3. 一個新的 Connection 進來,丟入 Event-based Array,由 Main Process 以 Nonblocking 的方式處理所有的 I/O。
          這三種方法當(dāng)然也都有各自的缺點:
          1. fork() 的問題在於每一個 Connection 進來時的成本太高。
          2. 用 Multi-thread 的問題在於 Thread-safe 與 Deadlock 問題難以解決,另外有 Memory-leak 的問題要處理。
          3. 用 Event-based 的方式在於實做上不好寫,尤其是要注意到事件產(chǎn)生時必須 Nonblocking,於是會需要實做 Buffering 的問題,而 Multi-thread 所會遇到的 Memory-leak 問題在這邊會更嚴(yán)重。而在多 CPU 的系統(tǒng)上沒有辦法使用到所有的 CPU resource。
          當(dāng)然,針對前面兩項有各自的解法:
          1. 以 Poll 的方式解決:當(dāng)一個 Process 處理完一個 Connection 後,不直接死掉,而繼續(xù)回到 accept() 的狀態(tài)繼續(xù)處理,但這樣會遇到 Memory-leak 的問題,於是採用這種方式的人通常會再加上「處理過 N 個 Connection 後死掉,由 Parent Process 再 fork() 一隻新的」。最有名的例子是 Apache 1.3。
          2. Thread-safe 的問題可以透過自己撰寫,或是尋找其他 Thread-safe Library 直接使用。Memory-leak 的問題可以試著透過 Garbage Collection Library 分析出來。Apache 2.0 的 Thread MPM 就是使用這個模式。
          然而,目前高效率的 Server 都偏好採用 Event-based,一方面是沒有 Create Process/Thread 所造成的 Overhead,另外一方面是不需要透過 Shared Memory 或是 Mutex 在不同的 Process/Thread 之間交換資料。

          然而,Event-based 在實做上的幾個複雜的地方在於:
          1. select()poll() 的效率過慢,造成每次要判斷「有哪些 Event 發(fā)生」這件事情的成本很高,這在 BSD 支援 kqueue()、Linux 支援 epoll()、Solaris 支援 /dev/poll 後就解決了,但這兩組 Function 都不是 Standard,於是在不同的平臺上就必須再改一次。
          2. 因為 Nonblocking,所以在 write() 或是 send() 時滿了需要自己 Buffering。
          3. 因為 Nonblocking,所以不能使用 fgets() 或是其他類似的 function,於是需要自己刻一個 Nonblocking 的 fgets()。但是使用者所丟過來的資料又不能保證在一次 read()recv() 就有一行,於是要自己做 Buffering。
          實際上這三件事情在 libevent 都有 Library 處理掉了。

          另外值得注意的一點在於 libevent 使用的是 3-clause BSD license 而非 GPL,所以在不想公開程式碼 (像是商業(yè)用途) 的情況下會比其他的 Library 適合。



          接下來要談的是 libevent 要如何使用,不過為了方便起見,我們直接寫一個很簡單的 Time Server 來當(dāng)作例子:當(dāng)你連上去以後 Server 端直接提供時間,然後結(jié)束連線。

          在這些例子裡面我以 FreeBSD 6.0 當(dāng)作測試的平臺,另外使用 libevent 1.1a 當(dāng)作 Event-based Library,Compile 時請使用 gcc -I/usr/local/include -o timeserver timeserver.c -L/usr/local/lib -levent (如果 libevent 的 Header 與 Library 放在 /usr/include/usr/lib 下的話可以省略這兩個參數(shù))。

          原始程式碼在文章的最後頭。

          event_init() 表示初始化 libevent 所使用到的變數(shù)。

          event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev)s 這個 File Description 放入 ev (第一個參數(shù)與第二個參數(shù)),並且告知當(dāng)事件 (第三個參數(shù)的 EV_READ) 發(fā)生時要呼叫 connection_accept() (第四個參數(shù)),呼叫時要把 ev 當(dāng)作參數(shù)丟進去 (第五個參數(shù))。

          其中的 EV_PERSIST 表示當(dāng)呼叫進去的時候不要把這個 event 拿掉 (繼續(xù)保留在 Event Queue 裡面),這點可以跟 connection_accept() 內(nèi)在註冊 connection_time() 的程式碼做比較。

          event_add(&ev, NULL) 就是把 ev 註冊到 event queue 裡面,第二個參數(shù)指定的是 Timeout 時間,設(shè)定成 NULL 表示忽略這項設(shè)定。

          最後的 event_dispatch() 表示進入 event loop,當(dāng) Queue 裡面的任何一個 File Description 發(fā)生事件的時候就會進入 callback function 執(zhí)行。

          這隻程式非常粗糙,有很多地方?jīng)]有注意到 Blocking 的問題,這點我們就先不管了。當(dāng)跑起來以後你就可以連到 port 7000,就會出現(xiàn)類似下面的結(jié)果:gslin@netnews [~] [3:14/W5] t 0 7000

          gslin@netnews [~/work/C] [3:15/W3] t 0 7000
          Trying 0.0.0.0...
          Connected to 0.
          Escape character is '^]'.
          Fri Nov 25 03:15:10 2005
          Connection closed by foreign host.

          最基本的使用就是這樣了,你可以 man event 看到完整的說明。

          這是 timeserver.c

          #include <netinet/in.h>
          #include <sys/socket.h>
          #include <sys/types.h>
          #include <event.h>
          #include <stdio.h>
          #include <time.h>

          void connection_time(int fd, short event, struct event *arg)
          {
          char buf[32];
          struct tm t;
          time_t now;

          time(&now);
          localtime_r(&now, &t);
          asctime_r(&t, buf);

          write(fd, buf, strlen(buf));
          shutdown(fd, SHUT_RDWR);

          free(arg);
          }

          void connection_accept(int fd, short event, void *arg)
          {
          /* for debugging */
          fprintf(stderr, "%s(): fd = %d, event = %d.\n", __func__, fd, event);

          /* Accept a new connection. */
          struct sockaddr_in s_in;
          socklen_t len = sizeof(s_in);
          int ns = accept(fd, (struct sockaddr *) &s_in, &len);
          if (ns < 0) {
          perror("accept");
          return;
          }

          /* Install time server. */
          struct event *ev = malloc(sizeof(struct event));
          event_set(ev, ns, EV_WRITE, (void *) connection_time, ev);
          event_add(ev, NULL);
          }

          int main(void)
          {
          /* Request socket. */
          int s = socket(PF_INET, SOCK_STREAM, 0);
          if (s < 0) {
          perror("socket");
          exit(1);
          }

          /* bind() */
          struct sockaddr_in s_in;
          bzero(&s_in, sizeof(s_in));
          s_in.sin_family = AF_INET;
          s_in.sin_port = htons(7000);
          s_in.sin_addr.s_addr = INADDR_ANY;
          if (bind(s, (struct sockaddr *) &s_in, sizeof(s_in)) < 0) {
          perror("bind");
          exit(1);
          }

          /* listen() */
          if (listen(s, 5) < 0) {
          perror("listen");
          exit(1);
          }

          /* Initial libevent. */
          event_init();

          /* Create event. */
          struct event ev;
          event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev);

          /* Add event. */
          event_add(&ev, NULL);

          event_dispatch();

          return 0;
          }

           

          這次要談的跟 Network Programming 沒有直接的關(guān)係。

          在寫 Nonblocking Network Program 通常要處理 Buffering 的問題,但並不好寫,主要是因為 read()recv() 不保證可以一次讀到一行的份量進來。

          libevent 裡面提供相當(dāng)不錯的 Buffer Library 可以用,完整的說明在 man event 的時候可以看到,最常用的應(yīng)該就是以 evbuffer_add()evbuffer_readline() 這兩個 Function,其他的知道存在就可以了,需要的時候再去看詳細(xì)的用法。

          下面直接提供 libevent-buff.c 當(dāng)作範(fàn)例,編譯後看執(zhí)行結(jié)果,再回頭來看 source code 應(yīng)該就有感覺了:

          #include <sys/time.h>
          #include <event.h>
          #include <stdio.h>

          void printbuf(struct evbuffer *evbuf)
          {
          for (;;) {
          char *buf = evbuffer_readline(evbuf);
          printf("* buf = %p, the string = \"\e[1;33m%s\e[m\"\n", buf, buf);
          if (buf == NULL)
          break;
          free(buf);
          }
          }

          int main(void)
          {
          struct evbuffer *evbuf;

          evbuf = evbuffer_new();
          if (evbuf == NULL) {
          fprintf(stderr, "%s(): evbuffer_new() failed.\n", __func__);
          exit(1);
          }

          /* Add "gslin" into buffer. */
          u_char *buf1 = "gslin";
          printf("* Add \"\e[1;33m%s\e[m\".\n", buf1);
          evbuffer_add(evbuf, buf1, strlen(buf1));
          printbuf(evbuf);

          u_char *buf2 = " is reading.\nAnd he is at home.\nLast.";
          printf("* Add \"\e[1;33m%s\e[m\".\n", buf2);
          evbuffer_add(evbuf, buf2, strlen(buf2));
          printbuf(evbuf);

          evbuffer_free(evbuf);
          }

           

          posted on 2010-04-16 10:29 jinfeng_wang 閱讀(1120) 評論(0)  編輯  收藏 所屬分類: ZZLinuxwebsite
          主站蜘蛛池模板: 洛南县| 大邑县| 娄底市| 临江市| 岚皋县| 察雅县| 玛曲县| 横山县| 普安县| 全南县| 盐城市| 清徐县| 香格里拉县| 南川市| 双辽市| 喜德县| 奉新县| 龙里县| 辽宁省| 乡宁县| 马山县| 张家口市| 成都市| 沛县| 临湘市| 平凉市| 和田县| 同江市| 鹤壁市| 商城县| 洛隆县| 南部县| 八宿县| 田东县| 田阳县| 开化县| 新昌县| 沙田区| 宜春市| 白山市| 吴旗县|