Linux C编程 | 从0实现telnet获取程序终端控制权

一、应用场景

在实际项目开发中,我们程序默认终端的输入输出是串口,但是调试时可能只能通过网络远程调试,而远程最常用的登录手段就是telnet和ssh;

本文就探讨如何通过telnet远程控制我们的主程序,退出telnet客户端,再交还终端控制权。

完整代码获取见文末。

二、原理

该功能最核心的功能是利用函数dup()、dup2()。

来看下Linux手册如何描述

dupdup2Linux/Unix 系统级 I/O 函数 ,核心作用:复制文件描述符 ,让多个文件描述符指向同一个文件表项(共享文件偏移量、打开状态)。

它们是进程间通信、重定向标准输入输出、守护进程编程的核心函数。

dup

函数原型如下:

复制代码
int dup(int oldfd);

功能

  • 复制 oldfd返回当前进程可用的最小文件描述符
  • 新 fd 和 oldfd 指向完全相同的文件表项

dup2

复制代码
int dup2(int oldfd, int newfd);

功能

  • 强制复制 oldfdnewfd
  • 如果 newfd 已经打开,先自动关闭它(原子操作,无竞争)
  • 最终 newfdoldfd 指向同一个文件

三、程序设计思路

之前写过一篇关于实现一个简单命令行的文章,本文在这个代码基础之上,实现通过telnet操作命令。

c语言实例|实现简单的命令行

程序主要流程如下:

注意:

  1. 新连接的客户端会把原有客户端关闭。
  2. 新的连接必须重新创建命令重启子线程。
bash 复制代码
	dup2 只是修改文件描述符指向
	但已经在等待的阻塞 I/O 不会自动迁移
	子线程还在等 旧的串口 stdin
	新 Telnet 输入根本进不去
	所以有新的连接必须重启该线程

四、核心代码

  • 文件

    peng@ubuntu:~/work/demo/telnet/telcmd$ tree .
    .
    ├── cmd.c
    ├── color.h
    ├── mkapp.sh
    ├── tel
    └── telnet.c

    0 directories, 5 files

  • 主函数main()

c 复制代码
int main() 
{
    int server_fd, client_fd;
    struct sockaddr_in addr;
    int addr_len = sizeof(addr);
	static int telnet_cli_created = 0;
	
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) 
	{ 
		perror("[telnet]socket"); 
		exit(1); 
	}

    //port 2323
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(TELNET_DUP_SERVER_PORT);
	
    if (bind(server_fd, (struct sockaddr*)&addr, addr_len) == -1) 
	{
        perror("[telnet]bind"); 
		exit(1);
    }

    listen(server_fd, 1);

	std_bakcup();
	cprintf(YEL,"[telnet]wait Telnet connect\n");
	create_cmd_thread();

	while(1){
	    client_fd = accept(server_fd, (struct sockaddr*)&addr, (socklen_t*)&addr_len);
	    if (client_fd == -1) { 
			perror("[telnet]accept"); 
			exit(1); 
		}
		switch_to_telnet(client_fd);		
		create_cmd_thread();
	}

    close(client_fd);
    close(server_fd);
    return 0;
}
  • switch_to_telnet()
c 复制代码
void switch_to_telnet(int new_fd)
{	
	static int old_fd = -1;
	// 忽略 SIGPIPE
	signal(SIGPIPE, SIG_IGN);
	cprintf(GREEN_H,"tel_dup(),%d %d\n",new_fd,old_fd);

	if(new_fd != old_fd){
		if(old_fd>0){
			if(telnet_fd  != -1){
				close(telnet_fd );
				telnet_fd  = -1;
			}			
		}
		dup2(new_fd, STDOUT_FILENO);  // printf -> client
		dup2(new_fd, STDERR_FILENO);  // perror -> client
		dup2(new_fd, STDIN_FILENO);	 // getchar <- client
		old_fd = new_fd;	
		telnet_fd  = new_fd ;	
	}else{
		cprintf(RED,"[telnet] fd is same\n");
	}
	fflush(stdout);
}
  • switch_back_to_serial()
c 复制代码
void switch_back_to_serial(void)
{
	dup2(original_stdout_fd, STDOUT_FILENO);
	dup2(original_stdout_fd, STDERR_FILENO);
	dup2(original_stdin_fd, STDIN_FILENO);
	if(telnet_fd  != -1){
		close(telnet_fd );
		telnet_fd  = -1;
	}
}
  • std_bakcup()
c 复制代码
void std_bakcup(void)
{
	//保存原始标准输出(串口)
	original_stdout_fd = dup(STDOUT_FILENO);
    original_stderr_fd = dup(STDERR_FILENO);
	original_stdin_fd = dup(STDIN_FILENO);
}
  • create_cmd_thread()
csharp 复制代码
void create_cmd_thread(void)
{
	int ret;
	pthread_attr_t attr;
	static int telnet_cli_created = 0;
	static pthread_t th_cmd;

	if(telnet_cli_created == 1)
	{
		pthread_cancel(th_cmd);
		telnet_cli_created = 0;
	}
	if(telnet_cli_created == 0){
		ret = pthread_attr_init(&attr);
		if (ret != 0)
		{
			printf("pthread_attr_init\n");
		}	
		pthread_create(&th_cmd,&attr,cmdThread,NULL); 

		telnet_cli_created = 1;
	}	
}

五、测试

ubuntu下测试

ubuntu需要支持telnet服务器,

  • 安装命令
bash 复制代码
sudo apt install xinetd telnetd -y
  • 修改文件**/etc/xinetd.d/telnet**

    service telnet
    {
    disable = no
    flags = REUSE
    socket_type = stream
    wait = no
    user = root
    server = /usr/sbin/in.telnetd
    log_on_failure += USERID
    }

  • 启动/重启服务

bash 复制代码
sudo systemctl restart xinetd
sudo systemctl enable xinetd  #开机运行
  • 测试
    客户端连接命令:
csharp 复制代码
 telnet 127.0.0.1 2323

可以看到telnet连接后,可以输入命令,并且返回命令结果。

windows下测试

测试步骤如下:

软件配置

Linux换行符是**\n**(对应asc码 0xa),windows的换行符是**\r\n**(对应asc码 0xb 0xa),mobaXterm、xshell需要设置支持Linux换行符,否则打印格式会乱。

mobaXterm


xshell


六、代码获取

转发留言:telnet

所有c语言基础示例+应用实例 合集获取:

相关推荐
M78佐菲6 小时前
ARM学习笔记(1)
linux·arm开发·笔记·嵌入式硬件·学习
骇客野人6 小时前
Redis 完整安装部署方案(Linux,分【源码编译】+【Docker】两种,推荐生产用源码,测试快速用 Docker)
运维
益达丫7 小时前
Socket编程揭秘:端口、IP与进程通信的底层逻辑
linux·笔记
aramae7 小时前
MySQL复合查询(8)
java·c语言·开发语言·后端·算法
无敌贵点大王8 小时前
RTThread学习记录11——RT-Thread 设备模型吃透:UART/ADC/PWM/PIN 到底有什么区别?
c语言·stm32·学习·链表
Logic1019 小时前
C语言/数据结构位运算题解:异或XOR找出时尚聚会中的“独特颜色“——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质
蓝速科技9 小时前
会议室门牌公告通知发布选型与落地指南丨蓝速科技
大数据·运维·数据库·人工智能·科技
chicheese9 小时前
Linux 面试速查表:从初级到高级,附高频场景答题模板
linux·运维
Linux-lucky10 小时前
32-38-Linux学习之旅之MySQL综合
linux·运维·学习·mysql·ubuntu
智能运维指南11 小时前
2026年ITSM系统怎么选?四款主流方案与五维评估模型
运维·itsm·嘉为蓝鲸