免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3140 | 回复: 0
打印 上一主题 下一主题

【zz】Network Programming Using Libevent [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-11-27 13:21 |只看该作者 |倒序浏览
from
http://blog.gslin.net
在課堂上學過 Unix Network Programming 後,我們知道在處理多 User 時會有幾種方法
解決:
   1. 一個新的 Connection 進來,用 fork() 產生一個 Process 處理。
   2. 一個新的 Connection 進來,用 pthread_create() 產生一個 Thread 處理。
   3. 一個新的 Connection 進來,丟入 Event-based Array,由 Main Process 以
Nonblocking 的方式處理所有的 I/O。
這三種方法當然也都有各自的缺點:
   1. 用 fork() 的問題在於每一個 Connection 進來時的成本太高。
   2. 用 Multi-thread 的問題在於 Thread-safe 與 Deadlock 問題難以解決,另外有
Memory-leak 的問題要處理。
   3. 用 Event-based 的方式在於實做上不好寫,尤其是要注意到事件產生時必須
Nonblocking,於是會需要實做 Buffering 的問題,而 Multi-thread 所會遇到的
Memory-leak 問題在這邊會更嚴重。而在多 CPU 的系統上沒有辦法使用到所有的 CPU
resource。
當然,針對前面兩項有各自的解法:
   1. 以 Poll 的方式解決:當一個 Process 處理完一個 Connection 後,不直接死掉
,而繼續回到 accept() 的狀態繼續處理,但這樣會遇到 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 發生」這件事
情的成本很高,這在 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,所以在
不想公開程式碼 (像是商業用途) 的情況下會比其他的 Library 適合。
接下來要談的是 libevent 要如何使用,不過為了方便起見,我們直接寫一個很簡單的
Time Server 來當作例子:當你連上去以後 Server 端直接提供時間,然後結束連線。
在這些例子裡面我以 FreeBSD 6.0 當作測試的平台,另外使用 libevent 1.1a 當作
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 下的話可以省略這兩個參數)。
原始程式碼在文章的最後頭。
event_init() 表示初始化 libevent 所使用到的變數。
event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev) 把 s 這個
File Description 放入 ev (第一個參數與第二個參數),並且告知當事件 (第三個參數
的 EV_READ) 發生時要呼叫 connection_accept() (第四個參數),呼叫時要把 ev 當作
參數丟進去 (第五個參數)。
其中的 EV_PERSIST 表示當呼叫進去的時候不要把這個 event 拿掉 (繼續保留在 Event
Queue 裡面),這點可以跟 connection_accept() 內在註冊 connection_time() 的程式
碼做比較。
而 event_add(&ev, NULL) 就是把 ev 註冊到 event queue 裡面,第二個參數指定的是
Timeout 時間,設定成 NULL 表示忽略這項設定。
最後的 event_dispatch() 表示進入 event loop,當 Queue 裡面的任何一個 File
Description 發生事件的時候就會進入 callback function 執行。
這隻程式非常粗糙,有很多地方沒有注意到 Blocking 的問題,這點我們就先不管了。當
跑起來以後你就可以連到 port 7000,就會出現類似下面的結果: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
#include
#include
#include
#include
#include
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
#include
#include
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);
}
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/24250/showart_206677.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP