libevent 的工作模型

来源:互联网 发布:spark 网页端口 编辑:程序博客网 时间:2024/05/29 17:18
///////////////////////////////////////////////////////////////////////////////////////
// 办事情必须要实事求是,所以我写了如下程序,并有如下结论:
// 事件处理模型是事件和事件处理的一一对应关系。
// 事件被登记并挂载在事件树上, 然后由dispatch 函数进行查询。
// 对于满足条件的事件,处理其事件。
// 通过此例测试可知,dispatch 是个单线程模型,不能够并发工作,当一个事件被处理时,
// 下一个事件不会被同时处理。 当一个事件被长时间处理时,后面的事件会被堆积。
// 总之,事件被顺序的一个一个执行。有一个好处,对于事件共享的数据,不必加锁保护。
// 我无意认真研读libevent 源码,而很多研读源码的文章也未说清上述观点,所以才有此测试程序
////////////////////////////////////////////////////////////////////////////////////////

观点都在代码里,就直接上代码来。 头文件:libevent_srv.h

ifndef __LIBEVENT_TST#define __LIBEVENT_TST#define PORT 8800#define BACKLOG 5#define MAX_RTSP_BUFFER 2048struct sock_ev {    struct event* read_ev;    struct event* write_ev;    struct event* timer_ev;    char* buffer;// record client info       char ipDocDecimal[INET_ADDRSTRLEN];    unsigned short port;};int  GetSessionID();void on_accept(int sock, short event, void* arg);void on_write(int sock, short event, void* arg);void on_read(int sock, short event, void* arg);void Timer_CB(int fd, short event , void *arg);void release_sock_event(struct sock_ev* ev);#endif
代码体: libevent_srv.c

#include <stdio.h>// printf 等声明#include <stdlib.h>// malloc, free 声明#include <string.h>// memset, strlen 等声明#include <sys/socket.h>// socket 等声明#include <netinet/in.h>// struct sockeadd 等声明#include <event.h>// struct event, event_base 等#include "libevent_srv.h"//gBase 是基本事件管理员。 对所有连接仅此一个.//因在多个函数中出现,故声明为全局变量struct event_base* gBase;int main(int argc, char* argv[]){int sock = socket(AF_INET, SOCK_STREAM, 0);//设置了该选项后,在父子进程模型中,当子进程为客户服务的时候如果父进程退出,可以重新启动程序完成服务的无缝升级,//否则在所有父子进程完全退出前再启动程序会在该端口上绑定失败,也即不能完成无缝升级.//  SO_REUSEADDR, 地址可以重用,不必time_wait 2个MSL 时间  (maximum segment lifetime)int yes = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);serv_addr.sin_addr.s_addr = INADDR_ANY;bind(sock, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr));printf("socket listen now\n");listen(sock, BACKLOG);//创建一个事件处理的全局变量,可以理解为这是一个负责集中处理各种出入IO事件的总管家,//它负责接收和派发所有输入输出IO事件的信息,这里调用的是函数event_base_new(), 很多程序里这里用的是event_init(),//但event_init 是一个过时的接口,官方建议采用event_base_new()gBase = event_base_new();struct event listen_ev;//listen_en这个事件监听sock这个描述字的读操作,当读消息到达时调用on_accept函数,//EV_PERSIST参数告诉系统持续的监听sock上的读事件,如果不加该参数,每次要监听该事件时就要重复的调用event_add函数,//sock这个描述符是bind到本地的socket端口上,因此其对应的可读事件自然就是来自客户端的连接到达event_set(&listen_ev, sock, EV_READ|EV_PERSIST, on_accept, NULL);//告诉处理IO的管家请留意我们的listen_ev上的事件event_base_set(gBase, &listen_ev);//当有关心的事件到达时,调用on_accept函数event_add(&listen_ev, NULL);//正式启动libevent的事件处理机制,使系统运行起来,//event_base_dispatch是一个无限循环 , 跟windows 编程的事件处理机制还真是相似啊。//通过此例测试可知,dispatch 是个单线程模型,不能够并发工作,当一个事件被处理时,下一个事件不会被同时处理。//当一个事件被长时间处理时,后面的事件会被堆积。总之,事件被顺序的一个一个执行。event_base_dispatch(gBase);return 0;}void on_accept(int sock, short event, void* arg){int sid = GetSessionID();printf("session id:%d\n",sid);//socket的描述字可以封装一个结构体sock_ev 来保护读、写的事件以及数据缓冲区//多个客户端因而对应多个newfd, 并对应多个sock_ev 变量struct sock_ev* ev = (struct sock_ev*)malloc(sizeof(struct sock_ev));ev->read_ev = (struct event*)malloc(sizeof(struct event));ev->write_ev = (struct event*)malloc(sizeof(struct event));ev->timer_ev = (struct event*)malloc(sizeof(struct event));ev->buffer = (char*)malloc(MAX_RTSP_BUFFER);int sin_size = sizeof(struct sockaddr_in);struct sockaddr_in cli_addr;int newfd = accept(sock, (struct sockaddr*)&cli_addr, &sin_size);char IPDotDecimal[INET_ADDRSTRLEN];struct in_addr addr=cli_addr.sin_addr;inet_ntop(AF_INET,&addr,IPDotDecimal,sizeof(IPDotDecimal));strcpy(ev->ipDocDecimal, IPDotDecimal);ev->port = cli_addr.sin_port;printf("ip:%s, port:%d\n",IPDotDecimal, cli_addr.sin_port);//在客户描述字newfd上监听可读事件,当有数据到达是调用on_read函数。read_ev作为参数传递给了on_read函数。//注意:read_ev需要从堆里malloc出来,如果是在栈上分配,那么当函数返回时变量占用的内存会被释放,//此时事件主循环event_base_dispatch会访问无效的内存而导致进程崩溃(即crash);event_set(ev->read_ev, newfd, EV_READ|EV_PERSIST, on_read, ev);event_base_set(gBase, ev->read_ev);event_add(ev->read_ev, NULL);// 增加一个定时器:evtimer_set(ev->timer_ev, Timer_CB, ev);event_base_set(gBase, ev->timer_ev);struct timeval timer_v;timer_v.tv_sec = 2;timer_v.tv_usec = 0;evtimer_add(ev->timer_ev, &timer_v);}void on_write(int sock, short event, void* arg){char* buffer = (char*)arg;send(sock, buffer, strlen(buffer), 0);}void on_read(int sock, short event, void* arg){struct event* write_ev;int size;struct sock_ev* ev = (struct sock_ev*)arg;bzero(ev->buffer, MAX_RTSP_BUFFER);size = recv(sock, ev->buffer, MAX_RTSP_BUFFER, 0);        printf("---receive data:---, size:%d\n", size);        printf("%s\n", ev->buffer); if (size == 0) {//已经关闭了连接, 释放资源release_sock_event(ev);close(sock);return;}//在on_read函数中从socket读取数据后程序就可以直接调用write/send接口向客户回写数据了,//  直接调用write/send函数向客户端写数据可能导致程序较长时间阻塞在IO操作上,//比如socket的输出缓冲区已满,则write/send操作阻塞到有可用的缓冲区之后才能进行实际的写操作,//而通过注册on_write函数,那么libevent会在合适的时间调用我们的callback函数//在sock 注册 on_write 函数event_set(ev->write_ev, sock, EV_WRITE, on_write, ev->buffer);event_base_set(gBase, ev->write_ev);event_add(ev->write_ev, NULL);} void Timer_CB(int fd, short event , void *arg){struct sock_ev * ev = (struct sock_ev *) arg;printf("Timer_CB called sleep 2s,emulate a long process\n");sleep(2);struct timeval timer_v;timer_v.tv_sec = 2;timer_v.tv_usec = 0;evtimer_add(ev->timer_ev, &timer_v);}void release_sock_event(struct sock_ev* ev){printf("socket resource released:%s %d\n",ev->ipDocDecimal,ev->port);event_del(ev->read_ev);free(ev->read_ev);free(ev->write_ev);free(ev->timer_ev);free(ev->buffer);free(ev);}int  GetSessionID(){static int sessionID = 1; // 对于成千上万的连接请求,如果有并发存在,需要枷锁保护sessionID 的数据更新// 但libevent 是单线程模型,所以不用加锁。return sessionID++;       }

为了代码的完整性,也贴上socket 客户端程序 client_test.c

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/socket.h>#include <netinet/in.h>#define MAXLINE 256#define SERV_PORT 8800void do_loop(FILE *fp, int sockfd){int n;char sendline[MAXLINE], recvline[MAXLINE + 1];while(fgets(sendline, MAXLINE, fp) != NULL){/* read a line and send to server */write(sockfd, sendline, strlen(sendline));/* receive data from server */n = read(sockfd, recvline, MAXLINE);if(n == -1){perror("read error");exit(1);}recvline[n] = 0; /* terminate string */printf("\nreceiv:%s\n",recvline);}}int main(int argc, char **argv){int sockfd;struct sockaddr_in servaddr;/* check args */if(argc != 2){printf("usage: udpclient <IPaddress>\n");exit(1);}/* init servaddr */bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(SERV_PORT);if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){printf("[%s] is not a valid IPaddress\n", argv[1]);exit(1);}//sockfd = socket(AF_INET, SOCK_DGRAM, 0);sockfd = socket(AF_INET, SOCK_STREAM, 0);/* connect to server */if(connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1){perror("connect error");exit(1);}do_loop(stdin, sockfd);return 0;}
[hjj@hjj ~/work/test/libevent_srv]$ cat Makefile
all:
    gcc -g -o libevent_srv libevent_srv.c -levent
    gcc -g -o client_test client_test.c
clean:
    rm *~ libevent_srv client_test

结果分析:

server 端可接受多个连接, 每个连接是一个收什么回什么的程序,并有定时器

client 是一个从键盘输入即发送,然后等待响应的程序。


服务端可以接受客户端数据,并穿插长时间处理的timer 操作。

[hjj@hjj ~/work/test/libevent_srv]$ ./libevent_srv


socket listen now
session id:1
ip:127.0.0.1, port:58065
Timer_CB called sleep 2s,emulate a long process
---receive data:---, size:4
123

Timer_CB called sleep 2s,emulate a long process
Timer_CB called sleep 2s,emulate a long process
---receive data:---, size:4
abc

Timer_CB called sleep 2s,emulate a long process
Timer_CB called sleep 2s,emulate a long process
---receive data:---, size:5
asdf

123 将会立即被发送,受server sleep 的影响,在receiv 未返回之前,

受阻于recv 函数。

但此时控制台仍然可以接受用户输入,存到键盘缓冲区。就是abc.

程序接受来响应,此时我们又往键盘缓冲区送人了asdf.

程序从键盘缓存区读到了abc, 发走了,又收到了。(fget, write,read)

程序从键盘缓存区读到了asdf, 发走了,又收到了。(fgets, write,read)

可见fgets 一次从键盘缓冲区读取一行,按步就班的进行。


[hjj@hjj ~/work/test/libevent_srv]$ ./client_test 127.0.0.1
123
abc

receiv:123

asdf

receiv:abc


receiv:asdf



0 0
原创粉丝点击