fsockopen()resource fsockopen(
string $hostname,
int $port,
int &$errno = null,
string &$errstr = null,
float $timeout = ini_get("default_socket_timeout")
)
$hostname:$port。udp://hostname;Unix Domain Socket 用路徑作 hostname,port=0。$timeout 設定連線逾時(秒),避免腳本卡死。GROUP 選擇新聞組、HEAD 取標頭、BODY 取內文、ARTICLE 取完整文章、QUIT 結束。<?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();
}
?>
$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);
stream_set_timeout($socket, 30) 防止讀取卡死。Content-Type 標頭 charset 並 mb_convert_encoding。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)而非手寫底層邏輯。