首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 實作飛信 (Fetion) 協議客戶端:Socket 通訊與狀態機設計

     編輯:PHP 相關知識

專案背景與定位

基於飛信 (Fetion) 通訊協議的逆向工程實作,純 PHP Socket 客戶端,支援登入、好友列表、發送/接收訊息、狀態維持。適用於伺服器端自動化通知、機器人開發、整合現有系統。

協議核心特徵

  • 基於 TCP 長連線:登入後保持連線,心跳維持
  • 二進制封包:自訂封包頭 (長度、命令碼、序號) + XML/JSON 載荷
  • 狀態機驅動:連線 → 挑戰 → 認證 → 就緒 → 通訊 → 重連
  • 加密傳輸:關鍵階段使用 RSA/AES 加密

核心類架構設計

<?php
namespace Fetion;

class Client {
    private $socket;
    private $config = [
        'host' => 'fetu.fetion.com.cn',
        'port' => 5222,
        'timeout' => 30,
        'heartbeat_interval' => 60
    ];
    private $state = 'DISCONNECTED'; // DISCONNECTED, CONNECTING, CHALLENGE, AUTH, READY
    private $sid = 1; // 序號生成器
    private $buffer = '';
    private $callbacks = [];
    private $user = [];
    
    public function __construct(array $config = []) {
        $this->config = array_merge($this->config, $config);
    }
    
    // 連線並啟動事件循環
    public function connect($mobile, $password) {
        $this->socket = stream_socket_client(
            "tcp://{$this->config['host']}:{$this->config['port']}",
            $errno, $errstr, $this->config['timeout'],
            STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT
        );
        if (!$this->socket) throw new \Exception("連線失敗: $errstr");
        
        stream_set_blocking($this->socket, 0); // 非阻塞
        stream_set_timeout($this->socket, $this->config['timeout']);
        
        $this->state = 'CONNECTING';
        $this->sendPacket('SIP', ['M' => 'REGISTER', 'U' => $mobile]);
        $this->eventLoop();
    }
    
    // 非阻塞事件循環
    private function eventLoop() {
        $read = [$this->socket];
        $write = $this->hasPendingPackets() ? [$this->socket] : [];
        $except = [$this->socket];
        
        while ($this->state !== 'DISCONNECTED') {
            $r = $read; $w = $write; $e = $except;
            $n = @stream_select($r, $w, $e, 1, 0); // 1秒輪詢
            
            if ($n === false) break; // 錯誤
            
            if (in_array($this->socket, $r)) $this->handleRead();
            if (in_array($this->socket, $w)) $this->handleWrite();
            if (in_array($this->socket, $e)) $this->handleError();
            
            $this->checkHeartbeat();
        }
    }
    
    // 封包發送 (長度前綴 + XML)
    private function sendPacket($cmd, $data) {
        $seq = $this->sid++;
        $xml = $this->buildXml($cmd, $data, $seq);
        $len = strlen($xml);
        $packet = pack('N', $len) . $xml; // 4字節大端長度
        
        // 非阻塞寫入佇列
        $this->writeBuffer .= $packet;
    }
    
    // 讀取處理 (處理粘包/拆包)
    private function handleRead() {
        $data = fread($this->socket, 8192);
        if ($data === '' || $data === false) {
            $this->disconnect('連線關閉');
            return;
        }
        $this->buffer .= $data;
        $this->parsePackets();
    }
    
    // 封包解析
    private function parsePackets() {
        while (strlen($this->buffer) >= 4) {
            $len = unpack('N', substr($this->buffer, 0, 4))[1];
            if (strlen($this->buffer) < 4 + $len) break; // 不完整封包
            
            $xml = substr($this->buffer, 4, $len);
            $this->buffer = substr($this->buffer, 4 + $len);
            $this->handlePacket($xml);
        }
    }
    
    // 狀態機處理
    private function handlePacket($xml) {
        $packet = simplexml_load_string($xml);
        $cmd = (string)$packet['M'];
        $seq = (int)$packet['S'];
        
        switch ($this->state) {
            case 'CONNECTING':
                if ($cmd === 'SIP/2.0 401 Unauthorized') {
                    $this->state = 'CHALLENGE';
                    $this->handleChallenge($packet);
                }
                break;
            case 'CHALLENGE':
                if ($cmd === 'SIP/2.0 200 OK') {
                    $this->state = 'AUTH';
                    $this->doAuth();
                }
                break;
            case 'AUTH':
                if ($cmd === 'SIP/2.0 200 OK') {
                    $this->state = 'READY';
                    $this->onReady();
                }
                break;
            case 'READY':
                $this->dispatchEvent($cmd, $packet);
                break;
        }
    }
    
    // 事件回調註冊
    public function on($event, callable $callback) {
        $this->callbacks[$event][] = $callback;
    }
    
    private function dispatchEvent($cmd, $packet) {
        foreach ($this->callbacks[$cmd] ?? [] as $cb) {
            $cb($packet, $this);
        }
    }
    
    // 公開 API
    public function sendMessage($to, $content) {
        if ($this->state !== 'READY') throw new \Exception('未就緒');
        $this->sendPacket('SIP', [
            'M' => 'MESSAGE',
            'T' => $to,
            'C' => base64_encode($content)
        ]);
    }
}

// 使用範例
$client = new Fetion\Client();
$client->on('MESSAGE', function($packet, $client) {
    $from = (string)$packet['F'];
    $content = base64_decode((string)$packet['C']);
    echo "收到來自 $from: $content\n";
    
    // 自動回覆
    if (strpos($content, '你好') !== false) {
        $client->sendMessage($from, '你好!我是 PHP 飛信機器人。');
    }
});

try {
    $client->connect('13800138000', 'password123');
} catch (Exception $e) {
    echo "錯誤: " . $e->getMessage();
}

關鍵技術難點與解決

難點解決方案
非阻塞 I/O 處理stream_select() 多路複用 + 讀寫緩衝區管理
粘包/拆包長度前綴協議 + 緩衝區累積解析
長連線維持定時心跳封包 + 讀取逾時檢測 + 自動重連機制
加密握手RSA 公鑰加密密碼 + AES 會話金鑰衍生 (需 OpenSSL 擴展)
協議變更適應模組化封包建構/解析,版本適配器模式

生產環境部署建議

  • 使用 Swoole/Workerman 替代原生 Socket,獲得更高併發與穩定性
  • 狀態持久化到 Redis,支援多進程/分散式部署
  • 敏感資訊 (密碼、Session Key) 加密儲存
  • 完善日誌記錄:封包收發、狀態變更、錯誤堆疊
  • 實作優雅關閉:SIGTERM 處理、待發送佇列清空、連線正常斷開

法律與道德提醒

本實作僅供學習協議設計、Socket 程式設計、狀態機模式等技術研究。實際使用飛信服務請遵守騰訊服務條款,勿用於垃圾訊息、騷擾、帳號盜取等違法行為。官方 API 應優先選擇。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved