本文最后更新于:2020年7月7日 晚上
概览:Java实现的Telnet协议,Telnet相关知识,操作协商细节,代码实现。
课设题目
设计题目:利用JAVA实现TELNET协议
设计要求:TELNET协议允许用户用一台终端来访问远程的主机 ,它允许终端于主机之间以半双工的方式交换信息,可参阅RFC864[6-13]。本次设计要求利用JAVA实现TELNET协议的基本功能 。
Telnet相关知识
Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议。Telnet协议的目的是提供一个相对通用的,双向的通信方法,允许界面终端设备和面向终端的过程能通过一个标准过程进行互相交互。应用Telnet协议能够把本地用户所使用的计算机变成远程主机系统的一个终端。
Telnet协议具有如下的特点:Telnet协议可以适应于多个不同的操作系统,在Windows电脑上连接的Linux系统的机器可以传输命令,Telnet协议可以传输特定的控制命令,例如中止进程的命令等,并且由于Telnei两端的机器与操作系统之间的异构型,Telnet不能严格规定每一个Telnet连接的详细配置,故Telnet协议支持双方进行协商。
Telnet协议主要由三部分组成:
(1)网络虚拟终端 NVT;
(2)操作协商定义;
(3)协商有限自动机。
网络虚拟终端 NVT
网络虚拟终端(NVT)是一种虚拟的终端设备,它被客户和服务器所采用,用来建立数据表示和解释的一致性。Telne使用网络虚拟终端字符集来处理异构系统的远程登录问题。网络虚拟终端字符集是一个通用接口, 在远程登陆连接上,客户软件将终端用户输入转化为标准的NVT数据和命令序列,经TCP连接传到远地机上的服务器,服务器再将NVT序列转换为原地系统的内部格式。这样终端键盘输入的异质性就被NVT所屏蔽,NVT这里实现了统一,只需要与NVT打交道即可。
Telnet的操作协商
TELNET协议允许通信机器协商会话过程中所使用的各种选项,通过一组标准过程来建立这些选项。协商选项的使用考虑了主计算机可提供超出虚拟终端服务范围的服务的可能性。例如窗口的大小、终端类型、字符回显等信息都要进行协商。
协商主要是通过Telnet指令来进行的,Telnet指令格式是:
常见的命令码:
名称 |
码字(byte) |
描述 |
EOF |
236 |
文件结束符 |
SUSP |
237 |
挂起当前进程(作业控制) |
ABORT |
238 |
异常终止进程 |
EOR |
239 |
记录结束符i |
NOP |
241 |
无操作 |
WILL |
251 |
开始执行指示选项或证实设备现已经开始执行指示选项 |
WONT |
252 |
拒绝执行指示选项或证实设备现已开始执行指示选项。 |
DO |
253 |
另一方执行的请求,或者证实期望对方执行的请求 |
DONT |
254 |
另一方停止执行的命令,或者证实一方不再期待另一方执行的命令。 |
常见选项码:
选项标识(byte) |
名称 |
1 |
回显 |
3 |
抑制继续进行 |
5 |
状态 |
6 |
定时标记 |
24 |
终端类型 |
31 |
窗口大小 |
选项协商的6种情况
进行协商有4中类型的请求:
(1)WILL:发送方本身想激活某个选项。
(2)DO:发送方想让接受端激活某个选项。
(3)WONT:发送方本身想禁止某个选项。
(4)DONT:发送方想让接受端去禁止某个选项。
发送者 |
接收者 |
说明 |
WILL |
DO |
发送者想激活某选项,接收者接受该选项请求 |
WILL |
DONT |
发送者想激活某选项,接收者拒接该选项请求 |
DO |
WILL |
发送者希望接收者激活某选项,接收者接受该请求 |
DO |
WONT |
发送者希望接收者激活某选项,接收者拒绝该请求 |
WONT |
DONT |
发送者希望使某选项无效,接收者必须接受该请求 |
WONT |
WONT |
发送者希望对方使某选项无效,接收者必须接受该请求 |
Linux开启Telnet
因为本次实验是拿一台虚拟机上的Linux作为服务器来做测试,所以需要开启Linux的Telnet服务。
参考我的博文: Linux-Telnet配置
Telnet的协商流程测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
| import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket;
public class Test {
public static void main(String[] args) throws IOException { Socket telnetSocket = new Socket("192.168.117.129",23);
OutputStream outputStream = telnetSocket.getOutputStream(); InputStream inputStream = telnetSocket.getInputStream();
byte[] bytes = new byte[1024*8];
int len = inputStream.read(bytes);
System.out.println("第一次接收响应,接受到的回应长度为"+len);
for(int j=0;j<len;j++){ int x = byteToInt(bytes[j]); System.out.println(x); }
byte[] first_responce = {(byte)255,(byte)252,(byte)24, (byte)255,(byte)252,(byte)32, (byte)255,(byte)252,(byte)35, (byte)255,(byte)252,(byte)39};
outputStream.write(first_responce); outputStream.flush();
System.out.println("第一次发送过去了");
len = inputStream.read(bytes);
System.out.println("第二次接收响应,接受到的回应长度为"+len);
for(int j=0;j<len;j++){ int x = byteToInt(bytes[j]); System.out.println(x); }
byte[] second_responce = {(byte)255,(byte)253,(byte)3, (byte)255,(byte)252,(byte)1, (byte)255,(byte)252,(byte)31, (byte)255,(byte)254,(byte)5, (byte)255,(byte)252,(byte)33};
outputStream.write(second_responce); outputStream.flush();
System.out.println("第二次发送过去了");
len = inputStream.read(bytes);
System.out.println("第三次接收响应,接受到的回应长度为"+len);
for(int j=0;j<len;j++){ int x = byteToInt(bytes[j]); System.out.println(x); }
byte[] three_responce = {(byte)255,(byte)253,(byte)1};
outputStream.write(three_responce); outputStream.flush();
System.out.println("第三次发送过去了");
System.out.println("-----------");
len = inputStream.read(bytes);
System.out.println("第四次接收响应");
String str = new String(bytes,0,len,"UTF-8"); System.out.println(str);
}
public static int byteToInt(byte b) { int x = b & 0xff; return x; } }
|
当程序与虚拟机中的linux建立连接之后,服务器会立刻发送协商信息给程序,接收信息后进行协商即可。具体的协商过程都在代码与注释中。
Telnet协商自动机
协商自动机是我在看别人的博客时发现的名词,就拿来用了。协商自动机的主要任务是,对于服务器发送过来的数据,分辨清楚它是协商消息还就是普通的数据。
如果是协商消息的话,就根据预先设置好的策略,进行回复,如果是数据的话,就展示给用户。
这解析服务器数据的过程就像是读取文件获得固定内容或者编译器读取代码那样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| public String negotiate(byte buf[],int lens) throws IOException{
byte front = (byte)0;
List<Byte> databuf = new ArrayList<Byte>(); List<Byte> negbuf = new ArrayList<Byte>();
int seg_offset = 0;
byte neg_state = STATE_DATA;
while(seg_offset < lens){
front = buf[seg_offset]; seg_offset++;
switch (neg_state){ case STATE_DATA: if(front == IAC){ neg_state = STATE_IAC; negbuf.add(IAC); } else{ databuf.add(front); } break; case STATE_IAC: switch(front){ case WILL: neg_state = STATE_IACWILL; break; case WONT: neg_state = STATE_IACWONT; break; case DONT: neg_state = STATE_IACDONT; break; case DO: neg_state = STATE_IACDO; break; default: neg_state = STATE_DATA; databuf.add(IAC); break; } break; case STATE_IACWILL: switch (front){ case TELOPT_ECHO: negbuf.add(DO); negbuf.add(TELOPT_ECHO); neg_state = STATE_DATA; break; case TELOPT_E3: negbuf.add(DO); negbuf.add(TELOPT_E3); neg_state = STATE_DATA; break; default: negbuf.add(DONT); negbuf.add(front); neg_state = STATE_DATA; break; } break; case STATE_IACDO: negbuf.add(WONT); negbuf.add(front); neg_state = STATE_DATA; break; case STATE_IACWONT: case STATE_IACDONT: neg_state =STATE_DATA; break; }
}
if(!negbuf.isEmpty() && (negbuf.size() % 3 == 0)){ Byte[] bytes = negbuf.toArray(new Byte[negbuf.size()]); byte[] responce = new byte[negbuf.size()]; ByteTobyte(bytes,responce); send(responce); return null; }
if(databuf != null){ Byte[] bytes = databuf.toArray(new Byte[databuf.size()]); byte[] message = new byte[databuf.size()]; ByteTobyte(bytes,message); String str = new String(message,0,databuf.size(),"UTF-8"); return str; }
return null; }
|
协商自动机执行的结果是,对于服务器发送的协商,程序解析完成后,根据预先设置的情况,向服务器发送协商结果。如果是服务器发送来的数据,就显示到控制台。
完整代码
TelnetClient.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
| import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.ArrayList; import java.util.List;
public class TelnetClient {
private Socket telnetSocket; private OutputStream outputStream; private InputStream inputStream;
private final static byte STATE_DATA = 0; private final static byte STATE_IAC = 1; private final static byte STATE_IACWILL = 2; private final static byte STATE_IACWONT = 3; private final static byte STATE_IACDO = 4; private final static byte STATE_IACDONT = 5;
private final static byte IAC = (byte) 255;
private final static byte WILL = (byte) 251;
private final static byte WONT = (byte) 252;
private final static byte DO = (byte) 253;
private final static byte DONT = (byte) 254;
private final static byte TELOPT_ECHO = (byte) 1;
private final static byte TELOPT_E3 = (byte) 3;
public void link(String host,int port) throws IOException{ telnetSocket = new Socket(host,port); outputStream = telnetSocket.getOutputStream(); inputStream = telnetSocket.getInputStream();
byte[] bytes = new byte[1024*8]; int len = 0;
while(true){ len = inputStream.read(bytes); String str = negotiate(bytes,len);
if(str != null){
System.out.print(str); break; } } }
public void send(byte[] bytes) throws IOException { outputStream.write(bytes); outputStream.flush(); }
public String receive() throws IOException{ byte[] bytes = new byte[1024*8]; int len = inputStream.read(bytes); if (len < 0) throw new IOException("Connection closed."); String str = negotiate(bytes,len); return str; }
public String negotiate(byte buf[],int lens) throws IOException{
byte front = (byte)0;
List<Byte> databuf = new ArrayList<Byte>(); List<Byte> negbuf = new ArrayList<Byte>();
int seg_offset = 0;
byte neg_state = STATE_DATA;
while(seg_offset < lens){
front = buf[seg_offset]; seg_offset++;
switch (neg_state){ case STATE_DATA: if(front == IAC){ neg_state = STATE_IAC; negbuf.add(IAC); } else{ databuf.add(front); } break; case STATE_IAC: switch(front){ case WILL: neg_state = STATE_IACWILL; break; case WONT: neg_state = STATE_IACWONT; break; case DONT: neg_state = STATE_IACDONT; break; case DO: neg_state = STATE_IACDO; break; default: neg_state = STATE_DATA; databuf.add(IAC); break; } break; case STATE_IACWILL: switch (front){ case TELOPT_ECHO: negbuf.add(DO); negbuf.add(TELOPT_ECHO); neg_state = STATE_DATA; break; case TELOPT_E3: negbuf.add(DO); negbuf.add(TELOPT_E3); neg_state = STATE_DATA; break; default: negbuf.add(DONT); negbuf.add(front); neg_state = STATE_DATA; break; } break; case STATE_IACDO: negbuf.add(WONT); negbuf.add(front); neg_state = STATE_DATA; break; case STATE_IACWONT: case STATE_IACDONT: neg_state =STATE_DATA; break; }
}
if(!negbuf.isEmpty() && (negbuf.size() % 3 == 0)){ Byte[] bytes = negbuf.toArray(new Byte[negbuf.size()]); byte[] responce = new byte[negbuf.size()]; ByteTobyte(bytes,responce); send(responce); return null; }
if(databuf != null){ Byte[] bytes = databuf.toArray(new Byte[databuf.size()]); byte[] message = new byte[databuf.size()]; ByteTobyte(bytes,message); String str = new String(message,0,databuf.size(),"UTF-8"); return str; }
return null; }
public int byteToInt(byte b) { int x = b & 0xff; return x; }
public void ByteTobyte(Byte[] bytes,byte[] byts){ for(int i=0;i<bytes.length;i++){ byts[i] = bytes[i]; } }
public void sleep(int second){ try { Thread.sleep(second*1000); } catch (InterruptedException e) { e.printStackTrace(); } }
public String subStr(String orgstr,String objstr){ if(orgstr == null){ return objstr; }
if(objstr.indexOf(orgstr.substring(0,orgstr.length()-1)) == 0){ return objstr.substring(orgstr.length()+1); } return objstr; } }
|
Main.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import java.io.IOException; import java.util.Scanner;
public class Main {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
String remotehost = "192.168.117.129";
String inputStr = null; String showStr = null;
TelnetClient tc = new TelnetClient(); tc.link(remotehost,23);
while(true){ inputStr = sc.nextLine(); inputStr += "\n";
tc.send(inputStr.getBytes()); tc.sleep(2);
showStr = tc.receive();
showStr = tc.subStr(inputStr,showStr); System.out.print(showStr); } } }
|
结果展示
不足
在资料中看到了NVT的说明,但是至今没搞懂NVT在哪里实现,我在Windows端下发送的命令加上\n
,就可以被执行,加上\r\n
就不会被执行,但是NVT不是统一使用的\r\n
作为换行符吗?
关于协商自动机,因为大多数Telnet协议的参数说明都很模糊,大部分网上查不到,并且RFC文档我看不太明白,所以那个协商函数是根据我在本地测试的情况编写的,好多问题根本没有考虑到。所以向这些基本操作可以使用,但是像是vi
这种命令去编辑文本就不能用了。
答辩的时候,老师询问了如果服务器的telnet进程突然出现了问题,一直给你发送消息你该怎么办,等好多特殊情况,我确实不知道该如何解决.
参考链接与资料分享
https://tools.ietf.org/html/rfc854
中文RFC文档阅读 701-1000
Telnet协议详解
Telnet协议的java实现
《基于Windows的TCPIP编程》王罡 清华大学出版社:https://pan.baidu.com/s/1jvDF1zBPmD0e4YyBQWebkw 提取码:y8dg