一次收到多組資料封包,如何處理?

flimulus

請問,若一次收到多組的資料封包,在 dealInput 函數上如何處理?

例如: 在 dealInput 函數上預期收到27byte但實際已收到87Byte,是否是回傳0,然後在dealProcess 自行使用 Buffer 切割封包,若是這樣,dealInput 的函數就無意義了。
請問如何處理?

阅读 2815
6个回答

walkor

在配置 例如 conf/conf.d/xxx.conf 中 有个 preread_length 字段,这个字段表明当有数据到来时,你不会全部读取,而是读取preread_length长度。

将preread_length设置成你预期读取的数据长度,一般设置为你协议包头的长度。例如你的协议包头长度为27byte,则preread_length=27。dealInput在一个请求数据到来时,会预先读取27字节,一般根据头部这27字节能够计算出整体包长,例如整个包包长是1027,那么你还差1000byte没收到,则dealnput返回1000,那么workerman会再次等待1000byte数据到来并读取1000byte

  • 暂无评论
flimulus

因為測試主機在新加坡,所以封包資料會 Delay,並且同時收到多筆。

我的問題是連續傳送多筆封包資料,其中最後一筆尚未收完,若是以現在您設計的架構,則無法準確處理單獨的每一筆封包。

我現在的解決方法,是在 dealInput 計算封包長度時,若傳回超過單筆封包長度時,便傳回負數,讓dealProcess 能得到完整的封包,剩下的封包等接收完畢後,在下一次傳給dealInput 檢查。

初步修改的程式碼如下:

public function dealInput($recv_str)
 { 
  1. 未達一個完整資料
    return  單一筆資料的長度 - strlen($recv_st); // 正數
  2. 剛好收到一筆資料
    return 單一筆資料的長度-strlen($recv_st); // 0
  3. 收到多筆資料(溢收)
    return 單一筆資料的長度 - strlen($recv_str); //  負數
}

//========================================================
public function dealInputBase($connection, $flag, $fd = null)
   {
        $this->currentDealFd = $fd;
        $buffer = stream_socket_recvfrom($connection, $this->recvBuffers);
        // 出错了
        if('' == $buffer && '' == ($buffer = fread($connection, $this->recvBuffers)))
        {
            if(!feof($connection))
            {
                return;
            }            
            // 客户端提前断开链接
            $this->statusInfo++;
            // 如果该链接对应的buffer有数据,说明发生错误
            if(!empty($this->recvBuffers))
            {
                $this->statusInfo++;
                $this->notice("CLIENT_CLOSE\nCLIENT_IP:".$this->getRemoteIp()."\nBUFFER:\n");
            }

            // 关闭链接
            $this->closeClient($fd);
            if($this->workerStatus == self::STATUS_SHUTDOWN)
            {
                $this->stop();
            }
            return;
        }        

        $this->recvBuffers .= $buffer;

        // 分拆資料封包
    while(true)
    {       
           $remain_len = $this->dealInput($this->recvBuffers);

          if ($remain_len<=0) 
          {         
            $data = $this->recvBuffers;

            if ($remain_len<0) //  只傳一筆資料至dealProcess
            {           
              $data = substr($data,0,strlen($data)+$remain_len);                
            }

            $this->dealProcess($data);  

            if ($remain_len<0) // 負數 再分拆
            {               
              $this->recvBuffers = substr($this->recvBuffers,$remain_len); // 複製新資料               
             }else{
              if($this->isPersistentConnection)
              {                
                 $this->recvBuffers  = array('buf'=>'', 'remain_len'=>$this->prereadLength);                 
              }else{
                // 关闭链接
                if(empty($this->sendBuffers))
                {
                    $this->closeClient($fd);
                }
              }          
              break;    
            }       
      }
      else if(false === $remain_len)
          {
            // 出错
            $this->statusInfo++;
            $this->sendToClient('packet_err:'.$this->recvBuffers);
            $this->notice("PACKET_ERROR\nCLIENT_IP:".$this->getRemoteIp()."\nBUFFER:\n");
            $this->closeClient($fd);
            break;
          }
          else 
          {
            $this->recvBuffers = $remain_len;
            break;
          }                   
    }

        // 检查是否是关闭状态或者是否到达请求上限
        if($this->workerStatus == self::STATUS_SHUTDOWN || $this->statusInfo >= $this->maxRequests)
        {
            // 停止服务
            $this->stop();
            // EXIT_WAIT_TIME秒后退出进程
            pcntl_alarm(self::EXIT_WAIT_TIME);
        }
    }

這樣不知有沒有問題?

謝謝。

  • 暂无评论
walkor

在一个请求开始时dealInput($recv_str) 中的$recv_str长度不会超过你设定的 preread_length,后面也不会超过你在 dealInput($recv_str) return 的长度,所以不存在收到的$recv_str长度大于单一资料的长度。

虽然有可能在服务端的socket缓冲区堆积多笔资料,但是workerman还是严格按照 preread_length + dealInput($recv_str) 的return值来从socket缓冲区截取数据,只有当前单笔数据处理完毕后,才会去socket缓冲区按照同样的规则截取下一笔数据资料。所以只要你的 preread_length 和 dealInput($recv_str) 的return值是正确的,$recv_str不会超过单笔资料的长度。

  • 暂无评论
flimulus

可能我設定錯誤,我再試試看。
謝謝您的回答。

  • 暂无评论
flimulus

我找到問題了,因為我是使用 workerman-chat-master 這個專案去修改。

而 Gateway.conf 的 preread_length 設定為 65535 所以才會出現多筆資料一次傳送,修改為正確值後

就正常的, 謝謝您的回應。

  • 暂无评论
walkor

好的,不客气。

  • 暂无评论