创建一个 Telnet 服务器主要涉及以下几个核心部分:

- 服务器端点:监听来自客户端的连接请求。
- 客户端处理:为每个连接成功的客户端创建一个独立的线程来处理交互。
- 协议解析:解析 Telnet 客户端发送的命令,特别是处理 Telnet 的选项协商。
- 命令执行:接收用户输入的命令,执行并返回结果。
- 数据发送:将执行结果或提示信息发送回客户端。
我们将使用 Java 标准库中的 java.net.ServerSocket 和 java.net.Socket 来实现网络通信,对于 Telnet 协议,我们将手动处理其协商过程,以确保与标准 Telnet 客户端的兼容性。
第 1 步:项目结构
为了保持代码清晰,我们将创建两个主要的 Java 类:
TelnetServer.java: 主服务器类,负责启动服务器、接受新连接。ClientHandler.java: 客户端处理器类,负责与单个客户端的通信、命令解析和执行。
第 2 步:核心代码实现
ClientHandler.java - 客户端处理器
这个类是服务器的核心,它在一个独立的线程中运行,处理与单个客户端的所有交互。
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class ClientHandler implements Runnable {
private final Socket clientSocket;
private PrintWriter out;
private BufferedReader in;
private boolean isActive = true;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try {
// 获取输入输出流
out = new PrintWriter(clientSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
// 1. 发送欢迎横幅
sendWelcomeBanner();
// 2. 处理 Telnet 协商(非常重要!)
// 这部分是确保 Telnet 客户端能正确显示的关键
handleTelnetNegotiation();
// 3. 主循环:读取用户输入并执行命令
String inputLine;
while (isActive && (inputLine = in.readLine()) != null) {
// 处理退出命令
if ("exit".equalsIgnoreCase(inputLine) || "quit".equalsIgnoreCase(inputLine)) {
out.println("Goodbye!");
break;
}
// 执行命令并返回结果
String result = executeCommand(inputLine);
out.println(result);
}
} catch (IOException e) {
if (isActive) {
System.err.println("Error handling client: " + e.getMessage());
}
} finally {
// 清理资源
close();
}
}
/**
* 发送欢迎信息
*/
private void sendWelcomeBanner() {
out.println("-------------------------------------------------");
out.println(" Welcome to the Simple Java Telnet Server");
out.println("-------------------------------------------------");
out.println("Type 'help' for a list of commands, or 'exit' to quit.");
out.print("> "); // 提示符
}
/**
* 处理 Telnet 协商
* Telnet 协议使用 IAC (Interpret As Command) 来协商选项。
* 这里我们简单处理,告诉客户端我们支持 ECHO (回显) 和 SUPPRESS_GO_AHEAD (抑制前进)。
* 这能确保大多数客户端能正常工作。
*/
private void handleTelnetNegotiation() throws IOException {
// 这是一个简化的协商过程。
// 完整的实现需要解析 IAC 命令并正确响应。
// 这里我们直接向客户端发送我们支持的选项,这通常能被客户端接受。
// DO ECHO (客户端回显我们发送的内容)
out.write((byte) 255); // IAC
out.write((byte) 251); // DO
out.write((byte) 1); // ECHO
out.flush();
// WILL SUPPRESS_GO_AHEAD (告诉客户端不要等待我们发送 "go ahead" 信号)
out.write((byte) 255); // IAC
out.write((byte) 251); // WILL
out.write((byte) 3); // SUPPRESS_GO_AHEAD
out.flush();
}
/**
* 执行系统命令
* 注意:直接执行系统命令有安全风险!
* 在生产环境中,应该严格限制可执行的命令。
*/
private String executeCommand(String command) {
StringBuilder output = new StringBuilder();
try {
// 使用 ProcessBuilder 来执行命令,更灵活和安全
ProcessBuilder pb = new ProcessBuilder(command.split(" "));
pb.redirectErrorStream(true); // 合并标准错误和标准输出
Process process = pb.start();
// 读取命令输出
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
int exitCode = process.waitFor();
if (exitCode != 0) {
output.append("Command exited with error code: ").append(exitCode).append("\n");
}
} catch (IOException | InterruptedException e) {
output.append("Error executing command: ").append(e.getMessage()).append("\n");
}
return output.toString().isEmpty() ? "Command executed. No output." : output.toString();
}
/**
* 关闭资源
*/
public void close() {
isActive = false;
try {
if (out != null) out.close();
if (in != null) in.close();
if (clientSocket != null && !clientSocket.isClosed()) {
clientSocket.close();
}
System.out.println("Client disconnected.");
} catch (IOException e) {
System.err.println("Error closing client handler: " + e.getMessage());
}
}
}
TelnetServer.java - 主服务器类
这个类负责启动服务器,并在有新客户端连接时,为其创建一个 ClientHandler 实例。

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TelnetServer {
private static final int PORT = 2323; // 选择一个不常用的端口
private static final int MAX_THREADS = 10; // 最大并发客户端数
private ExecutorService threadPool;
public void start() {
threadPool = Executors.newFixedThreadPool(MAX_THREADS);
System.out.println("Telnet Server is starting on port " + PORT + "...");
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("Server is running and waiting for connections...");
// 主循环,持续接受客户端连接
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("New client connected: " + clientSocket.getInetAddress().getHostAddress());
// 为每个客户端创建一个任务并提交到线程池
ClientHandler clientHandler = new ClientHandler(clientSocket);
threadPool.execute(clientHandler);
}
} catch (IOException e) {
System.err.println("Server exception: " + e.getMessage());
} finally {
shutdown();
}
}
private void shutdown() {
System.out.println("Shutting down server...");
if (threadPool != null) {
threadPool.shutdown(); // 停止接受新任务
// 注意:这里没有强制终止正在运行的任务,可以根据需要实现
}
}
public static void main(String[] args) {
TelnetServer server = new TelnetServer();
server.start();
}
}
第 3 步:如何运行和测试
-
编译代码: 将这两个文件保存在同一个目录下,然后打开终端,运行:
javac *.java
-
运行服务器:
java TelnetServer
你会看到控制台输出:
Telnet Server is starting on port 2323... Server is running and waiting for connections... -
使用 Telnet 客户端连接:
(图片来源网络,侵删)- 在 Windows 上:
打开命令提示符 或 PowerShell,输入:
telnet localhost 2323
- 在 macOS 或 Linux 上:
打开终端,输入:
telnet localhost 2323
注意:如果系统没有
telnet命令,在 macOS 上可以通过brew install telnet安装,在 Linux 上通常可以通过包管理器(如sudo apt-get install telnet)安装。
- 在 Windows 上:
打开命令提示符 或 PowerShell,输入:
-
交互测试: 连接成功后,你应该能看到欢迎横幅:
------------------------------------------------- Welcome to the Simple Java Telnet Server ------------------------------------------------- Type 'help' for a list of commands, or 'exit' to quit. >- 输入
dir(Windows) 或ls(Linux/macOS) 并回车,你会看到当前目录的列表。 - 输入
echo Hello World并回车,你会看到 "Hello World"。 - 输入
help或exit并回车,服务器会回应并断开连接。
- 输入
第 4 步:进阶与注意事项
上面的实现是一个功能完整的起点,但在生产环境中,你需要考虑更多:
安全性 (最重要!)
- 命令注入风险:
executeCommand方法直接执行用户输入的命令,这是极其危险的,恶意用户可以输入rm -rf /(Linux) 或del C:\*.* /s /q(Windows) 来破坏系统。- 解决方案:
- 白名单机制:只允许执行一个预定义的、安全的命令列表,只允许执行
dir,echo,ping等。 - 参数过滤:如果允许带参数的命令,必须对参数进行严格的过滤和验证,防止特殊字符(如 ,
&, ,>)被用来拼接恶意命令。 - 沙箱环境:在一个受限的沙箱容器中执行命令,即使命令被破坏,也无法影响到宿主系统。
- 白名单机制:只允许执行一个预定义的、安全的命令列表,只允许执行
- 解决方案:
完整的 Telnet 协议支持
我们上面的 handleTelnetNegotiation 是一个非常简化的版本,完整的 Telnet 协议支持很复杂,涉及 IAC (Interpret As Command)、DO/DON'T/WILL/WON'T 等命令的交换,如果你需要与所有 Telnet 客户端完美兼容,可以考虑使用成熟的库。
更好的并发模型
虽然我们使用了线程池,但对于成千上万的并发连接,基于线程的模型可能会消耗大量资源,可以考虑更高级的 I/O 模型:
- NIO (New I/O):使用
java.nio.channels.ServerSocketChannel和Selector实现单线程或少量线程处理成百上千的连接。 - Netty:一个优秀的异步事件驱动的网络应用框架,可以极大地简化网络编程,并提供了高性能和高可靠性,对于复杂的网络服务器,Netty 是更好的选择。
功能增强
- 用户认证:在用户连接后,要求输入用户名和密码。
- 虚拟文件系统:提供一个受限的文件系统视图,让用户可以
cd,ls,get,put文件,而不是直接执行系统命令。 - 日志记录:记录所有客户端的连接、断开、执行的命令等,用于审计和排错。
这个 Java Telnet 服务器项目是学习网络编程和 Java I/O 的一个绝佳实践,从简单的实现开始,然后逐步考虑安全、性能和功能扩展,是构建健壮网络服务的良好路径。
