15. PHP socket 初探 - 一些零碎细节的拾漏补缺(一)
前面可以说是弄了一系列的 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的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 被设置成了非阻塞,我们知道非阻塞就是程序立马返回,然后再过段时间回来询问,用例子就是“等馒头过程中,看下微博,抬头问馒头好了吗?然后看下微信,抬头问馒头好了吗?这样你是不是就能理解了?因为并没有客户端连接进来,所以每当询问一次 socket_accept 后得到的反馈都是 没有连接,所以就直接走到 客户端连接失败 的分支中去了,而且是不断的不停的。这个时候,你用 htop 或者 top 命令查看服务器 CPU,不出意外应该是 100%,这是非阻塞的极大缺点。
紧接着是异步呢?异步体现在哪儿了?我们说异步,是你去阿梅那里买馒头,阿梅告诉你说 馒头还没好,你去干别的吧,好了我打电话通知你,然后你就专心去打游戏去了,直到电话响了你去拿馒头。Workerman 的异步更多是体现在对一个完整请求的处理流上,而不是正儿八经的异步的定义概念,如果你没听明白,那也可能正常,慢慢理解。最后,我补充一句:epoll 是同步的,而不是异步。