Telnet与Java实现

1 Telnet基础

 Telnet全称:Tel Type Net。
 端口:23。
 作用:远程登录,明文传输

1.1 Telnet交互过程

1)本地与远程主机建立连接。该过程实际上是建立一个TCP连接,用户必须知道远程主机的IP或者域名;
2)将本地连接上输入的用户名和口令及以后输入的任何命令或字符以NVT(Net Virtual Terminal)格式传送到远程主机。该过程实际上是从本地主机向远程主机发送一个IP数据包;
3)将远程主机上输出的VNT格式的数据转化为本地所接受的格式送回本地终端,包括输入命令回显和命令执行结果;

4)最后,本地终端对远程主机进行撤消连接。该过程是撤销一个TCP连接。

1.2 Telnet应用分析

        从目前的应用来看,我们在哪些情况下回应用到Telnet呢。实际上,在网络环境日趋的混乱,安全形势日趋严峻的今天,绝大多数的设备都已经屏蔽了Telnet,为了提高安全性,日常管理中,应用的更多的是SSH或者通过VPN等方式。既然如此,那为什么还要去实现呢。我个人的观点是难免有些应用场景,比如只是对安全性要求不高的内网机器进行几条简单命令的操作,不会涉及安全。还有一点就是领导的攀比心理,别家的产品有的,我们也必须有,要不然打标的时候会被比下去。作为下属的我们,也只能从命啦。以上仅是个人观点,欢迎指点。

2 Java实现 

       通过上面简要的介绍,我们知道Telnet就是TCP/IP协议簇中的一种,Java要实现Telnet,无非就是做个Socket的客户端,然后向远程主机发送认证命令,然后读取认证结果,通过认证后,再向远程数据发送需要执行的命令,然后读取命令的执行结果。读取完成后,断开连接。过程就是这样,但是,实现没这么简单。
      Apache Commons包中有TelnetClient类,顾名思义,是Telnet协议的客户端实现类,有了它,可以省去我们自己写交互的过程。但是使用时,要注意以下几点:
      1、考虑好终端类型。Telnet有VT100,VT220,VT52等,有个设置不对,就会出现读取乱码。我开始用的时候是VT100,后来发现乱码,改为VT220后解决。
      2、如何判断读取结束。这点我会重点说。

2.1 判断读取结束的解决方案

        由于TelnetClient没有提供发送命令和读取结果的接口,我们只能自己实现,实现的逻辑就是获取客户端中的输入输出流,使用输出流向远程主机发送命令,使用输入流从远程主机读取命令执行结果。由于使用流操作,这样我就必须知道什么时候结束流的读取操作。期初我考虑到两种方案:
        1、配置读取结束读取的标识符,如果读取中读到这个标示符则结束读取。这个方案应该是很多人开始就想到的方案,但是这个方案存在的问题是如果命令的执行结果中就包含这个符号,就会丢失这个符号之后的数据。而且不能方便的覆盖全部的远程主机。
        2、创建固定大小的缓存,如10k,将读取到的数据循环存入缓存,缓存满了就转到成固定的串。一旦发现读取到的avaiable值为-1并且缓存不满则结束读取。这个方案的问题是会造成数据乱码,这个乱码并不是因为字符集导致的,而是因为在我们读取到的数据都是二进制数据,当缓存满时,有可能会将原本属于一个字节的数据分到两个缓存中,这样在转成串时就造成了由于数据不完整,不能转化成正常的字符串,从而导致乱码。
       如此以来,这两种方案都不太好,后来我考虑能不能找到一个标示流数据结束的标识,最终没有找到。但是在这个找的过程中,我想到了最终的解决方案,最终的解决方案思路是:
       1、创建一个固定大小的缓存,将读到的数据写入缓存。
       2、将读到的数据写入数组输出流。
       3、从登陆的数据中自动读取数据的最后一行,作为以后所有数据读取结束的标识。这样做的原因是无论是Windows还是Linux,在命令执行完后,都会输出下一个命令的提示符,这也就表示上一条命令执行结束。
       4、创建一个与结束标识符同长度的缓存,每次读取数据后,都从数据的结尾处往前截取该长度的数据。用于判断该数据是否是结束符。
       5、当读到结束符时,结束读取操作,并从数组输出流中写出读取到的全部数据。
       通过以上思路,就完美的解决了数据读取的问题。我自己测试不下千边,均能正常读取,正常结束。

3 代码

下面是具体的代码,请借鉴:

import org.apache.commons.net.telnet.TelnetClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class TelnetConnection extends {

	private static Logger log = LoggerFactory.getLogger(TelnetConnection.class);

	private String ip;

	private int port = 23;

	private String userName;

	private String userPwd;

	private String charSet = "GBK";

	private TelnetClient client = new TelnetClient("VT220");

	private InputStream in;

	private PrintStream out;

	public TelnetConnection(Map params) {
		if (params == null || params.size() == 0) {
			throw new IllegalArgumentException(
					"Telnet connection params is null");
		} else {
			this.ip = params.get(ParameterNameList.IPADDRESS.toString());
			String port = params.get(ParameterNameList.PORT.toString());
			this.port = port == null || port.length() == 0 ? 23 : Integer
					.parseInt(port);
			this.userName = params.get(ParameterNameList.USERNAME.toString());
			this.userPwd = params.get(ParameterNameList.PASSWORD.toString());
			this.charSet = params.get(ParameterNameList.CHARSET.toString());
			this.charSet = (this.charSet == null || this.charSet.length() == 0) ? "GBK"
					: this.charSet;
		}
	}

	@Override
	protected void open() throws Exception {
		client.connect(ip, port);
		in = client.getInputStream();
		out = new PrintStream(client.getOutputStream());
		/** Log the user on* */
		readUntil("\n\rlogin: ", "\n\rLogin: ");
		write(userName);
		readUntil("\n\rpassword: ", "\n\rPassword: ");
		write(userPwd);

        StringBuilder sb=new StringBuilder();
        while(cmdEnd==null){
            ReadResult result = readUntil(1);
            sb.append(result.getResult());
            if (isLoginFailed(sb.toString())) {
                throw new Exception("Login Failed");
            }
            Thread.sleep(100); //等100毫秒,降低cpu损耗
        }
	}

	private ReadResult readUntil(String... ends) throws Exception {
		return readUntil(-1, ends);
	}

	private ReadResult readUntil(int timeout, String... ends) throws Exception {

		ReadResult readResult = new ReadResult();

		List sampleList = null;
		ByteBuffer compareBuf = null;

		boolean enableEndsWithCheck = ends != null && ends.length > 0;
		if (enableEndsWithCheck) { // 有结束符时的逻辑
			sampleList = new ArrayList();

			int maxSampleLength = -1;
			for (int c = 0; c < ends.length; c++) { // 创建样本列表
				byte[] bytes = ends[c].getBytes(charSet);
				maxSampleLength = Math.max(maxSampleLength, bytes.length);
				sampleList.add(bytes);
			}

			compareBuf = ByteBuffer.allocate(maxSampleLength); // 比较缓冲
		}

		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

		int bufferSize = 1024;
		final byte[] buf = new byte[bufferSize]; // BUFSIZE 是一个常量10240=10k

		while (true) { // 循化读取
			int read;

			if (timeout > 0) { // 有超时限制
				TimeoutExecution timeoutExecution = new TimeoutExecution() {
					@Override
					protected Integer execute() throws Exception {
						return in.read(buf);
					}
				};
				if (timeoutExecution.execute(timeout, TimeUnit.SECONDS)) {
					read = timeoutExecution.getResult(); // 没有超时获得读取的数据长
				} else {
					// 超时
					break;
				}
			} else { // 无超时限制
				read = in.read(buf);
			}

			if (read != -1) { // 读到数据

				outputStream.write(buf, 0, read);

				if (enableEndsWithCheck) { // 有结束符时的逻辑
					byte[] array = compareBuf.array();
					if (read > array.length) {//当新的数据长度大于缓存最大容量时,截取新数据中最后一部分长度为缓存最大容量的数据,最为备选停止标识
						compareBuf.clear();
						compareBuf.put(buf, read - array.length, array.length); // 将最后数据部分,放入比较缓冲
					} else {
						int remaining = compareBuf.remaining();
						if (remaining >= read) { // 当缓存剩余空间大于新读出数据的长度时,直接写入
							compareBuf.put(buf, 0, read);
						} else {
							/*
							 * 如果剩余空间小于新读出数据的长度,则需要先从缓存中取出已存数据大小减去新数据大小的数据,
							 * 这样做的目的是为了避免造成停止结束标识断开。然后将这部分数据和新数据一起重新放入缓存。
							 */
							int firstPartIndex = array.length - read;
							byte[] oldArray = Arrays.copyOfRange(array,
									array.length - firstPartIndex, array.length);
							compareBuf.clear();// 清空
							compareBuf.put(oldArray);
							compareBuf.put(buf, 0, read);
						}
					}
					// 开始比较特征
					array = compareBuf.array();
					int position = compareBuf.position();

					boolean matched = false;
					for (int exampleIdx = 0; exampleIdx < sampleList.size(); exampleIdx++) {

						byte[] sample = sampleList.get(exampleIdx); // 获取样本

						if (sample.length <= position) { // 长度一样,进行逐一匹配
							int offset = position - sample.length; // 位移
							matched = true;
							for (int c = 0; c < sample.length; c++) {
								if (sample[c] != array[c + offset]) {
									matched = false;
									break;
								}
							}
							if (matched) { // 匹配
								readResult.setMatcher(exampleIdx);// 结果中包含
																	// example的下标
								break; // 跳过其它样本检查
							} else {
								continue; // 继续其它样本检查
							}
						} else { // 样本长度大,说明不匹配
							continue; // 下一个样本
						}
					}
					
					if (matched) { // 发现匹配结束符,终止匹配
						break;
					}
				}

			} else { // -1没有读到数据,这样情况不太可能被调用到
				break;
			}

		}

		readResult.setResult(outputStream.toString(charSet)); // 流中数据统一转码
		outputStream.close();

		return readResult;
	}

	public void write(String value) {
		try {
			out.print(value + "\r\n");
			out.flush();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	private String cmdEnd = null;

	private boolean isLoginFailed(String result) throws Exception {
		boolean isFailed = false;

		if (result.length() > 0) {
			isFailed = result.contains("ailed");
		}

		int i = result.lastIndexOf("\r\n");
		if (i > 0) {
			cmdEnd = result.substring(i); // 获得结束提示符,带回车
			if (logger.isDebugEnabled()) {
				logger.debug("Prompt Detected! \"{}\"", cmdEnd);
			}
		}

		return isFailed;
	}

	private class ReadResult {
		private int matcher = -1;
		private String result = null;

		public int getMatcher() {
			return matcher;
		}

		public void setMatcher(int matcher) {
			this.matcher = matcher;
		}

		public String getResult() {
			return result;
		}

		public void setResult(String result) {
			this.result = result;
		}

		@Override
		public String toString() {
			final StringBuilder sb = new StringBuilder("ReadResult{");
			sb.append("matcher=").append(matcher);
			sb.append(", result='").append(result).append('\'');
			sb.append('}');
			return sb.toString();
		}
	}

	public CliResult runCmd(String name, String command) {
		try {
			write(command);
			StringBuilder result = new StringBuilder();

			ReadResult readResult = readUntil(cmdEnd, "--More--", "--more--");
			result.append(readResult.getResult());

			while (readResult.getMatcher() != 0) { // "--More--", "--more--"
				String moreResult = readResult.getResult();
				result.append(moreResult.substring(0, moreResult.length() - 8));
				write((char) 32 + "");
				readResult = readUntil(cmdEnd, "--More--", "--more--");
			}

			String[] vals = result.toString().trim().split("\r\n");
			List rel = new ArrayList();
			if (logger.isDebugEnabled()) {
				log.debug("Value For Command " + command + " Is Below:");
			}
			for (String val : vals) {
				if (logger.isDebugEnabled()) {
					log.debug(val);
				}
				rel.add(val);
			}
			CliResult cliResult = new CliResult(name, command, rel);
			return cliResult;
		} catch (Exception e) {
			log.error("Exec Command Error, Cause " + e.getMessage(), e);
		}
		return null;
	}

	@Override
	protected void close() {
		try {
			if (this.in != null) {
				this.in.close();
			}
			if (this.out != null) {
				this.out.close();
			}
			this.client.disconnect();
		} catch (Exception e) {
			e.printStackTrace();
			log.error("Close Telnet Connection Error, cause " + e.getMessage());
		}

	}

    @Override
    protected boolean validate() {
    	return client.isAvailable();
    }

    public static void main(String args[]) {
		Map params = new HashMap();
		params.put(ParameterNameList.IPADDRESS.toString(), "127.0.0.1");
		params.put(ParameterNameList.USERNAME.toString(), "Administrator");
		params.put(ParameterNameList.PASSWORD.toString(), "XXXXXX");
		params.put(ParameterNameList.PORT.toString(), "23");

		TelnetConnection telCon = new TelnetConnection(params);
		try {
			telCon.open();
			CliResult val = telCon.runCmd("Windows", "systeminfo");
			List vals = val.getResult();
			int i = 1;
			for (String str : vals) {
				System.out.println(i + "  " + str);
				i++;
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			telCon.close();
		}
	}

}







你可能感兴趣的:(Java,网络管理协议)