说明
- 流,算是一种对不同事物,但有相同特性的抽象封装,可能这样说并不理解,但是我们早就使用过了,例如打开文件
fopen
等操作,其实就是用的流,fopen(\'abc.txt\')
实际上就是fopen(\'file://abc.txt\')
,或者是与app交互用到的php://input
等获取post数据也是流的一种 - php官方文档可以看Streams API for PHP Extension Authors和Stream
- 我们只看流中与socket相关的封装,上篇我们建立一个连接需要好几个步骤,比较繁琐,而stream中对此进行了简化封装。至于流的其它包装过滤等功能,可自己去查询资料,好像是在《Modern PHP》中也有章节对此做过讲解。
- 相关的stream函数可以参照Workerman中具体的使用场景,Workerman中没有使用上节的socket函数,而是调用的更加简洁方便的stream函数,
相关函数
-
服务端函数
stream_socket_server
( string $local_socket [, int &$errno [, string &$errstr [, int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN [, resource $context ]]]] ) : resource- 创建socket服务端
- 此函数可以代替上节的
socket_create
、socket_bind
、socket_listen
这三个函数,例如:$socket = stream_socket_server(\'tcp://0.0.0.0:8000\');
相当于$socket = socket_create(AF_INET, SOCK_STREAM, 0);
+socket_bind($socket, \'0.0.0.0\', 8000);
+socket_listen($socket);
- $flags参数,如果是用udp通信的话,
STREAM_SERVER_LISTEN
是不需要的,$context则是上下文,后面有单独的函数来生成此类型,还有需要注意的是,stream_socket_server
和socket_create
的返回值虽然都是resource,但两个不能通用,socket扩展中有单独的函数来转换这两者。
stream_socket_accept
( resource $server_socket [, float $timeout = ini_get("default_socket_timeout") [, string &$peername ]] ) : resource- 接收客户端的连接(udp通信时,不需要用到此函数)
- 类似
socket_accept
,需要注意的是第一个参数,只能是stream_socket_server
的返回值。
-
客户端函数
stream_socket_client
( string $remote_socket [, int &$errno [, string &$errstr [, float $timeout = ini_get("default_socket_timeout") [, int $flags = STREAM_CLIENT_CONNECT [, resource $context ]]]]] ) : resource- 创建socket客户端,连接到服务端
- 此函数可以替代上节的
socket_create
、socket_connect
这两个函数,例如$socket = stream_socket_client(\'tcp://0.0.0.0:8000\'\')
相当于$socket = socket_create(AF_INET, SOCK_STREAM, 0);
+$result = socket_connect($socket, \'0.0.0.0\', 8000);
- $flags参数主要有两个选项
STREAM_CLIENT_CONNECT
和STREAM_CLIENT_ASYNC_CONNECT
,第二个参数是设置异步,与阻塞又不太相同,挺让人费解的,而且网上也基本搜不到相关资料,当设置此参数时,将不会检测地址端口是否真的能连通,都为立即返回正常的资源resource,那如何来确定服务端是否真的能链接上呢?目前个人所知的方式是用stream_select
类似的多路IO复用来检测,当真正链接或链接失败后内核会通知到read或者write或者except(具体通知到哪个windows和linux不一致,需要区别处理),下面的stream_select
函数和后期IO多路复用时会讲到。
-
读写函数
fread
( resource $handle , int $length ) : string- 读取数据
fwrite
( resource $handle , string $string [, int $length ] ) : int- 写入数据
- 上面两个函数和我们平常读取文件一样,正如我们说的,操作文件也是操作一种流,所以方法通用
stream_socket_recvfrom
( resource $socket , int $length [, int $flags = 0 [, string &$address ]] ) : string- 接收数据,最后参数是引用,用于获取远端链接的地址
stream_socket_sendto
( resource $socket , string $data [, int $flags = 0 [, string $address ]] ) : int- 发送数据
- 上面两个函数和
fread
和fwrite
基本一致,但功能更多一些,$flags参数可以设置发送OOB数据,而$address参数则多是用于udp通信,由于udp通信时不使用stream_socket_accept
,所以无法获取到新的resource,那就无法向指定的客户端中写数据,所以一般先用stream_socket_recvfrom
获取最后引用参数$address即为客户端的地址,然后再用stream_socket_sendto
设置$address,来向指定的客户端发送。
-
其它常用函数
stream_select
( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] ) : int- 调用系统select()相关IO模型
- 类似上节的
socket_select
,详细的在后面的IO模型中会总结
stream_set_blocking
( resource $stream , bool $mode ) : bool- 设置阻塞与非阻塞,false为非阻塞,true为阻塞
- 类似上节的
socket_set_block
和socket_set_nonblock
,但是还是有些区别的,上节我们知道socket的阻塞影响的函数为:socket_connect、socket_accept以及各种socket读写函数,而stream的阻塞影响的函数仅仅为stream相关的读写函数,stream_socket_accept
不受影响,一直是阻塞,stream_socket_client
更不会受影响,因为stream_socket_client
的返回值,才能作为stream_set_blocking
的第一个参数,所以上方说stream_socket_client
的参数flags设置为异步,算是弥补了这一问题。
fclose
( resource $handle ) : bool- 关闭链接
stream_socket_shutdown
( resource $stream , int $how ) : bool- 关闭链接
- 上面两个函数功能基本一样,
stream_socket_shutdown
还可以控制只关闭读或者只关闭写 socket_import_stream
( resource $stream ) : resource- 将stream资源转为socket资源
socket_export_stream
( resource $socket ) : resource- 将socket资源转为stream资源
- 上面也提到过,socket扩展和stream两者链接产生的resource是不互通的,上面两个函数就是将两者转换的,而这两个函数是定义在上节所说的socket扩展中的,使用前需要确定socket扩展有打开
stream_context_create
- 上下文创建
stream_socket_server
和stream_socket_client
最后一个参数就是上下文,相关的参数需要经过此函数的组装后才能传入,我们可能早就接触过,例如我们经常用到的file_get_content
可以模拟post来获取数据,就需要用到它的第三个上下文参数。
示例
- 服务端
$socket = stream_socket_server(\'tcp://127.0.0.1:8888\', $errno, $errstr); while ($conn = stream_socket_accept($socket)) { fwrite($conn, "hello\\n"); fclose($conn); } fclose($socket);
- 客户端
$socket = stream_socket_client(\'tcp://127.0.0.1:8888\', $errorno, $errstr); while (!feof($socket)) { echo fread($socket, 1024); } fclose($socket);
- 上方是简单的示例代码,更多的可以查看官网各函数下的示例代码
- stream与socket相比是更简洁了,但是原理是一样的,同样的只能连接一个客户端的局限性并没有解决,还是需要后续的完善