- Telnet 協定係主從式架構, 客戶端與伺服端使用 TCP 傳輸層建立連線, 利用 port 23 進行對話, 它只支援文字模式介面. 其規範文件參考 RFC854.
- TELNET 協定內容主要有四 : (1) NVT (2) 命令與資料辨識 (3) 選項 (4) 終端模式
- 因為主從兩方控制字元可能不同, 因此 TELNET 採用了 NVT (Network Virtual Terminal) 網路虛擬終端機作為中介. 客戶端與伺服端互傳資料時均須符合 NVT 規定, 以 ASCII 碼傳送資料.
- NVT 字元集有兩類 : (1) 命令 (2) 資料, 前者最高位元為 1, 後者為 0. 所有的命令以 255 開頭, 稱為 IAC (Interprete As Command), 當收到 255 時, 表示後續字元都是命令 (控制字元), 否則都屬於資料.
- NVT 僅僅是 TELNET 的基本功能, 像伺服端要不要回應 (ECHO), 通知終端機型式或畫面字元寬度等並未在 NVT 中規定, 而是透過跟在 IAC 後面的選項 (Option) 或子溝通選項 (sub-negotiation) 來補強雙方溝通用語.
- TELNET 使用 DO (253), DONT (254), WILL (251), WONT (252) 四個詞彙來溝通選項 :
DO : 要求對方執行某選項. 或回應認同對方想要執行的選項.
DONT : 要求對方停止已經在執行之選項, 或回應拒絕對方所要求執行之選項
WILL : 表示本身將執行某選項, 或回應認可或確認對方所要求執行的選項
WONT : 拒絕對方的 DO 要求
所以如果要求對方執行某選項, 對方也答應執行的話, 是 DO-WILL; 對方不同意就是 DO-WONT, 本身想要執行某選項, 對方也認同是 WILL-DO; 對方不認可就是 WILL-DONT
import java.io.*;
import java.net.*;
public class test {
public static void main(String[] args) {
String host="localhost"; //這裡用 localhost 代替, 實際使用主機 IP
int port=23;
telnet(host,port);
}
public static void telnet(String host, int port) {
Socket socket;
InputStream in;
OutputStream out;
try {
socket=new Socket(host, port);
in=socket.getInputStream();
out=socket.getOutputStream();
while (true) {
int data=in.read();
if (data==255) { //IAC:Interpre As Command (後面是控制指令)
int tone=in.read(); //第一個 Byte=溝通類型
int option=in.read(); //第二個 Byte=選項
switch (tone) { //檢查伺服端傳來的溝通類型
case 250 : { //SB (Sub-negotiation Begin) : 子溝通選項
switch (option) { //只實作終端機型態
case 24 : { //Negotiate terminal type
//read remaining 3 bytes from server 後面還有三個 byte 須讀完
in.read(); //"1":SEND
in.read(); //"255":IAC
in.read(); //"240":SE
//reply with terminal type="VT100" 回覆伺服器 VT100
out.write(255); //IAC
out.write(250); //SB
out.write(24); //Terminal type
out.write(0); //"IS"
out.write("VT100".getBytes());
out.write(255); //IAC
out.write(240); //SE
out.flush();
}
}
break;
}
case 253 : { //DO (Request or Allow option) 要求我方執行
if (option==24) { //Allow option : terminal type
if (tone==253) { //DO
out.write(255); //IAC
out.write(251); //WILL
out.write(option); //Allow option
out.flush();
}
else if (tone==251) { //WILL 認可我方要求
out.write(255); //IAC
out.write(253); //DO
out.write(option); //DO option
out.flush();
}
}
break;
}
default : { //Deny option 預設 : 拒絕選項
if (tone==253) { //DO:reply with WONT
out.write(255); //IAC
out.write(252); //WONT
out.write(option); //rejected option
out.flush();
}
else if (tone==251) { //WILL:reply with DONT
out.write(255); //IAC
out.write(254); //DONT
out.write(option); //rejected option
out.flush();
}
} //end of default
} //end of switch
} //end of if
else { //NOT IAC : display received
System.out.print((char)data);
}
}
}
catch (Exception e){System.out.println(e);}
}
}
其實不需要從 RFC854 實做 Telnet, 直接以字元 IO 方式讀取伺服器回應再傳送指令也是可以的, 首先在下列網站找到一個 Telnet 伺服器 :
# http://www.telnet.org/htm/places.htm
挑選了其中的 rainmaker.wunderground.com 這個 TELNET 伺服器作為測試標的, 當以 TELNET 連線此 IP 時, 首先會出現 "Press Return to continue:" 提示文字, 按 ENTER 後出現 "Press Return for menu or enter 3 letter forecast city code--" 回應, 隨便輸入 3 個字元按 ENTER 後, 出現選單.
我參考了下列兩個網站資料, 改寫為如下之範例 :
# http://letrungthang.blogspot.tw/2011/12/telnet-in-java.html
# http://stackoverflow.com/questions/6399557/java-simple-telnet-client-using-sockets
import java.io.*;
import java.net.*;
public class telnet {
static Socket socket;
static BufferedInputStream bis;
static BufferedReader r;
static PrintWriter w;
public static void main(String[] args) {
try {
String host="rainmaker.wunderground.com";
int port=23;
if (connect(host,port)) {
System.out.println("connected");
String prompt1="Press Return to continue:";
String prompt2="city code-- ";
if (readUntil(prompt1) != null) {
send("\n");
Thread.sleep(500);
if (readUntil(prompt2) != null) {
send("TPE\n");
Thread.sleep(500);
System.out.println("connected");
}
}
}
else {System.out.println("not connected");}
socket.close();
}
catch (Exception e) {e.printStackTrace();}
}
public static boolean connect(String host,int port) {
try {
socket=new Socket(host, port);
socket.setKeepAlive(true);
bis=new BufferedInputStream(socket.getInputStream());
w=new PrintWriter(socket.getOutputStream(),true);
return true; // connect OK
}
catch (Exception e) {e.printStackTrace();return false;}
}
public static void send(String value) {
try {
w.println(value);
w.flush();
System.out.println(value);
}
catch (Exception e) {e.printStackTrace();}
}
public static String readUntil(String pattern) {
try {
char lastChar=pattern.charAt(pattern.length()-1);
StringBuffer sb=new StringBuffer();
int numRead=0;
char ch=(char)bis.read();
while(true) {
System.out.print(ch);
numRead++;
sb.append(ch);
if (ch==lastChar) {
if (sb.toString().endsWith(pattern)) {return sb.toString();}
}
if (bis.available()==0){break;}
ch=(char)bis.read();
if (numRead > 2000) {break;} // can not read the pattern
} //end of while
} //end of if
catch(Exception e) {e.printStackTrace();}
return null;
}
}
參考資料 :
# 逢甲大學資訊工程學系專題報告: 實作 Telnet Client
# 瞭解 Telnet
# java simple telnet client using sockets
# Telnet client library
# Places to telnet
沒有留言:
張貼留言