libevent 的工作模型

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

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

ifndef __LIBEVENT_TST
#define __LIBEVENT_TST

#define PORT 8800
#define BACKLOG 5
#define MAX_RTSP_BUFFER 2048

struct 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 8800

void 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



©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页