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

8次阅读

共计 2929 个字符,预计需要花费 8 分钟才能阅读完成。

原文地址: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 系列的大体核心三把斧就算是抡完了,弄完这些,你在遇到这些代码的时候,就应该不会像下面这个样子了:

正文完
 0