关于libevent:libevent学习使用2

libevent 定时器libevent 的time-test.c形容了一个定时事件的应用。 根本流程和之前的signal事件类似: 初始化一个event base/* Initialize the event library */ base = event_base_new();新建一个事件应用event_assign初始化一个事件,并传入超时解决的处理函数,event_assign 函数最初一个参数是超时处理函数的参数。参数flags的EV_PERSIST示意该事件每次产生、并解决完后持续监听。如果不设置,此超时事件只解决一次。 /* Initialize one event */flags = EV_PERSIST;//...event_assign(&timeout, base, -1, flags, timeout_cb, (void*) &timeout);增加事件应用struct timeval 设定超时事件,并将该构造传入event_add的第二个参数。 evutil_timerclear(&tv);tv.tv_sec = 2;event_add(&timeout, &tv);开始监听处理事件event_base_dispatch(base);开释资源event_free(signal_event);event_base_free(base);信号处理函数回调函数的参数如下: @param fd An fd or signal @param events One or more EV_* flags@param arg A user-supplied argument.typedef void (*event_callback_fn)(evutil_socket_t, short, void *);总结1、信号事件、定时器事件 这两个流程基本相同,创立eventbase、创立事件、增加事件、执行event_base_dispatch。 2、TCP Server 监听事件 应用evconnlistener_new_bind 函数,该函数蕴含了创立事件,和TCP监听流程,须要自定义客户端接入的socket处理程序。 3、socket解决 应用了bufferevent解决socket收发数据, 须要自定义读写和异样的处理函数。

December 20, 2021 · 1 min · jiezi

TAILQ-队列之一二事

TAILQ队列是FreeBSD内核中的一种队列数据结构,在一些著名的开源库中(如DPDK,libevent)有广泛的应用。TAILQ队列的定义TAILQ队列有HEAD和ENTRY两种基本的数据结构 #define TAILQ_HEAD(name, type) \struct name { \ struct type *tqh_first; /* first element */ \ struct type **tqh_last; /* addr of last next element */ \}#define TAILQ_ENTRY(type) \struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev;/* addr of previous next element*/ \} 注意:数据结构中的filed都是type类型的指针(或者是二级指针),这里的type是用户的队列元素类型,,将ENTRY结构内嵌到用户的QUEUE_ITEM结构中: struct QUEUE_ITEM{ int value; TAILQ_ENTRY(QUEUE_ITEM) entries; }; TAILQ_HEAD(headname,QUEUE_ITEM) queue_head; 这和Linux中list的组织方式不一样,后者是单纯地将struct list_head作为链表的一个挂接点,并没有用户的信息,具体差别可以看下图: TAILQ队列的操作TAILQ提供了多种操作队列的API,比如: TAILQ_HEAD(name, type)TAILQ_ENTRY(type)TAILQ_EMPTY(head)TAILQ_FIRST(head)TAILQ_FOREACH(var, head, field) TAILQ_INIT(head)TAILQ_INSERT_AFTER(head, listelm, elm, field)TAILQ_INSERT_BEFORE(listelm, elm, field)TAILQ_INSERT_TAIL(head, elm, field).....这些接口的实现和更多的操作接口可以参考 FreeBSD queue ...

June 19, 2019 · 4 min · jiezi

PHP socket初探 --- 一些零碎细节的拾漏补缺

原文:https://t.ti-node.com/thread/…前面可以说是弄了一系列的php socket和多进程的一大坨内容,知识浅显、代码粗暴、风格简陋,总的说来,还是差了一些细节。今天,就一些漏掉的细节补充一下。一些有志青年可能最近手刃了Workerman源码,对于里面那一大坨stream_select()、stream_socket_server()表示疑惑,这个玩意和socket_create、socket_set_nonblock()有啥区别?其实,php官方手册里也提到过一嘴,socket系函数就是基于BSD Socket那一套玩意搞的,几乎就是将那些东西简单包装了一下直接抄过来用的,抄到甚至连名字都和C语言操控socket的函数一模一样,所以说socket系函数是一种比较低级(Low-Level,这里的低级是指软件工程中分层中层次的高低)socket操控方式,可以最大程度给你操作socket的自由以及细腻度。在php中,socket系本身是作为php扩展而体现的,这个你可以通过php -m来查看有没有socket,这件事情意味着有些php环境可能没有安装这个扩展,这个时候你就无法使用socket系的函数了。但stream则不同了,这货是内建于php中的,除了能处理socket网络IO外,还能操控普通文件的打开写入读取等,stream系将这些输入输出统一抽象成了流,通过流来对待一切。有人可能会问二者性能上差距,但是本人没有测试过,这个我就不敢轻易妄言了,但是从正常逻辑上推演的话,应该不会有什么太大差距之类的。一定要分清楚监听socket和连接socket,我们服务器监听的是监听socket,然后accept一个客户端连接后的叫做连接socket。关于“异步非阻塞”,这五个字到底体现在哪儿了。swoole我就不说了,我源码也才阅读了一小部分,我就说Workerman吧,它在github上称:“Workerman is an asynchronous event driven PHP framework with high performance for easily building fast, scalable network applications.”,看到其中有asynchronous(异步)的字样,打我脸的是我并没有看到有non-block(非阻塞)的字样,不过无妨,脸什么的不重要,重要的是我文章里那一坨又一坨的代码里哪里体现了非阻塞、哪里体现了异步。来吧,看代码吧。看代码前,你要理解异步和非阻塞的区别是什么,因为这二者在表现结果上看起来是有点儿相似的,如果你没搞明白,那么一定要通过这个来理解一下《PHP socket初探 — 关于IO的一些枯燥理论》。<?php// 创建一个监听socket,这个一个阻塞IO的socket$listen = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );socket_bind( $listen, ‘0.0.0.0’, 9999 );socket_listen( $listen );while( true ){ // socket_accept也是阻塞的,虽然有while,但是由于accpet是阻塞的,所以这段代码不会进入无限死循环中 $connect = socket_accept( $listen ); if( $connect ){ echo “有新的客户端”.PHP_EOL; } else { echo “客户端连接失败”.PHP_EOL; }}将上面代码保存了运行一下,然后用telnet可以连接上去。但是,这段代码中有两处是阻塞的,最主要就是监听socket是阻塞的。那么,非阻塞的监听socket会是什么感受?<?php// 创建一个监听socket,将其设置为非阻塞$listen = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );socket_bind( $listen, ‘0.0.0.0’, 9999 );socket_listen( $listen );// ⚠️⚠️⚠️⚠️⚠️⚠️ 这里设置非阻塞!socket_set_nonblock( $listen );while( true ){ $connect = socket_accept( $listen ); if( $connect ){ echo “有新的客户端”.PHP_EOL; } else { echo “客户端连接失败”.PHP_EOL; }}将代码保存了运行一下,告诉我:来来来,分析一波儿,为啥会出现这种现象。因为监听socket被设置成了非阻塞,我们知道非阻塞就是程序立马返回,然后再过段时间回来询问,用例子就是“等馒头过程中,看下微博,抬头问馒头好了吗?然后看下微信,抬头问馒头好了吗?然后看下v2ex,抬头问馒头好了吗?。。。 。。。”,这样你是不是就能理解了?因为并没有客户端连接进来,所以每当询问一次socket_accept后得到的反馈都是“没有连接”,所以就直接走到“客户端连接失败”的分支中去了,而且是不断的不停的。这个时候,你用htop或者top命令查看服务器CPU,不出意外应该是100%,这是非阻塞的极大缺点。紧接着是异步呢?异步体现在哪儿了?我们说异步,是你去阿梅那里买馒头,阿梅告诉你说“馒头还没好,你去干别的吧,好了我打电话通知你”,然后你就专心去打游戏去了,直到电话响了你去拿馒头。Workerman的异步更多是体现在对一个完整请求的处理流上,而不是正儿八经的异步的定义概念,如果你没听明白,那也可能正常,慢慢理解。最后,我补充一句:epoll是同步的,而不是异步。 ...

November 21, 2018 · 1 min · jiezi

PHP socket初探 --- 含着泪也要磕完libevent(三)

原文地址:https://t.ti-node.com/thread/…这段时间相比大家也看到了,本人离职了,一是在家偷懒实在懒得动手,二是好不容易想写点儿时间全部砸到数据结构和算法那里了。今儿回过头来,继续这里的文章。那句话是怎么说的:“自己选择的课题,含着泪也得磕完!”(图文无关,详情点击这里)。其实在上一篇libevent文章中(《PHP socket初探 — 硬着头皮继续libevent(二)》),如果你总结能力很好的话,可以观察出来我们尝试利用libevent做了至少两件事情:毫秒级别定时器信号监听工具大家都是码php的,也喜欢把自己说的洋气点儿:“ 我是写服务器的 ”。所以,今天的第一个案例就是拿libevent来构建一个简单粗暴的http服务器:<?php$host = ‘0.0.0.0’;$port = 9999;$listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );socket_bind( $listen_socket, $host, $port );socket_listen( $listen_socket );echo PHP_EOL.PHP_EOL.“Http Server ON : http://{$host}:{$port}".PHP_EOL;// 将服务器设置为非阻塞,此处概念可能略拐弯,建议各位查阅一下手册socket_set_nonblock( $listen_socket );// 创建事件基础体,还记得航空母舰吗?$event_base = new EventBase();// 创建一个事件,还记得歼15舰载机吗?我们将“监听socket”添加到事件监听中,触发条件是read,也就是说,一旦“监听socket”上有客户端来连接,就会触发这里,我们在回调函数里来处理接受到新请求后的反应$event = new Event( $event_base, $listen_socket, Event::READ | Event::PERSIST, function( $listen_socket ){ // 为什么写成这样比较执拗的方式?因为,“监听socket”已经被设置成了非阻塞,这种情况下,accept是立即返回的,所以,必须通过判定accept的结果是否为true来执行后面的代码。一些实现里,包括workerman在内,可能是使用@符号来压制错误,个人不太建议这>样做 if( ( $connect_socket = socket_accept( $listen_socket ) ) != false){ echo “有新的客户端:".intval( $connect_socket ).PHP_EOL; $msg = “HTTP/1.0 200 OK\r\nContent-Length: 2\r\n\r\nHi”; socket_write( $connect_socket, $msg, strlen( $msg ) ); socket_close( $connect_socket ); }}, $listen_socket );$event->add();$event_base->loop();将代码保存为test.php,然后php http.php运行起来。再开一个终端,使用curl的GET方式去请求服务器,效果如下:这是一个非常非常简单地不能再简单的http demo了,对于一个完整的http服务器而言,他还差比较完整的http协议的实现、多核CPU的利用等等。这些,我们会放到后面继续深入的文章中开始细化丰富。还记得我们使用select系统调用实现了一个粗暴的在线聊天室,select这种业余的都敢出来混个聊天室,专业的绝对不能怂。无数个专业???????????????送给libevent!啦啦啦啦,开始码:<?php$host = ‘0.0.0.0’;$port = 9999;$fd = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );socket_bind( $fd, $host, $port );socket_listen( $fd );// 注意,将“监听socket”设置为非阻塞模式socket_set_nonblock( $fd );// 这里值得注意,我们声明两个数组用来保存 事件 和 连接socket$event_arr = []; $conn_arr = []; echo PHP_EOL.PHP_EOL.“欢迎来到ti-chat聊天室!发言注意遵守当地法律法规!".PHP_EOL;echo " tcp://{$host}:{$port}".PHP_EOL;$event_base = new EventBase();$event = new Event( $event_base, $fd, Event::READ | Event::PERSIST, function( $fd ){ // 使用全局的event_arr 和 conn_arr global $event_arr,$conn_arr,$event_base; // 非阻塞模式下,注意accpet的写法会稍微特殊一些。如果不想这么写,请往前面添加@符号,不过不建议这种写法 if( ( $conn = socket_accept( $fd ) ) != false ){ echo date(‘Y-m-d H:i:s’).’:欢迎’.intval( $conn ).‘来到聊天室’.PHP_EOL; // 将连接socket也设置为非阻塞模式 socket_set_nonblock( $conn ); // 此处值得注意,我们需要将连接socket保存到数组中去 $conn_arr[ intval( $conn ) ] = $conn; $event = new Event( $event_base, $conn, Event::READ | Event::PERSIST, function( $conn ) use( $event_arr ) { global $conn_arr; $buffer = socket_read( $conn, 65535 ); foreach( $conn_arr as $conn_key => $conn_item ){ if( $conn != $conn_item ){ $msg = intval( $conn ).‘说 : ‘.$buffer; socket_write( $conn_item, $msg, strlen( $msg ) ); } } }, $conn ); $event->add(); // 此处值得注意,我们需要将事件本身存储到全局数组中,如果不保存,连接会话会丢失,也就是说服务端和客户端将无法保持持久会话 $event_arr[ intval( $conn ) ] = $event; }}, $fd );$event->add();$event_base->loop();将代码保存为server.php,然后php server.php运行,再打开其他三个终端使用telnet连接上聊天室,运行效果如下所示:尝试放一张动态图试试,看看行不行,自己制作的gif都特别大,不知道带宽够不够。截止到这篇为止,死磕Libevent系列的大体核心三把斧就算是抡完了,弄完这些,你在遇到这些代码的时候,就应该不会像下面这个样子了: ...

November 20, 2018 · 2 min · jiezi