为在telnet自己TCP服务器程序的界面上实现shell一样的自动补齐和历史记录的功能。
2010年07月01日
希望在telnet自己TCP服务器程序的界面上实现shell一样的自动补齐和历史记录的功能。 程序的远程登陆的telnet界面通常是通过一个TCP服务器来实现的,但是如果想在这个TCP服务器端实现客户端登陆界面的自动补齐和历史记录的功能会有如下的问题:
(1)常见的telnet客户端是以行模式发送数据的,即输入一个字符串后再按一个回车,整个数据才会被发送到服务器端。
(2)常见的telnet客户端是自动回显的,即你在键盘上输入一个字符后,客户端自己将这个字符显示在客户端界面上,而不是显示的从服务器端发送过来的数据。
默认这样做的原因,是为了简化了客户端和服务器端的实现。但是如果希望能够让客户端能够具有shell一样的自动补齐和历史记录功能则无法实现,原因如下: (1)自动补齐功能一般是按tab键后按现有输入的部分进行匹配的,不能等按回车,因为按回车一般表示客户端输入完成,等待服务器处理的结果。应该是按下每个按键都发送一次。
(2)历史记录功能一般有按"↑"和"↓"键来实现对记录上翻和下翻的功能,而如上翻功能在按下"↑"键后应用上条历史记录来替代当前行输入的值,这就需要抹除当前行已有的字符,这点在自动回显的功能中很难做到。换句话说,最好由服务器端完全控制客户端的显示,不要让客户端自动显示任何东西。
按照telnet协议的规定,详见附录1,将回显功能关闭,将输入模式从逐行模式改为逐个字符模式。具体参考命令参考大全。
echo
在输入字符的本地屏幕显示与禁止本地屏幕显示间切换。本地屏幕显示用于正常处理,而禁止屏幕显示便于输入不宜显示在屏幕上的文本,如密码。该变量仅可用于逐行方式
输入方式
当带参数发出 telnet 命令时,它执行包含这些参数的open子命令,然后进入输入方式。输入方式的类型为逐个字母或逐行,取决于远程系统所支持的是什么。在逐个字母方式下,大部分输入的文本会立即送到远程主机处理。在逐行方式下,所有文本在本地屏幕显示,然后将完整的行发送到远程主机。
这样做增加了客户端和服务端实现的工作量,尤其是服务器端,需要对接收的字符进行拼装处理,同时要控制客户端的显示。主要实现功能如下:
(1)实现对客户端显示的控制功能,因为客户端不再自动显示,一些原有的功能现在需要由服务器来完成,比如删除一个字符,现在服务器需要三步完成:1.发送指令让客户端的光标向前移动一个字符à2.向客户端发送一个空格à3.再发送指令让客户端的光标向前移动一个字符。
(2)在服务器端对接收到的数据进行缓存和管理,根据收到的数据对前面的数据做对应的处理。如收到一个删除字符就要将缓存的字符串最后一个字符删除并将长度减一并进行(1)提到的操作。
(3)实现自动补齐算法,主要包括两种情况:A.如果和现有输入匹配的命令只有一个,直接用其在当前行替代已有输入。B.如果和现有输入匹配的命令有多个,一般是在当前行下面将这多个命令显示。
(4)对历史记录的管理。
整体实现参考了zebra源代码部分的telnet部分的实现,但是zebra源码实现的较为复杂,功能虽强,但超出我当前的需求代价太大,故参考简化实现。只在linux下的telnet测试通过,windows下可能有点小问题。具体的怎样将命令执行参考asterisk源码中cli的实现简化实现,感觉asterisk的cli的设计思路更加清晰些,但是只支持本地显示,不能telnet。
大概代码如下,省略部分
#define TEL_MAXHIST 20
#define TEL_COM_LEN 100
typedef struct tel_vty
{
/* socket fd */
int fd;
/* 命令接受缓冲区 */
char buf[TEL_COM_LEN];
/* 命令当前指针,主要用于左右移动等操作需要记录当前在操作字符串的那个位置 */
int cp;
/* 命令长度 */
int length;
/* 命令历史记录数组,准备循环使用,当未到达最大长度时每个新的节点都插入在hp之后的位置,数组最后一个位置的下一个位置是第一个位置 */
char *hist[TEL_MAXHIST];
/* 当前历史记录指针,主要用于在历史记录中上下翻时记录当前指向的历史记录 */
int hp;
/* 历史插入的结尾指针 */
int hindex;
}tel_vty;
/*------------------------------------------------ -------------------------*/
/* TELNET 协议处理 */
/*------------------------------------------------ -------------------------*/
char telnet_backward_char = 0x08;//用与控制客户端光标向前移动一个字符
char telnet_space_char = ' ';
//向客户端发送值
void vty_out(tel_vty *p_tel_vty, char cmd[], int len)
//清空当前buf
void vty_clear(tel_vty *p_tel_vty)
{
memset(p_tel_vty->buf, 0x00, p_tel_vty->length);
p_tel_vty->length=0;
}
/*使客户端不要自动回显*/
void vty_will_echo(tel_vty *p_tel_vty)
{
char cmd[] = { IAC, WILL, TELOPT_ECHO, '\0' };
vty_out(p_tel_vty, cmd, sizeof(cmd));
}
/* 使用suppress Go-Ahead telnet选项. */
static void vty_will_suppress_go_ahead(tel_vty *p_tel_vty)
{
char cmd[] = { IAC, WILL, TELOPT_SGA, '\0' };
vty_out(p_tel_vty, cmd, sizeof(cmd));
}
/* 使用逐个字符显示的输入方式. */
static void vty_dont_linemode(tel_vty *p_tel_vty)
{
char cmd[] = { IAC, DONT, TELOPT_LINEMODE, '\0' }; vty_out(p_tel_vty, cmd, sizeof(cmd)); } /* Use window size. */ static void vty_do_window_size(tel_vty *p_tel_vty) { char cmd[] = { IAC, DO, TELOPT_NAWS, '\0' }; vty_out(p_tel_vty, cmd, sizeof(cmd)); } //使客户端光标向前移动一个字符
static void vty_backward_char(tel_vty *p_tel_vty)
{
if(p_tel_vty->length > 0)
{
p_tel_vty->buf[p_tel_vty->length--]='\0';
vty_out(p_tel_vty, &telnet_backward_char, 1);
}
}
//发送空格字符
static void vty_space_char(tel_vty *p_tel_vty)
//回到当前行的开始
static void vty_beginning_of_line(tel_vty *p_tel_vty)
{
while(p_tel_vty->length)
vty_backward_char(p_tel_vty);
}
//使客户端删除一个字符
static void vty_ec(tel_vty *p_tel_vty)
{
// printf("ec len is %d\n", p_tel_vty->length);
vty_backward_char(p_tel_vty);
vty_space_char(p_tel_vty);
vty_backward_char(p_tel_vty);
}
//使客户端删除当前位置向后的一行
static void vty_kill_line(tel_vty *p_tel_vty)
{
int i=0;
for ( i = 0; ilength = 0;
}
//添加历史记录
static void vty_hist_add(tel_vty *p_tel_vty)
{
int index;
if (p_tel_vty->length == 0)
return;
index = p_tel_vty->hindex?p_tel_vty->hindex - 1: TEL_MAXHIST - 1;
/* 如果和上一次命令相同则忽略 */
if (p_tel_vty->hist[index])
if(strcmp(p_tel_vty->buf, p_tel_vty->hist[index]) == 0)
{
p_tel_vty->hp = p_tel_vty->hindex;
return;
}
if (p_tel_vty->hist[p_tel_vty->hindex]) { free(p_tel_vty->hist[p_tel_vty->hindex]); } p_tel_vty->hist[p_tel_vty->hindex] = strdup(p_tel_vty->buf); p_tel_vty->hindex++; if(p_tel_vty->hindex == TEL_MAXHIST) p_tel_vty->hindex = 0; p_tel_vty->hp = p_tel_vty->hindex; } //显示一行
static void vty_redraw_line(tel_vty *p_tel_vty)
{
vty_out(p_tel_vty, p_tel_vty->buf, p_tel_vty->length);
p_tel_vty->cp = p_tel_vty->length;
}
//打印历史记录
static void vty_histroy_print(tel_vty *p_tel_vty)
{
int length;
vty_kill_line_from_beginning(p_tel_vty);
/* 从历史记录缓存中取得字符窜 */
length = strlen(p_tel_vty->hist[p_tel_vty->hp]);
memcpy(p_tel_vty->buf, p_tel_vty->hist[p_tel_vty->hp], length);
p_tel_vty->cp = p_tel_vty->length = length;
/* 显示此行 */
vty_redraw_line(p_tel_vty);
}
//显示当前记录的下一个历史记录
static void vty_next_line(tel_vty *p_tel_vty)
{
int try_index;
if(p_tel_vty->hp == p_tel_vty->hindex)
return;
/* 寻找下个位置,如果已经在最后,循环到第一位 */
try_index = p_tel_vty->hp;
if (try_index == (TEL_MAXHIST - 1))
try_index = 0;
else
try_index++;
/* 查看此位置有没有记录 */
if (p_tel_vty->hist[try_index] == NULL)
return;
else
p_tel_vty->hp = try_index;
vty_histroy_print(p_tel_vty);
}
//显示当前记录的上一个历史记录
static void vty_previous_line(tel_vty *p_tel_vty)
{
int try_index;
try_index = p_tel_vty->hp;
if (try_index == 0)
{
try_index = TEL_MAXHIST - 1;
}
else
{
try_index--;
}
if (p_tel_vty->hist[try_index] == NULL)
return;
else
p_tel_vty->hp = try_index;
vty_histroy_print(p_tel_vty); } //自动补齐函数
static int vty_complete(tel_vty * p_tel_vty)
{
int fd = p_tel_vty->fd;
char matchstr[80] = "";
struct ngn_cli_entry *e;
struct ngn_cli_entry *match_e;
int len = 0;
int found = 0;
char tmp_out_str[80];
int tmp_showed = 0;
if (p_tel_vty->buf)
{
sprintf(matchstr,"%s",p_tel_vty->buf);
len = strlen(matchstr);
}
NGN_LIST_LOCK(&helpers);
e = NGN_LIST_FIRST(&helpers);
if (e)
{
do{
/* Hide commands that start with '_' */
if (p_tel_vty->buf && strncasecmp(p_tel_vty->buf, e->cmda[0], len))
continue;
if (found == 0)
{
sprintf(tmp_out_str, "%25.20s %s\n\r", e->cmda[0], S_OR(e->summary,""));
}
match_e = e;
found++;
if (found > 1)//多于一个匹配时的处理
{
if (tmp_showed == 0)
{
ngn_cli(fd, "\r\n");
ngn_cli(fd, tmp_out_str);
tmp_showed = 1;
}
ngn_cli(fd, "%25.20s %s\n\r", e->cmda[0], S_OR(e->summary,""));
}
}while(e = NGN_LIST_NEXT(e, list));
}
/* 找到唯一匹配 */
if ((found == 1))
{
e = match_e;
vty_kill_line_from_beginning(p_tel_vty);
memset(p_tel_vty->buf, 0x00, p_tel_vty->length);
memcpy(p_tel_vty->buf,e->cmda[0], strlen(e->cmda[0]));
p_tel_vty->length = strlen(e->cmda[0]);
vty_redraw_line(p_tel_vty);
}
else if (found > 1)//如果多于一个,其实已经在上面处理过了
{
vty_clear(p_tel_vty);
ngn_cli(fd, "\r\n->");
}
NGN_LIST_UNLOCK(&helpers); if (!found && matchstr[0]) { return RESULT_FAILURE; } return RESULT_SUCCESS; } //执行一个命令
static void vty_excute(tel_vty *p_tel_vty)
{
char recv_buf[MAX_CLI_LEN];
memset(recv_buf, 0x00, MAX_CLI_LEN);
ast_cli(p_tel_vty->fd, "\r\n");
vty_hist_add(p_tel_vty);
memcpy(recv_buf, p_tel_vty->buf, p_tel_vty->length);
ast_cli_command(p_tel_vty->fd,(const char *)recv_buf);//具体执行一个命令,这部分另外参考astersik源码实现。
memset(p_tel_vty->buf, 0x00 , p_tel_vty->length);
p_tel_vty->length = 0;
}
//初始化结构
static void init_tel_vty(tel_vty *p_tel_vty)
{
memset(p_tel_vty->buf, 0x00, TEL_COM_LEN);
p_tel_vty->cp = 0;
p_tel_vty->length = 0;
for ( int i = 0; ihist[i] = NULL;
}
p_tel_vty->hp = 0;
p_tel_vty->hindex = 0;
}
/* 主函数
*/
#define CONTROL_TAB 0X09
#define CONTROL_DEL 0X7F
void *processDebugThread(void *arg)
{
…
Int connfd = *((int *)arg);
/*设置终端 */
tel_vty tel_vty;
tel_vty.fd = connfd;
init_tel_vty(&tel_vty);
vty_will_echo(&tel_vty);
vty_will_suppress_go_ahead(&tel_vty);
/* 上面的处理严格的话要检查客户端的返回值,是OK才表明客户端支持这样设置,但对我的应用环境暂不需要 */
vty_dont_linemode(&tel_vty);
vty_do_window_size(&tel_vty);
const char *wel =
"--------Hello, you are entering the ren911's debug cmd terminal, welcome-------\n";
write(connfd, wel, strlen(wel));
write(connfd, "\r->", 3);
while ((n = read(connfd, buf, MAX_CLI_LEN)) > 0) {
{
/* 是特殊键,以^]]开头的键值( 如向上键是^]]A ) */
if (3 == n)
{
switch(buf[2]) {
case CONTROL_UP:
{
vty_previous_line(&tel_vty);
}
break; case CONTROL_DOWN: { vty_next_line(&tel_vty); } break; } } /* 主要处理的是回车换行"\r\n" */
else if ( 2 == n )
{
switch(buf[0]) {
case '\r':
{
if (tel_vty.length > 0)
{
vty_excute(&tel_vty);
}
vty_clear(&tel_vty);
ngn_cli(connfd, "\r\n->");
}
}
}
/* 单个字符的处理和一些特殊键的处理,例如删除键和TAB键 */
else if ( 1 == n )
{
switch (buf[0]) {
case CONTROL_DEL:
{
vty_ec(&tel_vty);
}
break;
case CONTROL_TAB:
{
vty_complete(&tel_vty);
}
break;
default:
{
ngn_cli(connfd, "%c", buf[0]);
tel_vty.buf[tel_vty.length++] = buf[0];
}
}
}
}
}
…
}
5 相关参考
1. telnet协议介绍http://support.microsoft.com/kb/231866/en-us/
2. zebra Zebra 是一个开源的 TCP/IP 路由软件,同 Cisco Internet 网络操作系统(IOS)类似。详见http://www.zebra.org/
3. asterisk 是一个开源的软PBX软件。详见http://www.asterisk.org/
发表评论
-
Process and Thread
2012-01-20 09:21 733Process and Thread 2011年02 ... -
转:构建可扩展的Java EE应用
2012-01-20 09:20 637转:构建可扩展的Java EE ... -
由C++转向C#需要注意的问题 (3)
2012-01-20 09:20 508由C++转向C#需要注意的问题 (3) 2010年06月02 ... -
探索 Gdb7.0 的新特性反向调试 (reverse debug)
2012-01-20 09:20 770探索 Gdb7.0 的新特性反向调试 (reverse deb ... -
单纯的我
2012-01-19 14:20 504单纯的我 2011年12月28日 今天是我 ... -
2012,传说中的世界末日!
2012-01-19 14:20 6192012,传说中的世界末日! ... -
“三牧之星”这确实是个令人高兴的事儿
2012-01-19 14:20 802“三牧之星”这确实是个令人高兴的事儿 2011年12月18日 ... -
我和精华园的Victor song
2012-01-19 14:20 576我和精华园的Victor song ... -
9月的玫瑰
2012-01-17 04:10 7919月的玫瑰 2011年09月06 ... -
作 文
2012-01-17 04:10 385作 文 2011年12月14日 First of al ... -
试卷代号:216l
2012-01-17 04:10 672试卷代号:216l 2012年01月05日 中央 ... -
英语中的零冠词现象探究
2012-01-17 04:10 841英语中的零冠词现象探究 2011年12月08日 ... -
FLASH实用代码大全4
2012-01-16 02:58 655FLASH实用代码大全4 2010年04月21日 96。 ... -
常用的课件开发工具
2012-01-16 02:58 665常用的课件开发工具 20 ... -
计算机一级B选择题
2012-01-16 02:58 786计算机一级B选择题 2011年06月06日 [1]. 操 ... -
计算机答案
2012-01-16 02:58 1390计算机答案 2011年05月27日 1、建立计算机网络的 ... -
AcionScript
2012-01-16 02:58 469AcionScript 2010年10月01日 在论坛当 ...
相关推荐
java-telnet连接远程服务器并执行shell命令 具体代码 java-telnet
今天为了搞一个自动更新程序,找了一些telnet自动登入的程序,自己写了一个类似的ftp自动登入的脚本程序。不敢独吞,与大家共享,这些脚本也可作BBS养马甲之用。 一。Windows平台: 复制一下代码到记事本中并另保存...
这是一个telnet终端和服务器制作程序资料,可以使用该资料。
java源代码,通过telnet方式连接服务器,可以设置是否记录日志等信息。经过稍微改动可以实现类似crt软件的功能。
本资源使用Java实现Telnet客户端程序,程序有界面,并且资源中简单的写了使用说明,希望能帮到大家
批量检测端口并导出记录 附件中的telnet.sh为执行脚本 ip.txt为IP地址,端口在脚本中自定 实现多IP多端口 默认路径/root 上传到服务器默认路径下直接可以使用 运行完成后: log.txt为记录清单
使用批处理实现telnet自动登陆并执行ping命令
部署在具有公网IP可访问的服务器中,与MCU的Telnet TCP客户端进行连接,通过服务端的控制终端可以远程访问MCU,目前在RTThread中测试通过。
这是一个可以实现telnet的类库,调用里面的方法就可以返回一个服务器是否开了某个端口
# telnet ip port 批量测试多个 ip|port 每次telnet 都要手动关闭 ctrl + ] 然后 ctrl + d 很麻烦 使用 (sleep 1;) | telnet $ip $port 就能1秒自动断开了
Delphi Telnet服务器端源程序软件
使用TcpClient实现telnet功能 demo为控制台程序模拟Windows的telnet程序
本程序实现了Telnet服务器功能。可使用Telnet终端与本程序所在开发板进行交互。 备注: 本程序是基于正点原子哥的STM32H7开发板程序,将《嵌入式网络那些事——STM32物联实战》的Telnet服务器程序进行的移植和修改。
用下列命令编译程序: gcc -Wall telnet-server -o telnetd 启动telnet服务: ./telnetd --daemon #以root用户身份在23端口(即telnet默认端口服务) 或 ./telnetd -P 7838 #以非root用户身份
C语言实现的telnet客户端 选项协商:所有的选项协商都是服务器主动提出的,客户机不主动发送协商命令,只是对选项协商进行应答,对于回显、抑制继续进行、终端类型是要处理的,其他的否定。 子选项协商只发送终端...
java 实现telnet服务器执行脚本
纯shell的自动telnet登录执行脚本 可以支持多台主机顺序telnet登录执行相关命令,支持4个参数。
TelNet服务器的源程序(49KB)
qt实现 编译环境: Qt5.x + mingw-QtCreater C++11 win32上需要LIBS += libwsock32 libws2_32 积分太高改不了,需要下载的大家可以联系我。
卷3:客户-服务器编程与应用(Linux/POSIX套接字版)》的实现,并在原来的基础上增加了Telnet多系统的功能,并做了详细的中文注释,附带各种文档(虽然很不规范)。这是我的毕业设计,能在主流的Linux系统上直接编译...