标题:【迅搜07】基础对象概览(二)服务器与命令对象及数据传输原理

文章目录
    分类:存储运维 标签:迅搜

    基础对象概览(二)服务器与命令对象及数据传输原理

    在学习完最基础的 XS 对象和一些字段对象之后,我们今天将学习到的是 XS 的 PHP SDK 中非常核心的一个对象,那就是 XSServer 对象。从名字就可以看出,它是负责和服务端交互的,也就是 PHP 与 Xapian 的交互部分。要说搜索索引,最核心的应该是在索引和搜索的操作上,这两部分也是我们后面要重点关注的部分。但是,如果没有 XSServer 的支持,则一切都无法实现。幸好,这一部分的内容不多,也并不复杂,咱们就好好来看一下。

    XSServer 服务器对象

    XSServer 是 XSIndex 和 XSSearch 的父类,主要保存的是服务器的一些信息以及和服务器的交互。也就是说,和 Xapian 进行通信就是这个对象的主要工作。


    咱们直接演示这个对象的一些属性打印出来的效果,先来看看它的构造函数,也就是实例化参数。

    $server = new XSServer('localhost:8384', $xs);
    echo $server->connString, PHP_EOL; // localhost:8384

    XSServer 对象的构造参数有两个,第一个是连接地址和端口,第二个参数是一个 XS 对象。不过一般很少会去这样直接实例化,为啥呢?前面就说过了,XSIndex 和 XSSearch 都是继承自它的,所以这两个类的实例化对象也是有 XSServer 的方法的。而且单独的一个 XSServer 的作用有限,因此,在 XS 对象中也没有直接返回 XSServer 对象的属性方法,更多的还是使用它的两个子类。

    $index = $xs->index;
    echo $index->connString, PHP_EOL; // localhost:8383
    
    $search = $xs->search;
    echo $search->connString, PHP_EOL; // localhost:8384

    注意看,索引对象和搜索对象返回的 connString 中的端口号是不同的。剩余的其它部分大家可以自己打印看一下,除了端口号不同之外,其它部分基本都是一样的。后续我们将使用 $search 对象进行余下的测试。

    echo $search->project, PHP_EOL; // zyarticle
    echo $search->socket, PHP_EOL; // Resource id #17
    var_dump($search->xs === $xs); // bool(true)

    这三个属性不用过多解释了吧。project 属性,对应 getProject()、setProject() 方法,用于获取和设置项目名,这个一会咱们再测试一下修改它之后会有什么效果;socket 对应 getSocket(),获取与服务器建立的连接的 Socket 句柄,没错,和后端服务器的连接是通过 TCP/Socket 进行连接通信的;xs 对应 getXs(),就是 XSServer 实例化时,传递进去的那个 XS 对象,也就是当前 XS 项目本身。

    var_dump($search->hasRespond()); // bool(false)
    if($search->hasRespond()){
    	$search->respond; // 返回 XSCommand 对象
    }

    hasRespond() 方法用于判断当前连接是否有可读数据,一般如果需要调用 respond 或对应的 getRespond() 方法前,会先调用 hasRespond() ,用于避免 respond,getRespond() 在长连接读取数据时会阻塞等待。这一块大家了解一下就好,后面我们还会看到它的应用。。

    修改 project

    咱们来试试修改 project ,这个 project 就是配置文件以及 XS 项目对象中的 project.name 部分。XS 通过这个 project.name 来区分不同的索引项目,并且建立不同的索引项目目录。如果我们切换了,会产生什么效果呢?

    $search->project = 'demo';
    print_r($search->setLimit(1)->search(''));
    //     Array
    // (
    //     [0] => XSDocument Object
    //         (
    //             [_data:XSDocument:private] => Array
    //                 (
    //                     [id] => 1
    //                     [title] => 三号DEMO的,关于 xunsearch 的 DEMO 项目测试
    //                     [category_name] => �\�r��
    //                     [content] => 项目测试是一个很有意思的行为!
    //                 )
    // ………………………………

    注意,默认我们实例化时,走的是 zyarticle 那个项目哦,切换成 demo 之后,查询的结果竟然直接就变成了 demo 项目里的数据了。

    打开关闭连接

    接着我们继续测试 close() 和 open() 这两个方法。上面的 project 不用改回来,一会咱们再解释原因。

    $search->close();
    try{
      print_r($search->search(''));
    }catch(XSException $e){
      echo $e->getMessage(),PHP_EOL; // Broken server connection
    }

    close() 方法是用于关闭连接的,也就是关闭当前对象与服务器的那个 TCP/Socket 连接。当连接被关闭之后,查询也就无法进行了,直接会通过 XSException 异常对象返回 Broken server connection 的信息。


    既然可以关闭连接,那么当然也可以再开启啦。

    $search->open('8384');
    // $search->reopen();
    print_r($search->setLimit(1)->search(''));
    //     Array
    // (
    //     [0] => XSDocument Object
    //         (
    //             [_data:XSDocument:private] => Array
    //                 (
    //                     [id] => 1
    //                     [title] => 【PHP数据结构与算法1】在学数据结构和算法的时候我们究竟学的是啥?
    //                     [category_name] => PHP
    //                     [tags] => 数据结构,算法
    //                     [pub_time] => 20220723
    // ………………

    open() 方法接收一个参数,就是端口号,用于搜索服务的端口号是 8384 ,所以咱们需要将 8384 传递进去。当然,这里也可以指定 IP Host 的,也就是 "localhost:8384" 这种形式也可以。另外,如果只是想重开之前的配置中的连接信息,那么直接使用 reopen() 方法就可以了,连参数都不用传。


    接下来能够查询出内容,表明连接正常了。不过在这一小节的开头,我说过不用修改上一小节 project 的内容,也就是说,在进行 open() 之前,project 的值是 demo 呀,为啥查询之后的结果又变回 zyarticle 里面的内容了呢?


    其实呀,原因很简单,就是 open() 方法在开启连接的时候,会按照 XS 项目对象来重置一些属性值。也就是说,project 被重置回 XS 对象中的 name 属性的内容了。

    // 源码位置 /vendor/hightman/xunsearch/lib/XSServer.class.php
    public function open($conn)
    {
      $this->close();
      $this->_conn = $conn;
      $this->_flag = self::BROKEN;
      $this->_sendBuffer = '';
      $this->_project = null; // 看这里看这里
      $this->connect();
      $this->_flag ^= self::BROKEN;
      if ($this->xs instanceof XS) {  // 看这里看这里
        $this->setProject($this->xs->getName());
      }
    }

    设置超时时间

    通过上面的属性信息,我们已经了解到了 XS 中,PHP SDK 与 Xapian 也就是 XS 的底层搜索引擎之间的交互是通过 Socket 来进行了。那么这样的长连接通常来说都是不限制超时时间的,也就是说,客户端主动断开连接,或者服务端出现问题,才会结束连接。不过,咱们也可以设置一个超时时间。

    $search->setTimeout(1);
    sleep(2);
    
    try{
      print_r($search->search(''));
    }catch(XSException $e){
      echo $e->getMessage(),PHP_EOL; // Failed to recv the data from server completely (SIZE:0/8, REASON:closed)
    }

    setTimeout() 用于设置连接超时时间,比如上面的测试代码咱们设置了一秒。然后 sleep() 两秒,再进行查询的时候,就会报出 Failed to recv the data from server completely 这样的错误。你可以再测试一下,将 setTimeout() 的时间加长一点,超过 sleep() 的时间,查询请求就都是正常的了。

    执行与发送命令

    最后,我们再来看一下命令的发送。这个命令的发送其实就是 PHP SDK 与 Xapian 的交互方法。不管是 XSIndex 还是 XSSearch ,在我们构造完索引或查询对象之后,最终都是通过 XSServer 中的 Socket 连接发送给服务端 Xapian 的。而接下来的这两个方法,就是最终发送时用到的方法。

    • sendCommand() 往服务器直接发送指令(无缓存)

    • execCommand() 执行服务端指令并获取返回值

    上一小节的 setTimeout() 方法,本身就是通过向服务端发送超时配置信息来让服务端主动断开连接的,我们就拿它的代码来测试。(因为最简单)

    // 源码位置 /vendor/hightman/xunsearch/lib/XSServer.class.php
    public function setTimeout($sec)
    {
      $cmd = array('cmd' => XS_CMD_TIMEOUT, 'arg' => $sec);
      $this->execCommand($cmd, XS_CMD_OK_TIMEOUT_SET);
    }

    代码就这两行,那么我们就拿到外面来测试一下。

    $search->reopen(); // 上一小节如果超时关闭了,记得要重开一下
    
    $cmd = array('cmd' => XS_CMD_TIMEOUT, 'arg' => 1);
    $search->execCommand($cmd, XS_CMD_OK_TIMEOUT_SET);
    sleep(2);
    try{
      print_r($search->search(''));
    }catch(XSException $e){
      echo $e->getMessage(),PHP_EOL; // Failed to recv the data from server completely (SIZE:0/8, REASON:closed)
    }

    可以看到效果是一样的。那么我们将 execCommand() 换成 sendCommand() 会怎么样呢?


    大家可以自己试一下,异常信息会变成 Unexpected respond {CMD:128, ARG:208} 。这个原因就需要看一下这两个方法的源码了,这个不是我们学习的重点,简单来说的话,sendCommand() 只是发送数据,源码中非常简单,就是向 Socket 连接句柄中 write() 数据。而 execCommand() 不仅写,还读取了返回值,因此是一个完整的命令交互过程。所以说,Unexpected respond 的错误信息,就是写完之后在读缓冲区中有内容没有被取出来 ,到后面再次 search() 时,就会出现问题。既然这样,那就好解决啦。

    $cmd = array('cmd' => XS_CMD_TIMEOUT, 'arg' => 1);
    $search->sendCommand($cmd, XS_CMD_OK_TIMEOUT_SET); // Unexpected respond
    sleep(2);
    var_dump($search->hasRespond()); // bool(true)
    if($search->hasRespond()){
      var_dump($search->respond);
      // object(XSCommand)#18 (5) {
      //     ["cmd"]=>
      //     int(128)
      //     ["arg1"]=>
      //     int(0)
      //     ["arg2"]=>
      //     int(208)
      //     ["buf"]=>
      //     string(0) ""
      //     ["buf1"]=>
      //     string(0) ""
      //   }
    }
    try{
      print_r($search->search(''));
    }catch(XSException $e){
      echo $e->getMessage(),PHP_EOL; // Failed to recv the data from server completely (SIZE:0/8, REASON:closed)
    }

    前面的 hasRespond() 方法和 respond 属性没看到效果,这回就看到了吧,知道为什么 respond 会阻塞读了吧,因为咱们是 Socket 长连接读嘛,没有数据的时候它肯定就会阻塞着一直等待数据的到来。


    XS_CMD_TIMEOUT 和 XS_CMD_OK_TIMEOUT_SET 都是 SDK 中自带的常量,也可以看成是与 Xapian 交互的命令代码(它们都是数字类型常量,可以点过去看看)。


    这一段的内容只是抛砖引玉,我们这个系列不是分析源码的,但是,说实话,都已经写到这里了,具体的 PHP 部分的源码执行过程大家也可以继续顺着去研究了。说实话,整个 XS 的 PHP SDK 部分源码并不是非常的复杂。而 Xapian 和 SCWS 部分的源码就完全不用想了,之后如果能学学它们的单独使用就不错啦,毕竟全是 C/C++ 写的,只能留给各位大神来研究了。


    后面我们要重点学习的 XSIndex 索引以及 XSSearch 搜索相关的内容,最终都是通过 XSServer 中的这两个方法进行数据传输的。

    XSCommand 命令对象

    通过上一节,细心的你一定会发现,XSServer 的 respond 属性会返回一个 XSCommand 对象。同时,如果你看了 execCommand() 或 sendCommand() 的源码,就会发现它们执行的第一件事也是在创建 XSCommand 对象。

    // 源码 /vendor/hightman/xunsearch/lib/XSServer.class.php
    public function execCommand($cmd, $res_arg = XS_CMD_NONE, $res_cmd = XS_CMD_OK)
    {
      // create command object
      if (!$cmd instanceof XSCommand) {
        $cmd = new XSCommand($cmd);
    	}
    // …………………………
      $buf = $this->_sendBuffer . $cmd;
      $this->_sendBuffer = '';
      $this->write($buf);
    // …………………………

    在数据写入到 Socket 句柄时,也是将 XSCommand 拼接到写入的数据字符串中进入数据传递。也就是说,XSCommand 这个对象是一个命令对象,是与服务端进行交互的一个基本单位。


    在这里可能有小伙伴会有疑问,_sendBuffer 貌似是一个字符串呀,_sendBuffer 和 XSCommand 对象拼接?这什么鬼?答案依然在源码中。

    // 源码 /vendor/hightman/xunsearch/lib/XSServer.class.php XSCommand 类
    public function __toString()
    {
      if (strlen($this->buf1) > 0xff) {
        $this->buf1 = substr($this->buf1, 0, 0xff);
      }
      return pack('CCCCI', $this->cmd, $this->arg1, $this->arg2, strlen($this->buf1), strlen($this->buf)) . $this->buf . $this->buf1;
    }

    没错,XSCommand 对象重写了 __toString() 这个魔术方法。大佬真的是大佬,各种 PHP 特性功能的应用啊。很明显,这个 __toString() 最后 pack() 出来的二进制字符串,就是与 Xapian 进行交互的最终 API 了。就像是 ES 的 RESTFul API 一样。不过话说回来,ES 的交互真的太人性化了,直接用浏览器或者 CURL 工具就可以进行测试。而 Xapian 这种,想要通过命令行直接测试引擎里面的数据看来是不太可能了,真的必须就得通过 PHP 或者其它语言的 SDK 来操作了。或者各位大佬自己写一套?


    这个对象其它部分就没什么内容了,几个属性都是和发送命令的参数相关的,只有两个方法:getArg() 和 setArg() ,也是用于获取和设置命令执行时的参数。

    总结

    好了,到此为止,XS 中的基本对象就介绍得差不多了。通过今天的内容,是不是感觉到服务端的交互其实也就这样。这就体现了基础知识的重要性,最后的交互 API 就是二进制数据使用 Socket 进行的嘛。好吧,说得简单,咱也没真实的写过纯 Socket 编程相关的项目。但是,多少还是看明白了这个过程。剩下的嘛,XSIndex 和 XSSearch 就是我们接下来要学习的核心内容了。另外,还有一个 XSDocument 对象,会在下一篇索引管理的第一篇文章中一起学习到。


    测试代码:


    https://github.com/zhangyue0503/dev-blog/blob/master/xunsearch/source/7.php


    参考文档:


    http://www.xunsearch.com/doc/php/api/XSServer


    http://www.xunsearch.com/doc/php/api/XSCommand

    视频链接

    微信文章地址:https://mp.weixin.qq.com/s/-YutfPF-8CnE3bd8KUNndw

    B站视频地址:https://www.bilibili.com/video/BV1ht421h7o5

    搜索
    关注