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

PHP Socket 程式設計實戰 (三):讀取新聞組文章、關閉連線與應用拓展

     編輯:PHP 綜合知識

承接前文:讀取文章內容

已成功選擇新聞組並取得最新文章編號 $last。本文示範如何批次讀取最後 10 篇文章,處理多行回應、錯誤碼判斷,並正確關閉連線。

批次讀取文章核心邏輯

<?php
$cfgLimit = 10;                    // 讀取筆數
$boucle = $last - $cfgLimit;       // 起始編號

while ($boucle <= $last) {
    set_time_limit(0);             // 防止長時間腳本中斷
    
    // 發送 ARTICLE 指令 (支援編號或 Message-ID)
    fputs($usenet_handle, "ARTICLE $boucle\r\n");
    
    $article = '';
    $tmp = fgets($usenet_handle, 4096);
    
    // 檢查回應碼:220 表示文章內容跟隨
    if (substr($tmp, 0, 3) !== '220') {
        echo "+----------------------+\n";
        echo "Error on article $boucle: $tmp";
        echo "+----------------------+\n";
    } else {
        // 讀取多行文章內容,以單獨一行 "." 結束 (.\r\n)
        while ($tmp !== ".\r\n") {
            $tmp = fgets($usenet_handle, 4096);
            $article .= $tmp;
        }
        
        echo "+----------------------+\n";
        echo "Article $boucle\n";
        echo "+----------------------+\n";
        echo $article;
    }
    $boucle++;
}
?>

關鍵協議細節:文章讀取終止條件

NNTP 規定多行回應 (文章內容、HEAD、BODY) 以 單獨一行包含句點 . (即 .\r\n) 結束。讀取迴圈必須精確比對此終止標記,否則會漏讀或多讀。

注意: 文章內容本身可能包含單獨一行 . 的行 (如程式碼片段)。NNTP 規定發送端需在每行開頭為 . 的行前加上額外 . (即 ..),接收端讀取時需移除這個轉義點。上述簡化程式碼未處理此轉義,實際應用需實現 dot-stuffing 解碼。

替代指令:HEAD 與 BODY

  • HEAD <id>:僅回應文章標頭 (From, Subject, Date, Message-ID, References 等),適合建立索引、列表預覽。
  • BODY <id>:僅回應文章本文,不含標頭。
  • ARTICLE <id> = HEAD + 空行 + BODY。

根據需求選擇指令可大幅減少頻寬與處理時間。

關閉連線

<?php
// 發送 QUIT 指令 (禮貌性關閉)
fputs($usenet_handle, "QUIT\r\n");
$bye = fgets($usenet_handle, 1024); // 讀取 205 回應
echo "Server: $bye";

// 關閉 Socket 資源
fclose($usenet_handle);
?>

最佳實踐: 即使伺服器異常斷線,fclose() 也能確保本地檔案描述符釋放。生產環境建議包裝在 try/finally 或 register_shutdown_function 中保證執行。

應用拓展:從「讀取」到「新聞組搜尋引擎」

將下載的文章持久化並建立全文索引,即可構建 Web 版新聞組搜尋引擎:

  1. 儲存結構: MySQL/PostgreSQL 表 articles (msgid, newsgroup, subject, from_addr, date, headers, body, fetched_at),msgid 唯一索引。
  2. 去重: 以 Message-ID 為主鍵判斷是否已抓取。
  3. 全文索引: MySQL FULLTEXT(subject, body)、PostgreSQL tsvector、或專用引擎 Elasticsearch, Meilisearch, Typesense。
  4. 定時抓取: Cron / Systemd Timer / Laravel Scheduler 每小時執行抓取腳本,僅下載新文章 (記錄 $last 至檔案/資料庫)。
  5. Web 介面: 關鍵字搜尋、分類瀏覽、執行緒檢視 (依 References 串聯)、RSS/Atom 輸出。

現有開源專案參考

  • phpBB / FluxBB:論壇軟體,含 NNTP 閘道功能。
  • Leafnode:離線新聞組讀取代理 (C 語言),可作為本地 NNTP 伺服器供 PHP 連接。
  • NewsPortal / PHPNews:早期 PHP 新聞組 Web 介面。
  • ht://Dig / SWISH-E:經典全文索引工具,可整合至 PHP。

現代化架構建議

層面傳統 Socket 腳本現代化方案
執行模式CLI 腳本、阻塞 I/O、單執行緒Swoole / OpenSwoole / RoadRunner 常駐記憶體、協程非阻塞、多 Worker 並發
協議處理手工拼裝/解析指令使用成熟函式庫 (如 php-nntp/php-nntp, symfony/mailer 等)
資料儲存直接輸出/簡單 INSERTDoctrine ORM / Eloquent + Migration,事件溯源記錄抓取狀態
索引搜尋MySQL LIKE / 簡易 FULLTEXTElasticsearch + IK 分詞,向量搜尋支援語義查詢
監控告警無Prometheus + Grafana 監控抓取延遲、錯誤率、佇列積壓
部署Cron + 腳本Kubernetes CronJob / Sidecar,健康檢查、滾動更新

完整腳本整合 (含錯誤處理、日誌、配置)

<?php
// fetch_nntp.php
require_once 'NntpClient.php'; // 使用上篇封裝的類別

$config = [
    'host' => env('NNTP_HOST', 'news.example.com'),
    'port' => (int)env('NNTP_PORT', 119),
    'user' => env('NNTP_USER'),
    'pass' => env('NNTP_PASS'),
    'groups' => explode(',', env('NNTP_GROUPS', 'php.general,php.dev')),
    'limit_per_group' => 50,
    'state_file' => __DIR__ . '/var/last_ids.json', // 記錄各群組最後抓取 ID
];

$client = new NntpClient($config['host'], $config['port']);
$client->connect();
if ($config['user']) $client->auth($config['user'], $config['pass']);

$state = json_decode(file_get_contents($config['state_file']), true) ?: [];

foreach ($config['groups'] as $group) {
    $info = $client->group($group);
    if (!$info) continue;
    
    $lastFetched = $state[$group] ?? ($info['last'] - $config['limit_per_group']);
    $start = max($info['first'], $lastFetched + 1);
    $end = $info['last'];
    
    for ($id = $start; $id <= $end; $id++) {
        $article = $client->article($id);
        if ($article) {
            // 解析標頭、儲存資料庫、送索引佇列...
            save_article($group, $id, $article);
        }
    }
    $state[$group] = $end;
}

$client->quit();
file_put_contents($config['state_file'], json_encode($state));
echo "抓取完成 " . date('Y-m-d H:i:s') . "\n";
?>

結語

本系列三篇文章完整演示了 PHP Socket 程式設計從建立連線、協議對話、資料讀取到資源釋放的全生命週期,並以 NNTP 為例展示了實際應用層協議的處理細節。雖然現代 Web 開發多基於 HTTP/REST/gRPC,但 Socket 程式設計能力對於理解網路底層、整合舊系統、開發高效能網路服務 (如即時通訊、遊戲伺服器、物聯網閘道) 至關重要。建議開發者在掌握阻塞 I/O 基礎後,進階學習 I/O 多路複用 (stream_select, libevent, Swoole 協程)、TLS/SSL 加密通訊 (stream_socket_enable_crypto)、協議狀態機設計,構建健壯、高併發的網路應用。

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