java ssh(jsch)實戰

1.maven環境jar包引入

<dependency>
	<groupId>org.apache.spark</groupId>
	<artifactId>spark-core_2.11</artifactId>
	<version>2.3.4</version>
</dependency>

<!-- java ssh -->
<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.54</version>
</dependency>

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>1.2.4</version>
</dependency>

<dependency>             
    <groupId>cglib</groupId>             
    <artifactId>cglib</artifactId>             
    <version>3.2.10</version>         
</dependency>

2.使用CGLIB動態代理

Session類的原因,需要實現一個子類,且需要和session類在同一個包下面

package com.jcraft.jsch;

/**
 * 解決Superclass has no null constructors but no arguments were given錯誤
 * 
 * @author duhai
 * @date 2019年12月12日
 */
public class SshSession extends Session {

	/**
	 * 這樣創建無妨,最後使用的是session
	 * @throws JSchException
	 */
	public SshSession() throws JSchException {
		super(new JSch(),"username", "host", 22);
	}
	
	public SshSession(final JSch jsch, final String username, final String host, final int port)
			throws JSchException {
		super(jsch, username, host, port);
	}
}

3.使用代理攔截器歸還Session 資源

package com.spark.demo.sub.sshsession;

import java.lang.reflect.Method;

import com.jcraft.jsch.Session;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * CGLib代理的方法攔截器:<br/>
 * method執行方法的爲目標對象: method.invoke(target, args);
 * 
 * @author duhai
 * @date 2019年12月12日
 */
public class SshSessionProxyFactoryBean implements MethodInterceptor {

	// 維護目標對象
	private Session target;
	// 代理對象
	private Session proxyObj;
	// 資源池對象,規範資源用
	private SshSessionFactoryBean sessionFactoryBean;

	public SshSessionProxyFactoryBean(final Session target, final SshSessionFactoryBean sessionFactoryBean) {
		this.target = target;
		this.sessionFactoryBean = sessionFactoryBean;
	}

	/**
	 * 創建代理對象
	 * 
	 * @param target
	 * @return
	 */
	public Object getInstance() {
		// 1.工具類
		Enhancer en = new Enhancer();
		// 2.設置父類
		en.setSuperclass(target.getClass());
		// 3.設置回調函數
		en.setCallback(this);
		// 4.創建子類(代理對象)
		proxyObj = (Session) en.create();
		return proxyObj;
	}

	/**
	 * 攔截session的相關方法,disconnect的時候不斷開連接,歸還到seesion資源池
	 * 
	 * @param obj
	 * @param method
	 * @param args
	 * @param proxy
	 * @return
	 * @throws Throwable
	 * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object,
	 *      java.lang.reflect.Method, java.lang.Object[],
	 *      net.sf.cglib.proxy.MethodProxy)
	 */
	public Object intercept(final Object obj, final Method method, final Object[] args, final MethodProxy proxy)
			throws Throwable {

		if ("disconnect".equals(method.getName())) {
			// 歸還Session 資源
			sessionFactoryBean.back(proxyObj);
			return new Object();
		} else {
			// 執行目標對象的方法
			Object returnValue = method.invoke(target, args);
			return returnValue;
		}
	}

}

4.ssh的session很吃資源,所以不能太多,需要資源池

package com.spark.demo.sub.sshsession;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SshSession;

/**
 * SshSession 工場類
 * @author duhai  
 * @date 2019年12月12日
 */
public class SshSessionFactoryBean {

	public static final Logger logger = LoggerFactory.getLogger(SshSessionFactoryBean.class);

	private static final String USER = "root";
	private static final String PASSWORD = "*******";
	private static final String HOST = "10.168.11.11";
	private static final int DEFAULT_SSH_PORT = 22;

	private int SESSION_PHY_NUM = 2;
	private int SESSION_POOL_SIZE = 8;

	// Sesssion 池
	private Stack<Session> sesseionProxyPool = new Stack<Session>();

	/**
	 * 類級的內部類,也就是靜態的成員式內部類,該內部類的實例與外部類的實例 沒有綁定關係,而且只有被調用到纔會裝載,從而實現了延遲加載
	 */
	private static class SshSessionFactoryBeanHolder {
		/**
		 * 靜態初始化器,由JVM來保證線程安全
		 */
		private static SshSessionFactoryBean instance = new SshSessionFactoryBean();
	}

	/**
	 * 私有化構造方法
	 */
	private SshSessionFactoryBean() {
		// 初始化Session池
		intPool();
	}

	/**
	 * 獲取實體類
	 * 
	 * @return
	 */
	public static SshSessionFactoryBean getInstance() {
		return SshSessionFactoryBeanHolder.instance;
	}

	/**
	 * 取出session資源,歸還在intercept中
	 * 
	 * @return
	 * @throws Exception
	 */
	public Session getObject() throws Exception {
		return pop();
	}

	/**
	 * 初始化Session池,異步方式
	 */
	private void intPool() {
		new Thread() {
			@Override
			public void run() {
				logger.info("SshSession池 開始初始化!");

				List<Session> list = new ArrayList<Session>();
				for (int i = 0; i < SESSION_PHY_NUM; i++) {
					try {
						Session session = createSession();
						list.add(session);
					} catch (JSchException e) {
						logger.error(e.getMessage());
						e.printStackTrace();
					}
				}

				for (int i = 0; i < SESSION_POOL_SIZE; i++) {
					Session session = list.get(i % SESSION_PHY_NUM);
					createProxySession(session);
				}
				logger.info("SshSession池 初始化成功!");

				// 開啓一個心跳檢查,每10秒心跳一次
				while (true) {
					try {
						logger.info("心跳檢查線程休眠10S");
						Thread.sleep(10 * 1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					logger.info("開始執行Session連接心跳.....");
					for (Session session : list) {
						if (!session.isConnected()) {
							logger.warn("一個Session連接失效,嘗試恢復連接");
							try {
								session.connect();
								logger.info("Session嘗試恢復連接成功");
							} catch (JSchException e) {
								logger.error("Session嘗試恢復連接失敗!!!,建議重啓服務");
							}
						}
						try {
							logger.info("發送Session心跳");
							session.sendKeepAliveMsg();
						} catch (Exception e) {
							logger.error("Session心跳錯誤,需要重連");
							e.printStackTrace();
						}
					}
				}
			}
		}.start();
	}

	/**
	 * 創建代理Session
	 * 
	 * @param session
	 * @return
	 */
	private Session createProxySession(final Session session) {
		Session proxySession = (Session) new SshSessionProxyFactoryBean(session, this).getInstance();
		sesseionProxyPool.push(proxySession);
		return proxySession;
	}

	/**
	 * 創建session
	 * 
	 * @return
	 * @throws JSchException
	 */
	private Session createSession() throws JSchException {
		JSch jsch = new JSch();
		// Session session = jsch.getSession(USER, HOST, DEFAULT_SSH_PORT);
		Session session = new SshSession(jsch, USER, HOST, DEFAULT_SSH_PORT);
		session.setPassword(PASSWORD);
		session.setConfig("StrictHostKeyChecking", "no");
		try {
			session.connect();
		} catch (Exception e) {
			String msg = "連接服務器失敗!";
			logger.error(msg, e);
			throw new RuntimeException(msg);
		}

		return session;

	}

	/**
	 * 對session stack的操作
	 */
	private Object lockObj = new Object();

	/**
	 * 取出session資源
	 * 
	 * @return
	 */
	public synchronized Session pop() {
		if (sesseionProxyPool.isEmpty()) {
			logger.warn("沒有Session,使線程休眠");
			synchronized (lockObj) {
				try {
					lockObj.wait();
				} catch (InterruptedException e) {
				}
			}
			logger.warn("pop 被喚醒");
		}

		Session session = sesseionProxyPool.pop();
		return session;
	}

	/**
	 * 歸還Session 資源
	 * 
	 * @param session
	 * @param proxySession
	 */
	public void back(final Session proxySession) {
		sesseionProxyPool.push(proxySession);
		synchronized (lockObj) {
			lockObj.notify();
		}
	}

	public int getSesseionProxyPoolSize() {
		return sesseionProxyPool.size();
	}

}

5.測試

package com.spark.demo.sub.sshsession;

import java.io.IOException;
import java.io.InputStream;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

public class SshSessionTest {

	public static void main(final String[] args) throws JSchException, IOException, InterruptedException {
		SshSessionFactoryBean sshSessionFactoryBean = SshSessionFactoryBean.getInstance();

		String command = "/usr/jdk1.8.0_121/bin/java -version";

		// 啓動有個時間,間隔一下再跑命令
		Thread.sleep(5000);

		Session session = sshSessionFactoryBean.pop();

		System.out.println("getSesseionProxyPoolSize:" + sshSessionFactoryBean.getSesseionProxyPoolSize());

		Channel channel = session.openChannel("exec");
		((ChannelExec) channel).setCommand(command);
		channel.setInputStream(null);
		((ChannelExec) channel).setErrStream(System.err);
		InputStream in = channel.getInputStream();
		channel.connect();

		byte[] tmp = new byte[1024];
		while (true) {
			while (in.available() > 0) {
				int i = in.read(tmp, 0, 1024);
				if (i < 0) {
					break;
				}
				System.out.print(new String(tmp, 0, i));
			}
			if (channel.isClosed()) {
				if (in.available() > 0) {
					continue;
				}
				System.out.println("exit-status: " + channel.getExitStatus());
				break;
			}
			try {
				Thread.sleep(1000);
			} catch (Exception ee) {
			}
		}
		channel.disconnect();
		session.disconnect();

		System.out.println("getSesseionProxyPoolSize:" + sshSessionFactoryBean.getSesseionProxyPoolSize());

		System.out.println("exit....................");
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章