cpehub/php-telnet 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

cpehub/php-telnet

Composer 安装命令:

composer require cpehub/php-telnet

包简介

PHP Telnet Client

README 文档

README

A standards-compliant Telnet client for PHP.

phpcs phpstan phpmd phpunit

php-telnet speaks the Telnet protocol correctly: it escapes IAC per RFC 854/855, negotiates options with the RFC 1143 "Q Method" state machine, and answers the server's negotiation automatically while you wait for a prompt. It ships with a pluggable transport layer (raw sockets by default, or streams with optional TLS).

Upgrading from v1? The behaviour changed in a few important ways (most notably timelimit is now in milliseconds). See Upgrading from v1 to v2.

Requirements

  • PHP 8.2+
  • ext-sockets (used by the default transport)
  • psr/log (pulled in automatically by Composer)

Installation

composer require cpehub/php-telnet

Basic usage

use Cpehub\Telnet\Client;

// timelimit is in MILLISECONDS (5000 ms = 5 s).
$client = new Client($ip, 23, 5000);

$client->setPromptPattern('~\w+(>|#)$~'); // default await pattern
$client->login($login, $password);        // run the login procedure

$data = $client->sendMessage('show version'); // send a command, get the response

sendMessage() waits for the configured prompt pattern and returns the clean response text (Telnet protocol bytes are stripped for you).

Advanced usage

Build and exchange raw command sequences with CommandSequence:

use Cpehub\Telnet\Client;
use Cpehub\Telnet\Components\Command;
use Cpehub\Telnet\Components\CommandSequence;
use Cpehub\Telnet\Components\Option;

$client = new Client($ip, 23, 5000);
$client->setPromptPattern('~\w+(>|#)$~');

// Send a low-level command sequence.
$sequence = new CommandSequence();
$sequence
    ->addCommand(Command::DO, Option::SUPPRESS_GO_AHEAD)
    ->addCommand(Command::WILL, Option::ECHO)
    ->addOption(Option::TERMINAL_TYPE, 'xterm');
$client->sendSequence($sequence);

// Wait for a specific command sequence from the server.
$await = new CommandSequence();
$await->addCommand(Command::DONT, Option::X_DISPLAY_LOCATION);
$client->awaitSequence($await);

// Send text and wait for a prompt.
$command = new CommandSequence();
$command->addText('enable');
$client->sendSequence($command);

$result = $client->awaitPrompt('~Password:~'); // wait for 'Password:' and return the response

Note: CommandSequence::compile() doubles IAC (0xFF) bytes in text and subnegotiation parameters as required by RFC 854/855 — pass raw bytes and let the library escape them; do not double 0xFF yourself.

TLS ("telnets")

The default transport uses raw sockets. For an encrypted connection, inject a StreamTransport created via its tls() named constructor (port 992 by default):

use Cpehub\Telnet\Client;
use Cpehub\Telnet\Transport\StreamTransport;

$transport = StreamTransport::tls($ip, 992);

// The 5th constructor argument is the transport; pass null for logger to keep the default.
$client = new Client($ip, 992, 5000, null, $transport);
$client->setPromptPattern('~\w+(>|#)$~');
$client->login($login, $password);

Logging

Pass any PSR-3 LoggerInterface to the constructor, or set it later:

use Cpehub\Telnet\Client;

// Constructor: (ip, port, timelimit_ms, logger, ...)
$client = new Client($ip, 23, 5000, $logger);

// Or at any time:
$client->setLogger($logger);

Credentials are never logged: login() sends the password through a redacted path, so the log shows send: <redacted> instead of the raw bytes.

Exceptions

All exceptions extend Cpehub\Telnet\Exceptions\TelnetException:

  • Cpehub\Telnet\Exceptions\ConnectionException — connection/transport failures (invalid host/port, connect error, peer closed the connection).
  • Cpehub\Telnet\Exceptions\ProtocolException — malformed Telnet stream (e.g. a dangling IAC, a truncated negotiation or an unterminated subnegotiation).

Catching TelnetException still catches everything. Exception messages no longer embed buffer contents (a security hardening change); use the value returned by the send*/await* methods to read the response.

Class synopsis

Cpehub\Telnet\Client

use Psr\Log\LoggerInterface;
use Cpehub\Telnet\Components\CommandSequence;
use Cpehub\Telnet\Protocol\NegotiationPolicy;
use Cpehub\Telnet\Transport\TransportInterface;

class Client
{
    public const BYTE_READ = 4096;
    public const DEFAULT_MAX_BUFFER = 16 * 1024 * 1024; // 16 MiB

    /**
     * @param string $ip        telnet host
     * @param int    $port      telnet port
     * @param int    $timelimit max awaiting time, in MILLISECONDS (default 1000 = 1 s)
     * @param LoggerInterface|null    $logger    PSR-3 logger
     * @param TransportInterface|null $transport transport (default: SocketTransport)
     * @param NegotiationPolicy|null  $policy    option-negotiation policy
     * @param int    $maxBuffer max accumulated data buffer, in bytes
     */
    public function __construct(
        string $ip,
        int $port = 23,
        int $timelimit = 1000,
        ?LoggerInterface $logger = null,
        ?TransportInterface $transport = null,
        ?NegotiationPolicy $policy = null,
        int $maxBuffer = self::DEFAULT_MAX_BUFFER
    );

    /** Set the default await pattern used by all requests. */
    public function setPromptPattern(string $promptPattern): self;

    /** Set the PSR-3 logger. */
    public function setLogger(LoggerInterface $logger): self;

    /** Run the login procedure. $promptPattern defaults to the configured pattern. */
    public function login(string $login, string $password, ?string $promptPattern = null): CommandSequence;

    /** Send a command and return the clean response text. */
    public function sendMessage(string $message, ?string $promptPattern = null, ?int $timelimit = null): string;

    /** Send a command without waiting for a response. */
    public function sendLastMessage(string $message): void;

    /** Send a command sequence. Pass $sensitive = true to redact it from logs. */
    public function sendSequence(CommandSequence $sequence, bool $sensitive = false): void;

    /** Wait for a specific command sequence and return the response sequence. */
    public function awaitSequence(CommandSequence $sequence, ?int $timelimit = null): CommandSequence;

    /**
     * Wait for a prompt and return the response sequence.
     *
     * Throws TelnetException if no prompt pattern is configured/passed, or if the
     * pattern is not a valid regex.
     */
    public function awaitPrompt(?string $promptPattern = null, ?int $timelimit = null): CommandSequence;
}

Cpehub\Telnet\Components\CommandSequence

class CommandSequence
{
    /** @param string|null $input optional raw byte string to parse into a sequence */
    public function __construct(?string $input = null);

    /** Add a command (optionally with an option byte). */
    public function addCommand(int $command, ?int $option = null): self;

    /** Add text/data. Accepts strings and int byte values (e.g. Printer::CR). */
    public function addText(int|string ...$parts): self;

    /** Add a subnegotiation option with its data. */
    public function addOption(int $option, string $data): self;

    /** Get all text parts from the sequence. */
    public function getText(): string;

    /** Human-readable dump of the sequence. */
    public function dump(): string;

    /** Compile the sequence to a byte string (doubles IAC in data and SB params). */
    public function compile(): string;
}

Cpehub\Telnet\Components\Command constants

Command {
    public const SE                = 0xF0;
    public const NOP               = 0xF1;
    public const DATA_MARK         = 0xF2;
    public const BREAK             = 0xF3;
    public const INTERRUPT_PROCESS = 0xF4;
    public const ABORT_OUTPUT      = 0xF5; // was ABOUT_OUTPUT in v1 (still available as a deprecated alias)
    public const ARE_YOU_THERE     = 0xF6;
    public const ERASE_CHARACTER   = 0xF7;
    public const ERASE_LINE        = 0xF8;
    public const GO_AHEAD          = 0xF9;
    public const SB                = 0xFA;
    public const WILL              = 0xFB;
    public const WONT              = 0xFC;
    public const DO                = 0xFD;
    public const DONT              = 0xFE;
    public const IAC               = 0xFF;
}

See https://www.rfc-editor.org/rfc/rfc854 for details.

Cpehub\Telnet\Components\Option constants

Option {
    public const TRANSMIT_BINARY     = 0x00; // https://www.rfc-editor.org/rfc/rfc856
    public const ECHO                = 0x01; // https://www.rfc-editor.org/rfc/rfc857
    public const SUPPRESS_GO_AHEAD   = 0x03; // https://www.rfc-editor.org/rfc/rfc858
    public const STATUS              = 0x05; // https://www.rfc-editor.org/rfc/rfc859
    public const END_OF_RECORD       = 0x19; // https://www.rfc-editor.org/rfc/rfc885
    public const TERMINAL_TYPE       = 0x18; // https://www.rfc-editor.org/rfc/rfc1091
    public const WINDOW_SIZE         = 0x1f; // https://www.rfc-editor.org/rfc/rfc1073
    public const TERMINAL_SPEED      = 0x20; // https://www.rfc-editor.org/rfc/rfc1079
    public const REMOTE_FLOW_CONTROL = 0x21; // https://www.rfc-editor.org/rfc/rfc1372
    public const TERMINAL_LINEMODE   = 0x22; // https://www.rfc-editor.org/rfc/rfc1184
    public const X_DISPLAY_LOCATION  = 0x23; // https://www.rfc-editor.org/rfc/rfc1096
    public const ENVIRONMENT         = 0x27; // https://www.rfc-editor.org/rfc/rfc1572
}

Cpehub\Telnet\Components\Printer constants

Printer {
    public const NL   = 0x00; // \0
    public const LF   = 0x0A; // \n
    public const CR   = 0x0D; // \r
    public const BELL = 0x07;
    public const BS   = 0x08;
    public const HT   = 0x09;
    public const VT   = 0x0B;
    public const FF   = 0x0C;
}

Upgrading from v1 to v2

v2.0.0 is the first substantial release since v1.0.2. Public method signatures are preserved, but three behavioural contracts changed. Read this before you composer update.

# Change v1.0.2 v2.0.0 What to do
A timelimit unit seconds milliseconds Multiply your old second values by 1000. new Client($ip, 23, 5) meant 5 s in v1; in v2 it means 5 ms — use 5000. The default is unchanged in effect (1 s → 1000 ms). Applies to the constructor and the per-call $timelimit in sendMessage()/awaitSequence()/awaitPrompt().
B CommandSequence::compile() escapes IAC raw 0xFF passed through 0xFF doubled in data and SB params (RFC 854/855) Stop doubling 0xFF yourself; pass raw bytes and let the library escape them.
C login() rewritten fixed WILL/DO burst + canned reply; waited for the literal string 'Password:' negotiation handled automatically by the read loop; password prompt matched with ~password[: ]*$~i (case-insensitive, anchored) Usually nothing. If you relied on the old negotiation choreography, review it. Make sure a prompt pattern is configured (see D).
D awaitPrompt() validates the pattern passed empty/null to preg_match and looped throws TelnetException when no pattern is configured/passed, or when the regex is invalid Call setPromptPattern('...') or pass a pattern explicitly.
E New Composer requirements only php: ^8.2 adds ext-sockets and psr/log Ensure ext-sockets is enabled. psr/log is pulled in automatically (in v1 it was missing and the autoload could fatal).
F setLogger() fixed infinite recursion (stack overflow on first call) assigns the logger Nothing — it just works now.
G Constant renamed Command::ABOUT_OUTPUT Command::ABORT_OUTPUT (old name kept as a deprecated alias, same value 0xF5) Migrate to Command::ABORT_OUTPUT.
H No buffer in errors/logs exception messages/logs embedded the raw buffer messages omit buffer contents (security hardening) If you parsed exception text to recover the response body, use the value returned by the send*/await* methods instead.

Additive (non-breaking) improvements you can adopt:

  • sendSequence(CommandSequence $sequence, bool $sensitive = false) — pass true to redact a sequence from logs.
  • New options Option::TRANSMIT_BINARY (0x00) and Option::END_OF_RECORD (0x19).
  • Typed exceptions ConnectionException and ProtocolException (both extend TelnetException).
  • Optional TLS via StreamTransport::tls() (see TLS).
  • New optional constructor arguments: $transport, $policy, $maxBuffer.

Before / after

// v1.0.2 — timelimit in SECONDS, misspelled method
$client = new Client($ip, 23, 5);
$client->setPromtPattern('~\w+(>|#)$~');
$client->login($login, $password);
$data = $client->sendMessage('show version');

// v2.0.0 — timelimit in MILLISECONDS, corrected method name
$client = new Client($ip, 23, 5000);
$client->setPromptPattern('~\w+(>|#)$~');
$client->login($login, $password);
$data = $client->sendMessage('show version');

cpehub/php-telnet 适用场景与选型建议

cpehub/php-telnet 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 160 次下载、GitHub Stars 达 0, 最近一次更新时间为 2025 年 01 月 07 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 cpehub/php-telnet 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 cpehub/php-telnet 我们能提供哪些服务?
定制开发 / 二次开发

基于 cpehub/php-telnet 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 160
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 2
  • 点击次数: 4
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 2
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-01-07