Apache Commons Net 3.13.0 快速入门与集成示例

以 SubnetUtils 最小验证为起点,逐个实测 FTPClient、SMTPClient、TelnetClient 与真实服务的联动,给出 Apache Commons Net 3.13.0 的快速上手与集成路径

Apache Commons NetApache Commons Net2026/10/15

Apache Commons Net 3.13.0 快速入门与集成示例

目标:10 分钟跑通第一个 Commons Net 程序,再依次与真实 FTP 服务器、SMTP 服务、Telnet 服务完成端到端联动。已在 Ubuntu 24.04.5 LTS(x86_64)+ OpenJDK 17.0.20.1 + Maven 3.9.16 实测通过。

一、环境信息

  • OS:Ubuntu 24.04.5 LTS
  • 架构:x86_64
  • JDK:OpenJDK 17.0.20.1
  • 构建工具:Apache Maven 3.9.16
  • 组件版本:Apache Commons Net 3.13.0(传递依赖 commons-io 2.21.0)
  • Maven 坐标:commons-net:commons-net:3.13.0
  • 联动服务:vsftpd 3.0.5-0ubuntu3.1、python3 3.12.3 自研 SMTP sink、netcat-openbsd 1.226
  • 工作目录:/root/commons-net-demo

本文实验机同时充当三个服务的宿主:FTP 服务器(vsftpd,127.0.0.1:21)、SMTP 演示服务(127.0.0.1:2525)、Telnet 演示服务(127.0.0.1:2323),客户端与服务端同机直连,回显均来自真实网络交互。

二、10 分钟最小可用

创建工程目录与 POM:

mkdir -p /root/commons-net-demo/src/main/java
cd /root/commons-net-demo
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>demo</groupId>
  <artifactId>commons-net-demo</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>8</maven.compiler.release>
  </properties>

  <dependencies>
    <dependency>
      <groupId>commons-net</groupId>
      <artifactId>commons-net</artifactId>
      <version>3.13.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version>
      </plugin>
    </plugins>
  </build>
</project>

第一个程序不碰网络服务,用工具类 SubnetUtils 解析 CIDR 网段——这是 Commons Net 里除协议客户端外最常用的一族工具:

import org.apache.commons.net.util.SubnetUtils;
import org.apache.commons.net.util.SubnetUtils.SubnetInfo;

public class SubnetDemo {
    public static void main(String[] args) {
        SubnetUtils su = new SubnetUtils("192.168.56.0/26");
        SubnetInfo info = su.getInfo();
        System.out.println("[不含网络号/广播号] low=" + info.getLowAddress()
                + " high=" + info.getHighAddress()
                + " 可用地址数=" + info.getAddressCountLong());
        su.setInclusiveHostCount(true);
        info = su.getInfo();
        System.out.println("[含网络号/广播号]   low=" + info.getLowAddress()
                + " high=" + info.getHighAddress()
                + " 可用地址数=" + info.getAddressCountLong());
        System.out.println("network=" + info.getNetworkAddress()
                + " netmask=" + info.getNetmask()
                + " broadcast=" + info.getBroadcastAddress());
        System.out.println("isInRange(192.168.56.100) = " + info.isInRange("192.168.56.100"));
        System.out.println("isInRange(192.168.57.100) = " + info.isInRange("192.168.57.100"));
        System.out.println("commons-net OK");
    }
}

编译并查看依赖树:

mvn -B compile
mvn -q dependency:tree -DoutputFile=deptree.txt
cat deptree.txt

实测回显(工作区里还有本系列后续章节要用的另外几个演示类,共 10 个源文件):

[INFO] Compiling 10 source files with javac [debug release 8] to target/classes
[INFO] BUILD SUCCESS
[INFO] Total time:  1.256 s
demo:commons-net-demo:jar:1.0.0
\- commons-net:commons-net:jar:3.13.0:compile
   \- commons-io:commons-io:jar:2.21.0:compile

依赖树证实两点:Commons Net 3.13.0 无第三方传递依赖之外的负担;commons-io 2.21.0 是它的正式传递依赖(FTP 传输路径实测必用,缺了会 NoClassDefFoundError,见第六节)。

运行:

java -Dfile.encoding=UTF-8 -cp target/classes:$HOME/.m2/repository/commons-net/commons-net/3.13.0/commons-net-3.13.0.jar:$HOME/.m2/repository/commons-io/commons-io/2.21.0/commons-io-2.21.0.jar SubnetDemo

实测回显:

[不含网络号/广播号] low=192.168.56.1 high=192.168.56.62 可用地址数=62
[含网络号/广播号]   low=192.168.56.0 high=192.168.56.63 可用地址数=64
network=192.168.56.0 netmask=255.255.255.192 broadcast=192.168.56.63
isInRange(192.168.56.100) = false
isInRange(192.168.57.100) = false
commons-net OK

验证结果:192.168.56.100 属于 64127 段,所以对 /26(063)判定为 false,行为正确。到这里 Commons Net 已经可用;下面三节把它接入真实网络服务。

三、与 FTP 服务器联动实测

3.1 准备 vsftpd 服务

apt-get install -y vsftpd

输出节选(实测):

vsftpd is already the newest version (3.0.5-0ubuntu3.1).

创建测试用户(密码 q1w2e3r4)并允许其作为 FTP 登录用户:

echo /usr/sbin/nologin >> /etc/shells
useradd -m -s /usr/sbin/nologin ftpuser
echo "ftpuser:q1w2e3r4" | chpasswd
id ftpuser

实测回显:

uid=1001(ftpuser) gid=1001(ftpuser) groups=1001(ftpuser)

写入 /etc/vsftpd.conf(本地用户可登录、可写、chroot 到家目录、固定被动模式端口段):

listen=YES
listen_ipv6=NO
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=NO
use_localtime=YES
xferlog_enable=NO
connect_from_port_20=YES
chroot_local_user=YES
allow_writeable_chroot=YES
secure_chroot_dir=/var/run/vsftpd/empty
pam_service_name=vsftpd
seccomp_sandbox=NO
pasv_enable=YES
pasv_min_port=40000
pasv_max_port=40100

启动并验证服务端横幅:

mkdir -p /var/run/vsftpd/empty
nohup /usr/sbin/vsftpd /etc/vsftpd.conf >/var/log/vsftpd-demo.log 2>&1 &
curl -s telnet://127.0.0.1:21 --max-time 3 | head -1

实测回显:

220 (vsFTPd 3.0.5)

3.2 FTPClient 登录、上传、列目录、下载

准备待上传文件与演示程序:

printf "Hello Apache Commons Net FTPClient\n" > /root/commons-net-demo/hello-ftp.txt
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class FtpDemo {
    public static void main(String[] args) throws Exception {
        FTPClient ftp = new FTPClient();
        ftp.connect("127.0.0.1", 21);
        System.out.println("连接回显: " + ftp.getReplyString().trim());
        if (!ftp.login("ftpuser", "q1w2e3r4")) {
            throw new IllegalStateException("登录失败: " + ftp.getReplyString());
        }
        System.out.println("登录成功, 当前目录=" + ftp.printWorkingDirectory()
                + " 服务端类型=" + ftp.getSystemType());
        ftp.enterLocalPassiveMode();
        ftp.setFileType(FTP.BINARY_FILE_TYPE);

        File local = new File("/root/commons-net-demo/hello-ftp.txt");
        try (InputStream in = new FileInputStream(local)) {
            System.out.println("storeFile = " + ftp.storeFile("hello-ftp.txt", in)
                    + " 回复码=" + ftp.getReplyCode());
        }
        System.out.println("listNames 目录列表:");
        for (String name : ftp.listNames()) {
            System.out.println("  " + name);
        }
        File down = new File("/root/commons-net-demo/hello-ftp-download.txt");
        try (OutputStream out = new FileOutputStream(down)) {
            System.out.println("retrieveFile = " + ftp.retrieveFile("hello-ftp.txt", out));
        }
        System.out.println("下载文件字节数=" + down.length()
                + " 与上传一致=" + (down.length() == local.length()));
        ftp.logout();
        ftp.disconnect();
        System.out.println("断开后 isConnected = " + ftp.isConnected());
    }
}
java -Dfile.encoding=UTF-8 -cp target/classes:$HOME/.m2/repository/commons-net/commons-net/3.13.0/commons-net-3.13.0.jar:$HOME/.m2/repository/commons-io/commons-io/2.21.0/commons-io-2.21.0.jar FtpDemo

实测回显:

连接回显: 220 (vsFTPd 3.0.5)
登录成功, 当前目录=/ 服务端类型=UNIX Type: L8
storeFile = true 回复码=226
listNames 目录列表:
  hello-ftp.txt
retrieveFile = true
下载文件字节数=35 与上传一致=true
断开后 isConnected = false

验证结果:登录后 printWorkingDirectory() 返回 /(vsftpd 把用户 chroot 到了家目录 /home/ftpuser);storeFile/retrieveFile 均返回 true 且回复码 226(传输完成);下载文件与上传文件字节数一致。服务端落盘复核:

ls -l /home/ftpuser/

实测回显:

total 4
-rw-r--r-- 1 ftpuser ftpuser 35 Oct 15 01:55 hello-ftp.txt

💡 技术拓展:FTP 每次文件传输都要另开一条数据连接,分主动与被动两种建立方向:

维度主动模式(PORT)被动模式(PASV/EPSV)
数据连接由谁发起服务端连客户端客户端连服务端
客户端环境要求必须可被服务端直连(公网 IP、开放端口)无
服务端环境要求无需开放被动端口段
典型问题客户端在内网/NAT 后必失败服务端漏放行端口段
Commons Net APIenterLocalActiveMode()enterLocalPassiveMode()

本文客户端与 vsftpd 同机部署,两种模式都能通;演示程序与生产实践默认采用被动模式,主动模式的实测对比见《常用 API 与网络客户端用法详解》。

四、与 SMTP 服务联动实测

4.1 准备本地 SMTP sink

sink 是只收不发、把对话打印到日志的"黑洞"服务,用 60 行左右的标准库 Python 实现(无第三方依赖),保存为 /root/smtp_sink.py:

#!/usr/bin/env python3
# SMTP sink:本地收信黑洞,打印与客户端的完整对话
import base64
import socket

HOST, PORT = "127.0.0.1", 2525


def reply(conn, line):
    conn.sendall((line + "\r\n").encode())


def handle(conn, addr):
    print("connection from %s:%d" % addr, flush=True)
    rd = conn.makefile("rb")
    reply(conn, "220 net-demo SMTP sink ready")
    while True:
        line = rd.readline()
        if not line:
            break
        text = line.decode("utf-8", "replace").rstrip("\r\n")
        print("C: %s" % text, flush=True)
        u = text.upper()
        if u.startswith("HELO"):
            reply(conn, "250 sink greets %s" % text.split(" ", 1)[1])
        elif u.startswith("EHLO"):
            reply(conn, "250-sink greets %s" % text.split(" ", 1)[1])
            reply(conn, "250-AUTH LOGIN PLAIN")
            reply(conn, "250 8BITMIME")
        elif u.startswith("AUTH LOGIN"):
            reply(conn, "334 VXNlcm5hbWU6")  # base64("Username:")
            user = base64.b64decode(rd.readline().strip()).decode()
            print("C: (base64 decoded) user=%s" % user, flush=True)
            reply(conn, "334 UGFzc3dvcmQ6")  # base64("Password:")
            pwd = base64.b64decode(rd.readline().strip()).decode()
            print("C: (base64 decoded) pass=%s" % pwd, flush=True)
            if user == "smtpuser" and pwd == "q1w2e3r4":
                reply(conn, "235 auth ok")
            else:
                reply(conn, "535 auth failed")
        elif u.startswith("MAIL FROM"):
            reply(conn, "250 OK")
        elif u.startswith("RCPT TO"):
            reply(conn, "250 OK")
        elif u.startswith("DATA"):
            reply(conn, "354 end with <CRLF>.<CRLF>")
            print("---- message body ----", flush=True)
            while True:
                l = rd.readline()
                if not l or l.rstrip(b"\r\n") == b".":
                    break
                print("M: %r" % l.rstrip(b"\r\n"), flush=True)
            print("---- end of body ----", flush=True)
            reply(conn, "250 OK queued")
        elif u.startswith("QUIT"):
            reply(conn, "221 bye")
            break
        else:
            reply(conn, "250 OK")
    conn.close()
    print("connection closed", flush=True)


def main():
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind((HOST, PORT))
    srv.listen(5)
    print("SMTP sink listening on %s:%d ..." % (HOST, PORT), flush=True)
    while True:
        conn, addr = srv.accept()
        try:
            handle(conn, addr)
        except Exception as e:
            print("error: %r" % e, flush=True)
            try:
                conn.close()
            except Exception:
                pass


if __name__ == "__main__":
    main()

启动并验证:

nohup python3 /root/smtp_sink.py >/var/log/smtp-sink.log 2>&1 &
curl -s telnet://127.0.0.1:2525 --max-time 3 | head -1

实测回显:

220 net-demo SMTP sink ready

💡 技术拓展:真实投递邮件除 SMTP 协议本身外还要选认证方式,常见 SMTP 认证扩展对比:

方式原理安全性
不认证(HELO 直发)仅信任来源 IP低,仅内网/中继白名单可用
AUTH LOGIN用户名密码 Base64 编码传输需 TLS 保护,裸跑可被窃听
AUTH PLAIN一次提交明文对同上
AUTH CRAM-MD5服务端发挑战、密码不落网高,但现代服务端已少用

本文 sink 是本地演示服务,4.2 用无认证流程发信;带 AUTH LOGIN 认证的完整实测见《常用 API 与网络客户端用法详解》。

4.2 SMTPClient 真实发信

import org.apache.commons.net.smtp.SMTPClient;
import org.apache.commons.net.smtp.SMTPReply;

import java.io.Writer;
import java.nio.charset.StandardCharsets;

public class SmtpDemo {
    public static void main(String[] args) throws Exception {
        SMTPClient client = new SMTPClient("UTF-8");
        client.connect("127.0.0.1", 2525);
        if (!SMTPReply.isPositiveCompletion(client.getReplyCode())) {
            client.disconnect();
            throw new IllegalStateException("SMTP 服务器拒绝连接: " + client.getReplyString());
        }
        System.out.println("连接回显: " + client.getReplyString().trim());
        client.login("client.demo.local");
        System.out.println("HELO 回显: " + client.getReplyString().trim());
        client.setSender("sender@demo.local");
        client.addRecipient("rcpt@demo.local");
        try (Writer w = client.sendMessageData()) {
            w.write("From: sender@demo.local\r\n");
            w.write("To: rcpt@demo.local\r\n");
            w.write("Subject: commons-net smtp demo\r\n");
            w.write("\r\n");
            w.write("hello from Apache Commons Net SMTPClient\r\n");
            w.write("第二行:中文测试\r\n");
        }
        System.out.println("DATA 送达: " + client.completePendingCommand()
                + " 回复码=" + client.getReplyCode());
        client.disconnect();
        System.out.println("断开后 isConnected = " + client.isConnected());
    }
}
java -Dfile.encoding=UTF-8 -cp target/classes:$HOME/.m2/repository/commons-net/commons-net/3.13.0/commons-net-3.13.0.jar:$HOME/.m2/repository/commons-io/commons-io/2.21.0/commons-io-2.21.0.jar SmtpDemo

实测回显:

连接回显: 220 net-demo SMTP sink ready
HELO 回显: 250 sink greets client.demo.local
DATA 送达: true 回复码=250
断开后 isConnected = false

sink 侧收到的完整报文(日志节选):

connection from 127.0.0.1:35452
C: HELO client.demo.local
C: MAIL FROM:<sender@demo.local>
C: RCPT TO:<rcpt@demo.local>
C: DATA
---- message body ----
M: b'From: sender@demo.local'
M: b'To: rcpt@demo.local'
M: b'Subject: commons-net smtp demo'
M: b''
M: b'hello from Apache Commons Net SMTPClient'
M: b'\xe7\xac\xac\xe4\xba\x8c\xe8\xa1\x8c\xef\xbc\x9a\xe4\xb8\xad\xe6\x96\x87\xe6\xb5\x8b\xe8\xaf\x95'
---- end of body ----
connection closed

验证结果:\xe7\xac\xac\xe4\xba\x8c\xe8\xa1\x8c\xef\xbc\x9a\xe4\xb8\xad\xe6\x96\x87\xe6\xb5\x8b\xe8\xaf\x95 正是"第二行:中文测试"的 UTF-8 字节序列,sink 完整还原了报文;completePendingCommand() 拿到 250 表示 DATA 会话按协议正常闭合。

4.3 中文正文的编码坑(实测对照)

SMTPClient 的写通道默认编码是 ISO-8859-1(源码常量 SMTP.DEFAULT_ENCODING,经反射实测值),该编码无法表示汉字。用无参构造发同一封中文邮件,sink 收到的字节是:

M: b'????????'

8 个汉字变成 8 个字面问号——不是显示问题,是编码时就丢掉了。另外实测 setCharset(StandardCharsets.UTF_8) 对 SMTP 的写通道无效(编码字段在构造时即固定),正确做法是构造时指定:new SMTPClient("UTF-8")。4.2 的回显即该构造下 sink 收到的正确 UTF-8 字节。

五、与 Telnet 服务联动实测

5.1 准备 netcat 演示服务

用 netcat 监听 2323 端口,stdin 喂入应答脚本(-i 1 每行间隔 1 秒),保存为 /root/telnet-script.txt:

WELCOME TO NET-TELNET-DEMO
READY
ECHO: HELLO
BYE
nc -l 127.0.0.1 2323 -i 1 -q 3 < /root/telnet-script.txt

5.2 TelnetClient 真实交互

import org.apache.commons.net.telnet.TelnetClient;

import java.io.InputStream;
import java.io.OutputStream;

public class TelnetDemo {
    public static void main(String[] args) throws Exception {
        TelnetClient telnet = new TelnetClient();
        telnet.connect("127.0.0.1", 2323);
        System.out.println("已连接 127.0.0.1:2323");
        InputStream in = telnet.getInputStream();
        OutputStream out = telnet.getOutputStream();

        System.out.println("服务端欢迎行: " + readLine(in).trim());
        System.out.println("服务端就绪行: " + readLine(in).trim());

        out.write("HELLO\r\n".getBytes());
        out.flush();
        System.out.println("发送 HELLO, 服务端回复: " + readLine(in).trim());

        out.write("QUIT\r\n".getBytes());
        out.flush();
        System.out.println("发送 QUIT, 服务端回复: " + readLine(in).trim());

        telnet.disconnect();
        System.out.println("会话关闭, isConnected = " + telnet.isConnected());
    }

    static String readLine(InputStream in) throws Exception {
        StringBuilder sb = new StringBuilder();
        int ch;
        while ((ch = in.read()) != -1 && ch != '\n') {
            sb.append((char) ch);
        }
        return sb.toString();
    }
}
java -Dfile.encoding=UTF-8 -cp target/classes:$HOME/.m2/repository/commons-net/commons-net/3.13.0/commons-net-3.13.0.jar:$HOME/.m2/repository/commons-io/commons-io/2.21.0/commons-io-2.21.0.jar TelnetDemo

实测回显:

已连接 127.0.0.1:2323
服务端欢迎行: WELCOME TO NET-TELNET-DEMO
服务端就绪行: READY
发送 HELLO, 服务端回复: ECHO: HELLO
发送 QUIT, 服务端回复: BYE
会话关闭, isConnected = false

服务端侧同样留了痕迹——nc 把 TelnetClient 实际发来的字节打印了出来:

HELLO
QUIT

验证结果:双向回显对上了,TelnetClient 对普通 ASCII 字节是透传的(Telnet 协议的 IAC 转义只影响协商字节,详见《常用 API 与网络客户端用法详解》)。

六、集成常见坑(全部实测复现)

  1. 缺 commons-io 直接 NoClassDefFoundError。去掉 classpath 中的 commons-io 再跑 3.2 的 FTP 上传:

    连接回显: 220 (vsFTPd 3.0.5)
    登录成功, 当前目录=/ 服务端类型=UNIX Type: L8
    Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/io/output/ProxyOutputStream
            at java.base/java.lang.ClassLoader.defineClass1(Native Method)
            at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)
            at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
            at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
            at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
            at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
            at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
            at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
            at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
            at org.apache.commons.net.ftp.FTPClient._storeFile(FTPClient.java:978)
            at org.apache.commons.net.ftp.FTPClient.storeFile(FTPClient.java:3302)
            at org.apache.commons.net.ftp.FTPClient.storeFile(FTPClient.java:3323)
            at FtpDemo.main(FtpDemo.java:25)
    Caused by: java.lang.ClassNotFoundException: org.apache.commons.io.output.ProxyOutputStream
            at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
            at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
            at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
            ... 13 more
    

    3.13.0 把传输流包装实现迁移到了 commons-io,Maven 方案自动带入;手拼 classpath 时两个 JAR 都要有。SubnetUtils、TelnetClient 等纯工具类不受影响。

  2. FTP 控制通道编码与 JVM 编码是两回事。FTP 默认控制编码 ISO-8859-1,文件名含中文时须 setControlEncoding("UTF-8")(参数细节见《配置参数详解》);SMTP 则要在构造器传 "UTF-8"(4.3)。

  3. FTP 一次只能创建一级目录。makeDirectory("data/2026") 在 data 不存在时被 vsftpd 拒绝(回复码 550),需先建 data 再建 data/2026,实测过程见《常用 API 与网络客户端用法详解》。

  4. 中文环境下的 vsftpd 列表解析。3.13.0 的 RELEASE-NOTES 明确修复了"FTP fails to parse listings for Linux vsftpd in Chinese or Japanese(NET-740)",升级到 3.13.0 可避免中文 locale 下 listFiles() 解析失败的旧坑。

  5. Telnet 演示服务要留好退出通道。netcat 脚本式服务与 TelnetClient 联调时,读阻塞发生在服务端不再输出之后,务必像 5.2 那样按服务端约定读到终止行再 disconnect()。

七、与其他网络方案选型对比

需求Commons Net 方案替代方案选型建议
FTP/FTPS 客户端FTPClientJDK 无内置;JSch/sshj 是 SFTP(另一协议)传统 FTP/FTPS 首选 Commons Net
SMTP 发信SMTPClient(协议级)Jakarta Mail(JavaMail,功能全)只要发简单邮件、要精细控制命令流用 Commons Net;要 MIME/附件/模板用 Jakarta Mail
TelnetTelnetClient裸 Socket(要自己实现 IAC 协商)与 Telnet 设备交互直接用 TelnetClient,协商已内置
子网计算SubnetUtils/SubnetUtils6手写位运算直接用工具类,边界行为(/32 等)已被库处理
HTTP 访问无JDK HttpClientCommons Net 不覆盖 HTTP,勿混用

八、参考链接