server.py
import socket
import threading
import time
import signal
from datetime import datetime
HOST = '0.0.0.0'
PORT = 2323
BUFFER_SIZE = 1024
PROMPT = "$ "
SERVER_RUNNING = True
client_sockets = []
client_lock = threading.Lock()
def handle_sigint(signum, frame):global SERVER_RUNNINGprint(f"\n[!] 收到Ctrl+C信号,正在停止服务器...")SERVER_RUNNING = Falsewith client_lock:for sock in client_sockets:try:sock.sendall("\n服务器正在关闭,连接已断开。\n")sock.close()print(f"[-] 已关闭客户端连接: {sock.getpeername()}")except Exception as e:print(f"[!] 关闭客户端连接出错: {str(e)}")client_sockets.clear()print(f"[+] Telnet测试服务器已完全停止")
def handle_client(client_socket: socket.socket, client_addr: tuple):with client_lock:client_sockets.append(client_socket)print(f"[+] 新连接来自: {client_addr}")try:welcome_msg = f"""
=====================================
Telnet 测试服务器 (Python)
欢迎您,连接来自: {client_addr[0]}:{client_addr[1]}
当前时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
支持命令: ls / pwd / date / whoami / help / exit
=====================================
{PROMPT}"""client_socket.sendall(welcome_msg.encode('utf-8'))supported_cmds = {'ls': lambda: "\n".join(["file1.txt", "file2.log", "docs/", "data.csv"]) + "\n",'pwd': lambda: "/home/test/user\n",'date': lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n",'whoami': lambda: "test_user\n",'help': lambda: "支持命令: ls, pwd, date, whoami, help, exit\n",'exit': lambda: "再见!连接即将关闭...\n"}while SERVER_RUNNING:client_input = b""while b"\n" not in client_input and SERVER_RUNNING:client_socket.settimeout(1)try:data = client_socket.recv(BUFFER_SIZE)if not data: print(f"[-] 客户端 {client_addr} 主动断开连接")returnfor byte in data:if byte == 8: if len(client_input) > 0:client_input = client_input[:-1]client_socket.sendall(b"\x08 \x08")elif byte in (13, 10): client_input += b"\n"elif 32 <= byte <= 126: client_input += bytes([byte])except socket.timeout:continueexcept Exception as e:print(f"[!] 接收客户端 {client_addr} 数据出错: {str(e)}")returncmd = client_input.decode('utf-8').strip().lower()if not cmd: client_socket.sendall(PROMPT.encode('utf-8'))continueprint(f"[*] 收到 {client_addr} 的命令: {cmd}")if cmd in supported_cmds:response = supported_cmds[cmd]()if cmd == 'exit':client_socket.sendall(response.encode('utf-8'))time.sleep(0.3)breakelse:response = f"错误: 未知命令 '{cmd}',输入 'help' 查看支持的命令\n"client_socket.sendall(response.encode('utf-8'))client_socket.sendall(PROMPT.encode('utf-8'))except Exception as e:if SERVER_RUNNING:print(f"[!] 与 {client_addr} 交互出错: {str(e)}")finally:with client_lock:if client_socket in client_sockets:client_sockets.remove(client_socket)client_socket.close()print(f"[-] 与 {client_addr} 的连接已关闭")
def start_telnet_server():signal.signal(signal.SIGINT, handle_sigint)server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)server_socket.setblocking(False)try:server_socket.bind((HOST, PORT))server_socket.listen(5)print(f"[+] Telnet测试服务器已启动,监听 {HOST}:{PORT}")print(f"[+] 按 Ctrl+C 停止服务器\n")while SERVER_RUNNING:try:client_socket, client_addr = server_socket.accept()client_thread = threading.Thread(target=handle_client,args=(client_socket, client_addr),daemon=True)client_thread.start()except BlockingIOError:time.sleep(0.1)except Exception as e:if SERVER_RUNNING:print(f"[!] 服务器监听出错: {str(e)}")finally:server_socket.close()if __name__ == "__main__":start_telnet_server()
telnet_as_dll.go
package main
import "C"
import ("errors""fmt""net""os""os/signal""sync""syscall""time""unsafe"
)
const (TELNET_LINE_MODE_EDIT = C.TELNET_LINE_MODE_EDITTELNET_CHAR_MODE = C.TELNET_CHAR_MODE
)
type TelnetMode int
type telnetSession struct {conn net.Connmode TelnetModeprompt stringrecvBuf []bytemu sync.Mutexclosed boolsigChan chan os.Signal
}
var (sessionMap = struct {sync.Mutexm map[*C.TelnetHandle]*telnetSession}{m: make(map[*C.TelnetHandle]*telnetSession)}
)
func init() {sigChan := make(chan os.Signal, 1)signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)go func() {<-sigChansessionMap.Lock()for _, sess := range sessionMap.m {sess.Close()}sessionMap.m = make(map[*C.TelnetHandle]*telnetSession)sessionMap.Unlock()os.Exit(0)}()
}
func TelnetConnect(host *C.char, port C.int) *C.TelnetHandle {goHost := C.GoString(host)goPort := int(port)target := fmt.Sprintf("%s:%d", goHost, goPort)conn, err := net.DialTimeout("tcp", target, 5*time.Second)if err != nil {fmt.Printf("[ERROR] Connect failed: %v\n", err)return nil}sess := &telnetSession{conn: conn,mode: TELNET_LINE_MODE_EDIT,recvBuf: make([]byte, 4096),sigChan: make(chan os.Signal, 1),}cHandle := (*C.TelnetHandle)(C.malloc(C.sizeof_TelnetHandle))sessionMap.Lock()sessionMap.m[cHandle] = sesssessionMap.Unlock()return cHandle
}
func TelnetSetMode(handle *C.TelnetHandle, mode C.int) C.int {sessionMap.Lock()sess, ok := sessionMap.m[handle]sessionMap.Unlock()if !ok || sess.closed {return -1}sess.mu.Lock()sess.mode = TelnetMode(mode)if mode == C.TELNET_LINE_MODE_EDIT {sess.conn.Write([]byte{255, 250, 34, 0, 255, 240})}sess.mu.Unlock()return 0
}
func TelnetSetTerminalType(handle *C.TelnetHandle, termType *C.char) C.int {sessionMap.Lock()sess, ok := sessionMap.m[handle]sessionMap.Unlock()if !ok || sess.closed {return -1}sess.mu.Lock()defer sess.mu.Unlock()negotiateCmd := []byte{255, 250, 24, 1, 255, 240, }_, err := sess.conn.Write(negotiateCmd)if err != nil {return -1}return 0
}
func TelnetSend(handle *C.TelnetHandle, data *C.char, length C.int) C.int {sessionMap.Lock()sess, ok := sessionMap.m[handle]sessionMap.Unlock()if !ok || sess.closed || length <= 0 {return -1}goData := C.GoBytes(unsafe.Pointer(data), length)sess.mu.Lock()defer sess.mu.Unlock()if sess.mode == TELNET_LINE_MODE_EDIT && len(goData) > 0 && goData[len(goData)-1] != '\n' {goData = append(goData, '\n')}n, err := sess.conn.Write(goData)if err != nil {return -1}return C.int(n)
}
func TelnetRecv(handle *C.TelnetHandle, buffer *C.char, bufferSize, timeoutMs C.int) C.int {sessionMap.Lock()sess, ok := sessionMap.m[handle]sessionMap.Unlock()if !ok || sess.closed || bufferSize <= 0 {return -1}sess.mu.Lock()defer sess.mu.Unlock()sess.conn.SetReadDeadline(time.Now().Add(time.Duration(timeoutMs) * time.Millisecond))n, err := sess.conn.Read(sess.recvBuf)if err != nil {if errors.Is(err, os.ErrDeadlineExceeded) {return 0}if errors.Is(err, net.ErrClosed) {return -2}return -1}copy((*[1 << 20]byte)(unsafe.Pointer(buffer))[:], sess.recvBuf[:n])return C.int(n)
}
func TelnetDetectPrompt(handle *C.TelnetHandle, prompt *C.char) C.int {sessionMap.Lock()sess, ok := sessionMap.m[handle]sessionMap.Unlock()if !ok || sess.closed {return -1}sess.mu.Lock()sess.prompt = C.GoString(prompt)sess.mu.Unlock()return 0
}
func TelnetClose(handle *C.TelnetHandle) {sessionMap.Lock()sess, ok := sessionMap.m[handle]if ok {sess.Close()delete(sessionMap.m, handle)}sessionMap.Unlock()C.free(unsafe.Pointer(handle))
}
func (s *telnetSession) Close() {s.mu.Lock()defer s.mu.Unlock()if s.closed {return}s.closed = trues.conn.Close()close(s.sigChan)signal.Stop(s.sigChan)
}func main() {}
telnet_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdbool.h>#ifdef _WIN32
#include <windows.h>
#include <conio.h>
#else
#include <poll.h>
#include <termios.h>
#include <unistd.h>
#endif#include "telnet.h"TelnetHandle* g_handle = NULL;
volatile bool g_running = true;
char last_cmd[256] = {0};
void signal_handler(int signum) {if (signum == SIGINT || signum == SIGTERM) {printf("\n收到退出信号,正在断开连接...\n");g_running = false;}
}
void set_terminal_mode(bool raw) {
#ifdef _WIN32HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);DWORD mode;GetConsoleMode(hStdin, &mode);SetConsoleMode(hStdin, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
#elsestatic struct termios old_termios;if (raw) {tcgetattr(STDIN_FILENO, &old_termios);struct termios new_termios = old_termios;new_termios.c_lflag &= ~ICANON;new_termios.c_lflag |= ECHO;new_termios.c_cc[VMIN] = 1;new_termios.c_cc[VTIME] = 0;tcsetattr(STDIN_FILENO, TCSANOW, &new_termios);} else {tcsetattr(STDIN_FILENO, TCSANOW, &old_termios);}
#endif
}
bool has_user_input() {
#ifdef _WIN32return _kbhit() != 0;
#elsestruct pollfd pfd = {STDIN_FILENO, POLLIN, 0};return poll(&pfd, 1, 0) > 0;
#endif
}
int read_user_input(char* buffer, size_t max_len) {memset(buffer, 0, max_len); static size_t input_pos = 0; input_pos = 0;#ifdef _WIN32char c;while (1) {while (_kbhit()) {c = _getch();if (c == '\r' || c == '\b' || c == 27 || (c >= 32 && c <= 126)) {break;}}if (c == '\r') { if (input_pos > 0) {buffer[input_pos] = '\n'; input_pos++;printf("\n");return input_pos;}c = 0; continue;} else if (c == '\b' && input_pos > 0) { input_pos--;printf("\b \b");c = 0;} else if (c == 27) { strcpy(buffer, "exit\n");return strlen(buffer);} else if (c >= 32 && c <= 126 && input_pos < max_len - 2) { buffer[input_pos] = c;input_pos++;printf("%c", c);c = 0;} else {if (_kbhit()) {c = _getch();} else {Sleep(10); }}}
#elsessize_t n = read(STDIN_FILENO, buffer, max_len - 1);if (n > 0) {char* nl = strchr(buffer, '\n');if (nl) {*nl = '\0';strncpy(last_cmd, buffer, sizeof(last_cmd)-1);*nl = '\n';}return n;}return 0;
#endif
}
void filter_echo_response(char* response, int recv_len) {if (strlen(last_cmd) == 0) return;char cmd_form1[256]; char cmd_form2[256]; snprintf(cmd_form1, sizeof(cmd_form1), "%s\n", last_cmd);snprintf(cmd_form2, sizeof(cmd_form2), "%s\n", last_cmd);int len1 = strlen(cmd_form1);int len2 = strlen(cmd_form2);if (recv_len >= len1 && strncmp(response, cmd_form1, len1) == 0) {memmove(response, response + len1, recv_len - len1);response[recv_len - len1] = '\0';} else if (recv_len >= len2 && strncmp(response, cmd_form2, len2) == 0) {memmove(response, response + len2, recv_len - len2);response[recv_len - len2] = '\0';}
}int main(int argc, char* argv[]) {char* host = "localhost";int port = 2323;if (argc >= 2) host = argv[1];if (argc >= 3) port = atoi(argv[2]);printf("Telnet 客户端 - 连接 %s:%d\n", host, port);printf("提示:输入命令按回车发送,输入'exit'或按Ctrl+C/ESC退出\n\n");signal(SIGINT, signal_handler);signal(SIGTERM, signal_handler);set_terminal_mode(true);g_handle = TelnetConnect(host, port);if (!g_handle) {fprintf(stderr, "错误:无法连接 %s:%d\n", host, port);set_terminal_mode(false);return 1;}TelnetSetMode(g_handle, TELNET_LINE_MODE_EDIT);TelnetDetectPrompt(g_handle, "$ ");char recv_buf[4096] = {0};char send_buf[1024] = {0};while (g_running) {int recv_len = TelnetRecv(g_handle, recv_buf, sizeof(recv_buf)-1, 100);if (recv_len > 0) {recv_buf[recv_len] = '\0';filter_echo_response(recv_buf, recv_len);if (strlen(recv_buf) > 0) {printf("%s", recv_buf);fflush(stdout);}memset(recv_buf, 0, sizeof(recv_buf));} else if (recv_len == -2) {printf("\n错误:服务器已断开连接\n");break;}if (has_user_input()) {int send_len = read_user_input(send_buf, sizeof(send_buf));if (send_len > 0) {char* nl_pos = strchr(send_buf, '\n');if (nl_pos != NULL) {*nl_pos = '\0';strncpy(last_cmd, send_buf, sizeof(last_cmd)-1);*nl_pos = '\n'; }TelnetSend(g_handle, send_buf, send_len);if (strncmp(send_buf, "exit\n", 5) == 0) {g_running = false;}memset(send_buf, 0, sizeof(send_buf));}}}printf("\n正在关闭连接...\n");TelnetClose(g_handle);set_terminal_mode(false);printf("客户端已退出\n");return 0;
}
build on Windows
go build -v -buildmode=c-shared -o telnet.dll telnet_as_dll.go
gcc telnet_client.c -o telnet_client.exe -ltelnet -L.
up python server and test output
Telnet 客户端 - 连接 localhost:2323
提示:输入命令按回车发送,输入'exit'或按Ctrl+C/ESC退出=====================================
Telnet 测试服务器 (Python)
欢迎您,连接来自: 127.0.0.1:49857
当前时间: 2025-08-17 19:22:51
支持命令: ls / pwd / date / whoami / help / exit
=====================================
$ ls
错误: 未知命令 '"ls',输入 'help' 查看支持的命令
$ pwd
/home/test/user
$ date
2025-08-17 19:22:58
$ whoami
test_user
$ help
支持命令: ls, pwd, date, whoami, help, exit
$ exit正在关闭连接...
客户端已退出
bug
- client ctrl + c 没退出,按esc可以退出
- 第一次ls会说没找到命令