[转]Network Programming Using Libevent

news/2024/7/6 1:15:53


Network Programming Using Libevent - (I)

 

在課堂上學過 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 適合。 

Network Programming Using Libevent - (II)

 

接下來要談的是 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 < 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 Using Libevent - (III)

 

這次要談的跟 Network Programming 沒有直接的關係。

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

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

下面直接提供 libevent-buff.c 當作範例,編譯後看執行結果,再回頭來看 source code 應該就有感覺了:

#include
#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);
}

 

 

内容来源:http://www.cublog.cn/u/5251/showart_270584.html

顺便建议参考:http://unx.ca/log/category/libevent/





http://www.niftyadmin.cn/n/3652608.html

相关文章

LVM和磁盘配额的易于增加和减少容量

文章目录LVM概述LVM机制的基本概念LVM 的管理命令LVM操作主要命令步骤磁盘配额实现磁盘限额的条件Linux 磁盘限额的特点CentOS7中设置磁盘配额步骤LVM概述 LVM——逻辑卷管理 能够在保持现有数据不变的情况下&#xff0c;动态调整磁盘容量 /boot分区用于存放引导文件&#xff…

一些使用Vim的小技巧

太简单的就不说了&#xff0c;随便找手册可以找到&#xff0c;这里就说说一些小技巧吧&#xff0c;也是最近使用给逼出来的学习&#xff0c;呵呵&#xff0c;不过挺方便的。1. 全局替换(1) v G $ 选定全部&#xff0c;然后输入 :s/原始字符串/目标字符串/(2) :%s/原始字符串/…

磁盘阵列的介绍和命令用法

文章目录RAID磁盘阵列介绍RAID 0磁盘阵列&#xff08;条带化存储&#xff09;RAID 1磁盘阵列&#xff08;镜像存储&#xff09;RAID 5磁盘阵列RAID 6磁盘阵列RAID 10磁盘阵列&#xff08;先做镜像&#xff0c;再做条带&#xff09;RAID 01 &#xff08;先做条带&#xff0c;再做…

两个简单的画验证码图形程序

生成验证码比较简单&#xff0c;画图也不难&#xff0c;不过大家都不喜欢读手册&#xff0c;也不喜欢自己动手&#xff0c;一般是网上抄一段代码了事&#xff0c;我一直如此&#xff0c;偶尔画图&#xff0c;其实发现画图挺有趣。不过拿普通字体生成的验证码&#xff0c;是没有…

Linux中的引导过程与服务控制

文章目录Linux操作系统引导过程系统初始化进程Systemd 单元类型运行级别所对应的 Systemd 目标修复MBR扇区故障排除启动类故障修复GRUB引导故障修复GRUB 引导故障方法一&#xff1a;手动输入引导命令&#xff08;笨拙繁琐&#xff0c;不建议使用&#xff09;方法二&#xff1a;…

MySQL中MyISAM引擎与InnoDB引擎性能简单测试

MySQL中MyISAM引擎与InnoDB引擎性能简单测试[硬件配置]CPU : AMD2500 (1.8G)内存: 1G/现代硬盘: 80G/IDE[软件配置]OS : Windows XP SP2SE : PHP5.2.1DB : MySQL5.0.37Web: IIS6[MySQL表结构]CREATETABLEmyisam ( id int(11) NOTNULLauto_increment, name varchar(100) defau…

[原创] Tips: 两种目录遍历的方法

目录的遍历是个老问题&#xff0c;主要用在目录遍历类操作&#xff0c;比如删除、统计磁盘占用等等情况。目录就是一个典型的树形结构&#xff0c;递归是最简单的方法了。functionErgodicDirectory1($dir) { $dpopendir($dir); while($filereaddir($dp)) { if…

系统的安全以及强行破解密码的手法

文章目录账号安全基本措施系统账号清理密码安全控制命令历史限制终端自动注销SU命令切换用户用途及用法密码验证限制使用su命令的用户Linux中的PAM安全认证su命令的安全隐患PAM可插拔式认证模块PAM认证原理PAM认证的构成PAM安全认证流程使用sudo机制提升权限sudo命令的用途及用…