“晚风吹人醒 万事藏于心 
        我没说不公平 也没说苦 我说我知道了”

转自:https://www.cnblogs.com/vishun/p/10475296.html

说明#

  • 流,算是一种对不同事物,但有相同特性的抽象封装,可能这样说并不理解,但是我们早就使用过了,例如打开文件fopen等操作,其实就是用的流,fopen('abc.txt')实际上就是fopen('file://abc.txt'),或者是与app交互用到的php://input等获取post数据也是流的一种
  • php官方文档可以看Streams API for PHP Extension AuthorsStream
  • 我们只看流中与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_createsocket_bindsocket_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_serversocket_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_createsocket_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_CONNECTSTREAM_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
      • 发送数据
    • 上面两个函数和freadfwrite基本一致,但功能更多一些,$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_blocksocket_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_serverstream_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相比是更简洁了,但是原理是一样的,同样的只能连接一个客户端的局限性并没有解决,还是需要后续的完善
posted on 2021-12-02 11:13  诗里刻画的影子  阅读(567)  评论(0编辑  收藏  举报