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

PHP Socket 程式設計實戰:連接 NNTP 新聞組伺服器抓取文章

     編輯:PHP 綜合知識

前言:PHP 網路程式設計基石

PHP 透過 fsockopen() / stream_socket_client() 可建立 TCP/UDP 連線,實作各類應用層協定客戶端。本文以 NNTP (Network News Transfer Protocol, RFC 977) 為例,示範連接新聞組伺服器、認證、選擇群組、抓取最新文章的完整流程。

核心函式:fsockopen()

resource fsockopen(
    string $hostname,
    int $port,
    int &$errno = null,
    string &$errstr = null,
    float $timeout = ini_get("default_socket_timeout")
)
  • $hostname:域名或 IP
  • $port:NNTP 標準埠 119(SSL 為 563)
  • $timeout:連線逾時秒數,防止腳本卡死
  • 回傳:檔案指標,可用 fgets()、fputs()、fclose() 操作

NNTP 協定關鍵指令

指令格式說明成功回應碼
連線問候—伺服器主動送出200 / 201
認證AUTHINFO USER <user>\r\nAUTHINFO PASS <pass>\r\n私有伺服器需求281
選擇群組GROUP <newsgroup>\r\n回傳文章數、首末編號211
取得文章標頭HEAD <msgid|number>\r\n僅標頭221
取得完整文章ARTICLE <msgid|number>\r\n標頭+內文220
列出群組LIST\r\n所有可用群組215
結束連線QUIT\r\n—205

完整實作範例:抓取最新 10 篇文章

<?php
// 設定
$cfg = [
    'host'     => 'news.example.com',
    'port'     => 119,
    'timeout'  => 10,
    'group'    => 'comp.lang.php',
    'user'     => null,   // 若需認證
    'pass'     => null,
    'limit'    => 10,     // 抓取筆數
];

// 1. 建立連線
$errno = $errstr = '';
$conn = @fsockopen($cfg['host'], $cfg['port'], $errno, $errstr, $cfg['timeout']);
if (!$conn) {
    die("連線失敗 [$errno]: $errstr\n");
}
stream_set_timeout($conn, $cfg['timeout']);
echo "已連線至 {$cfg['host']}:{$cfg['port']}\n";

// 2. 讀取問候訊息
$welcome = fgets($conn, 1024);
echo "伺服器: $welcome";
if (!preg_match('/^20[01]/', $welcome)) {
    die("伺服器拒絕連線\n");
}

// 3. 認證(若需)
if ($cfg['user']) {
    fwrite($conn, "AUTHINFO USER {$cfg['user']}\r\n");
    $resp = fgets($conn, 1024);
    if (!preg_match('/^381/', $resp)) die("帳號錯誤: $resp");

    fwrite($conn, "AUTHINFO PASS {$cfg['pass']}\r\n");
    $resp = fgets($conn, 1024);
    if (!preg_match('/^281/', $resp)) die("密碼錯誤: $resp");
    echo "認證成功\n";
}

// 4. 選擇新聞群組
fwrite($conn, "GROUP {$cfg['group']}\r\n");
$resp = fgets($conn, 1024);
echo "群組回應: $resp";
if (!preg_match('/^211\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)/', $resp, $m)) {
    die("選擇群組失敗\n");
}
list(, $count, $first, $last, $groupName) = $m;
echo "群組: $groupName, 文章數: $count, 號碼範圍: $first ~ $last\n";

// 5. 計算要抓取的文章編號(最新 N 篇)
$start = max($first, $last - $cfg['limit'] + 1);
$articles = [];

for ($num = $last; $num >= $start; $num--) {
    // 取得文章標頭(較輕量)
    fwrite($conn, "HEAD $num\r\n");
    $headResp = fgets($conn, 1024);
    if (!preg_match('/^221/', $headResp)) continue;

    $headers = '';
    while (($line = fgets($conn, 1024)) !== ".\r\n") {
        $headers .= $line;
    }

    // 解析關鍵標頭
    $subject = '';
    $from = '';
    $date = '';
    foreach (explode("\n", $headers) as $h) {
        if (preg_match('/^Subject:\s*(.*)/i', $h, $mm)) $subject = trim($mm[1]);
        elseif (preg_match('/^From:\s*(.*)/i', $h, $mm)) $from = trim($mm[1]);
        elseif (preg_match('/^Date:\s*(.*)/i', $h, $mm)) $date = trim($mm[1]);
    }

    $articles[] = [
        'number' => $num,
        'subject' => $subject,
        'from' => $from,
        'date' => $date,
    ];
}

// 6. 關閉連線
fwrite($conn, "QUIT\r\n");
fgets($conn, 1024);
fclose($conn);

// 7. 輸出結果
echo "\n=== 最新 {$cfg['limit']} 篇文章 ===\n";
foreach ($articles as $a) {
    echo "[{$a['number']}] {$a['subject']}\n";
    echo "    From: {$a['from']}\n";
    echo "    Date: {$a['date']}\n\n";
}
?>

進階主題與坑點

1. 多行回應處理

NNTP 多行回應(如 HEAD、ARTICLE、LIST)以單獨一行 .\r\n 結束。讀取迴圈必須檢測此終止條件,否則會阻塞。

2. 編碼問題

新聞組文章常使用 ISO-8859-1、UTF-8、Big5、GB2312 等編碼。標頭 Content-Type: text/plain; charset=... 指定編碼,需配合 mb_convert_encoding() 統一轉 UTF-8。

3. 大量文章效能

  • 使用 XOVER 指令(RFC 2980)一次取得範圍內所有文章概覽(主旨、發信者、日期、大小等),避免逐筆 HEAD 往返延遲。
  • 支援 PIPELINING(不等回應連續發送指令)可大幅降低 RTT 影響。

4. SSL/TLS 加密連線

$conn = fsockopen("ssl://news.example.com", 563, $errno, $errstr, 10);
// 或
$ctx = stream_context_create(['ssl' => ['verify_peer' => false]]);
$conn = stream_socket_client("ssl://news.example.com:563", $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $ctx);

5. 錯誤處理與重試

  • 網路不穩定時實作指數退避重試。
  • 設定 stream_set_timeout() 避免永久阻塞。
  • 記錄完整通訊日誌便於除錯。

現代替代方案

  • PEAR Net_NNTP:封裝完整 NNTP 客戶端,支援 XOVER、PIPELINING、SSL。
  • Zend_Mail_Storage_Nntp (ZF1) / laminas-mail:整合於郵件/新聞組庫。
  • Python newslib / Go gonnntp:若需高併發抓取,考慮用 Go/Rust 寫獨立服務,PHP 僅呼叫 API。

總結

PHP Socket 程式設計是理解網路協定運作的最佳切入點。透過 NNTP 實作,涵蓋了「連線建立 → 協定握手 → 指令/回應解析 → 多行資料讀取 → 異常處理」的完整客戶端生命週期。掌握此模式,即可輕鬆實作 SMTP 發信、POP3/IMAP 收信、IRC 機器人、HTTP 原始請求、自定義 TCP 協定等各類網路應用。

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