萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

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

核心函數:fsockopen()

resource fsockopen(
    string $hostname,
    int $port,
    int &$errno = null,
    string &$errstr = null,
    float $timeout = ini_get("default_socket_timeout")
)
  • 建立 TCP 連線至 $hostname:$port。
  • UDP 協議請用 udp://hostname;Unix Domain Socket 用路徑作 hostname,port=0。
  • $timeout 設定連線逾時(秒),避免腳本卡死。

NNTP 協議基礎 (RFC 977)

  • 連接埠:119(標準)、563 (NNTPS/SSL)。
  • 文字指令回應模式:客戶端發送指令 → 伺服器回應代碼 + 文字。
  • 常用指令:GROUP 選擇新聞組、HEAD 取標頭、BODY 取內文、ARTICLE 取完整文章、QUIT 結束。

完整實作範例:下載指定新聞組最新 10 篇文章標題

<?php
/**
 * NNTP 客戶端簡易實作
 */
class NntpClient
{
    private $socket;
    private $timeout = 10;
    
    public function connect(string $host, int $port = 119): bool
    {
        $this->socket = @fsockopen($host, $port, $errno, $errstr, $this->timeout);
        if (!$this->socket) {
            throw new RuntimeException("連線失敗 ($errno): $errstr");
        }
        // 讀取歡迎訊息 (200/201)
        return $this->readResponse(200, 201);
    }
    
    public function selectGroup(string $group): array
    {
        $this->write("GROUP $group");
        // 回應: 211 n f l s (文章數、第一篇編號、最後篇編號、組名)
        if (!$this->readResponse(211)) return [];
        $parts = explode(' ', trim($this->lastResponse));
        return [
            'count'   => (int)$parts[1],
            'first'   => (int)$parts[2],
            'last'    => (int)$parts[3],
            'group'   => $parts[4],
        ];
    }
    
    public function getHeaders(int $first, int $last, int $limit = 10): array
    {
        $headers = [];
        for ($num = $last; $num >= $first && count($headers) < $limit; $num--) {
            $this->write("HEAD $num");
            if ($this->readResponse(221)) {
                $headers[] = $this->readMultiline();
            }
        }
        return $headers;
    }
    
    public function quit(): void
    {
        if ($this->socket) {
            $this->write('QUIT');
            $this->readResponse(205);
            fclose($this->socket);
            $this->socket = null;
        }
    }
    
    private function write(string $cmd): void
    {
        fwrite($this->socket, $cmd . "\r\n");
    }
    
    private function readResponse(int ...$expectedCodes): bool
    {
        $line = fgets($this->socket, 4096);
        $this->lastResponse = $line;
        $code = (int)substr($line, 0, 3);
        return in_array($code, $expectedCodes);
    }
    
    private function readMultiline(): string
    {
        $data = '';
        while (($line = fgets($this->socket, 4096)) !== false) {
            if (trim($line) === '.') break; // 單獨一個點結束
            $data .= $line;
        }
        return $data;
    }
    
    private $lastResponse = '';
}

// 使用範例
try {
    $client = new NntpClient();
    $client->connect('news.example.com', 119);
    
    $info = $client->selectGroup('comp.lang.php');
    echo "文章數: {$info['count']}, 範圍: {$info['first']}-{$info['last']}
"; $headers = $client->getHeaders($info['first'], $info['last'], 10); foreach ($headers as $i => $h) { echo "

文章 " . ($i+1) . "

"; echo "
" . htmlspecialchars($h) . "
"; } $client->quit(); } catch (Exception $e) { echo "錯誤: " . $e->getMessage(); } ?>

進階:SSL/TLS 連線 (NNTPS)

$socket = fsockopen('ssl://news.example.com', 563, $errno, $errstr, 15);
// 或使用 stream_socket_client 更精細控制 SSL 內容
$ctx = stream_context_create(['ssl' => ['verify_peer' => false]]); // 測試用
$socket = stream_socket_client('ssl://news.example.com:563', $errno, $errstr, 15, STREAM_CLIENT_CONNECT, $ctx);

錯誤處理與健壯性建議

  1. 設定逾時:stream_set_timeout($socket, 30) 防止讀取卡死。
  2. 斷線重連:封裝重試邏輯(指數退避)。
  3. 編碼處理:NNTP 內容可能為 ISO-8859-1、UTF-8、Big5 等,需檢測 Content-Type 標頭 charset 並 mb_convert_encoding。
  4. 大量下載:使用 XOVER 指令 (RFC 2980) 批次取得標頭,比逐個 HEAD 高效得多。

結語

PHP Socket 程式設計讓你能直接操作 TCP/UDP 協議,實作各類客戶端(NNTP, POP3, SMTP, IRC, 自訂協議)。掌握 fsockopen/stream_socket_client、非阻塞 I/O (stream_set_blocking)、SSL/TLS 內容建立、協議解析邏輯,是網路程式設計的核心能力。生產環境建議使用成熟庫(如 react/socket 非阻塞、symfony/mailer SMTP)而非手寫底層邏輯。

copyright © 萬盛學電腦網 all rights reserved