1. JSch介绍
JSch 是SSH2的纯Java实现。JSch允许连接到sshd的服务器并使用端口转发、X11转发、文件传输等,并且可以将其功能集成到自己的Java程序中。
2. 实现原理
- 根据远程主机的IP地址、用户名和密码,建立会话session
- 设置用户信息(包括密码和Userinfo),然后连接session,
getSession()
只是创建一个session,需要设置必要的认证信息之后,调用connect()
才能建立连接
- 设置channel上需要远程执行的shell脚本,连接channel,就可以远程执行该shell脚本,调用
openChannel(String type)
可以在session上打开指定类型的channel。该channel只是被初始化,使用前需要先调用connect()
进行连接
- 可以读取远程执行shell脚本的输出,然后依次断开channel和session的连接
3. 代码工程
实现文件上传到服务器,服务器下载文件以及执行服务器命令
引入pom
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<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>JSchDemo</artifactId>
<version>1.0</version>
<name>JSchDemo</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.4.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>2.4.5</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>Remote实体类
1
2
3
4
5
6
7
8
9
10
public class Remote {
private String host;
private final int port = 22;
private String user;
private String password;
private final String identity = "~/.ssh/id_rsa";
private String passphrase;
}JschUtils工具类
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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493package org.example;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
/**
* @author xiaoyuge
*/
public class JschUtils {
public static final int SESSION_TIMEOUT = 30000;
public static final int CONNECT_TIMEOUT = 3000;
public static Session getSession(Remote remote) throws JSchException {
JSch jSch = new JSch();
if (Files.exists(Paths.get(remote.getIdentity()))) {
jSch.addIdentity(remote.getIdentity(), remote.getPassphrase());
}
Session session = jSch.getSession(remote.getUser(), remote.getHost(), remote.getPort());
session.setPassword(remote.getPassword());
session.setConfig("StrictHostKeyChecking", "no");
return session;
}
public static List<String> remoteExecute(Session session, String command) throws JSchException {
log.debug(">> {}", command);
List<String> resultLines = new ArrayList<>();
ChannelExec channel = null;
try {
channel = openExecChannel(session);
channel.setCommand(command);
InputStream input = channel.getInputStream();
channel.connect(CONNECT_TIMEOUT);
try {
BufferedReader inputReader = new BufferedReader(new InputStreamReader(input));
String inputLine;
while ((inputLine = inputReader.readLine()) != null) {
log.debug(" {}", inputLine);
resultLines.add(inputLine);
}
} finally {
if (input != null) {
try {
input.close();
} catch (Exception e) {
log.error("JSch inputStream close error:", e);
}
}
}
} catch (IOException e) {
log.error("IOException:", e);
} finally {
disconnect(channel);
}
return resultLines;
}
/**
* scp file to remote server
*
* @param session session
* @param source local file
* @param destination remote target file
* @return file size
*/
public static long scpTo(Session session, String source, String destination) {
FileInputStream fileInputStream = null;
ChannelExec channel = null;
try {
channel = openExecChannel(session);
OutputStream out = channel.getOutputStream();
InputStream in = channel.getInputStream();
boolean ptimestamp = false;
String command = "scp";
if (ptimestamp) {
command += " -p";
}
command += " -t " + destination;
channel.setCommand(command);
channel.connect(CONNECT_TIMEOUT);
if (checkAck(in) != 0) {
return -1;
}
File _lfile = new File(source);
if (ptimestamp) {
command = "T " + (_lfile.lastModified() / 1000) + " 0";
// The access time should be sent here,
// but it is not accessible with JavaAPI ;-<
command += (" " + (_lfile.lastModified() / 1000) + " 0\n");
out.write(command.getBytes());
out.flush();
if (checkAck(in) != 0) {
return -1;
}
}
//send "C0644 filesize filename", where filename should not include '/'
long fileSize = _lfile.length();
command = "C0644 " + fileSize + " ";
if (source.lastIndexOf('/') > 0) {
command += source.substring(source.lastIndexOf('/') + 1);
} else {
command += source;
}
command += "\n";
out.write(command.getBytes());
out.flush();
if (checkAck(in) != 0) {
return -1;
}
//send content of file
fileInputStream = new FileInputStream(source);
byte[] buf = new byte[1024];
long sum = 0;
while (true) {
int len = fileInputStream.read(buf, 0, buf.length);
if (len <= 0) {
break;
}
out.write(buf, 0, len);
sum += len;
}
//send '\0'
buf[0] = 0;
out.write(buf, 0, 1);
out.flush();
if (checkAck(in) != 0) {
return -1;
}
return sum;
} catch (JSchException e) {
log.error("scp to caught jsch exception, ", e);
} catch (IOException e) {
log.error("scp to caught io exception, ", e);
} catch (Exception e) {
log.error("scp to error, ", e);
} finally {
closeInputStream(fileInputStream);
disconnect(channel);
}
return -1;
}
/**
* scp remote file to local
*
* @param session session
* @param source remote file
* @param destination local file
* @return file size
*/
public static long scpFrom(Session session, String source, String destination) {
FileOutputStream fileOutputStream = null;
ChannelExec channel = null;
try {
channel = openExecChannel(session);
channel.setCommand("scp -f " + source);
OutputStream out = channel.getOutputStream();
InputStream in = channel.getInputStream();
channel.connect();
byte[] buf = new byte[1024];
//send '\0'
buf[0] = 0;
out.write(buf, 0, 1);
out.flush();
while (true) {
if (checkAck(in) != 'C') {
break;
}
}
//read '644 '
in.read(buf, 0, 4);
long fileSize = 0;
while (true) {
if (in.read(buf, 0, 1) < 0) {
break;
}
if (buf[0] == ' ') {
break;
}
fileSize = fileSize * 10L + (long) (buf[0] - '0');
}
String file = null;
for (int i = 0; ; i++) {
in.read(buf, i, 1);
if (buf[i] == (byte) 0x0a) {
file = new String(buf, 0, i);
break;
}
}
// send '\0'
buf[0] = 0;
out.write(buf, 0, 1);
out.flush();
// read a content of lfile
if (Files.isDirectory(Paths.get(destination))) {
fileOutputStream = new FileOutputStream(destination + File.separator + file);
} else {
fileOutputStream = new FileOutputStream(destination);
}
long sum = 0;
while (true) {
int len = in.read(buf, 0, buf.length);
if (len <= 0) {
break;
}
sum += len;
if (len >= fileSize) {
fileOutputStream.write(buf, 0, (int) fileSize);
break;
}
fileOutputStream.write(buf, 0, len);
fileSize -= len;
}
return sum;
} catch (JSchException e) {
log.error("scp to caught jsch exception, ", e);
} catch (IOException e) {
log.error("scp to caught io exception, ", e);
} catch (Exception e) {
log.error("scp to error, ", e);
} finally {
closeOutputStream(fileOutputStream);
disconnect(channel);
}
return -1;
}
/**
* remote edit
*
* @param session session
* @param source target file
* @param process edit command collect
* @return isSuccess
*/
private static boolean remoteEdit(Session session, String source, Function<List<String>, List<String>> process) {
InputStream in = null;
OutputStream out = null;
try {
String fileName = source;
int index = source.lastIndexOf('/');
if (index >= 0) {
fileName = source.substring(index + 1);
}
//backup source
remoteExecute(session, String.format("cp %s %s", source, source + ".bak." + System.currentTimeMillis()));
//scp from remote
String tmpSource = System.getProperty("java.io.tmpdir") + session.getHost() + "-" + fileName;
scpFrom(session, source, tmpSource);
in = new FileInputStream(tmpSource);
//edit file according function process
String tmpDestination = tmpSource + ".des";
out = new FileOutputStream(tmpDestination);
List<String> inputLines = new ArrayList<>();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String inputLine = null;
while ((inputLine = reader.readLine()) != null) {
inputLines.add(inputLine);
}
List<String> outputLines = process.apply(inputLines);
for (String outputLine : outputLines) {
out.write((outputLine + "\n").getBytes());
out.flush();
}
//scp to remote
scpTo(session, tmpDestination, source);
return true;
} catch (Exception e) {
log.error("remote edit error, ", e);
return false;
} finally {
closeInputStream(in);
closeOutputStream(out);
}
}
/**
* update file
*
* @param session session
* @param in file stream
* @param directory local dir
* @param fileName FTP server file name:xxx.txt ||xxx.txt.zip
*/
public static boolean uploadFile(Session session, InputStream in, String directory, String fileName) {
log.info(">>>>>>>>uploadFile--ftp start>>>>>>>>>>>>>");
ChannelSftp channel = null;
try {
channel = openSftpChannel(session);
channel.connect(CONNECT_TIMEOUT);
String[] folders = directory.split("/");
try {
for (int i = 0; i < folders.length; i++) {
if (i == 0 && folders[i].length() == 0) {
channel.cd("/");
} else if (folders[i].length() > 0) {
try {
channel.cd(folders[i]);
} catch (SftpException e) {
channel.mkdir(folders[i]);
channel.cd(folders[i]);
}
}
}
} catch (SftpException e) {
log.error("ftp create file fail" + directory, e);
return false;
}
try {
channel.put(in, fileName);
} catch (SftpException e) {
log.error("sftp error-->" + e.getMessage(), e);
return false;
}
log.info(">>>>>>>>uploadFile--ftp upload end>>>>>>>>>>>>>");
log.info(">>>>>>>>ftp upload dir:{},filename:{}>>>>>>>>>>>>>", directory, fileName);
return true;
} catch (JSchException e) {
log.error("JSch error-->" + e.getMessage(), e);
return false;
} finally {
closeInputStream(in);
disconnect(channel);
}
}
/**
* @param channel sftp connect
* @param directory
* @param fileName
* @return
*/
public static InputStream stream(ChannelSftp channel, String directory, String fileName) {
try {
channel.connect(CONNECT_TIMEOUT);
InputStream inputStream = channel.get(directory + "/" + fileName);
log.info(">>>>>>>>ftp file directory:{},filename:{}>>>>>>>>>>>>>", directory, fileName);
return inputStream;
} catch (SftpException e) {
log.error("sftp error-->" + e.getMessage());
return null;
} catch (JSchException e) {
log.error("JSch error-->" + e.getMessage());
return null;
}
}
/**
* ftp delete remote file
*
* @param session session
* @param directory directory
* @param fileName filename
* @return is Success
*/
public static boolean deleteFile(Session session, String directory, String fileName) {
log.info(">>>>>>>>deleteFile--ftp delete file end>>>>>>>>>>>>>");
ChannelSftp channel = null;
try {
channel = openSftpChannel(session);
channel.connect(CONNECT_TIMEOUT);
channel.rm(directory + "/" + fileName);
log.info(">>>>>>>>deleteFile--deletefile end>>>>>>>>>>>>>");
log.info(">>>>>>>>ftp delete file directory:{},filename:{}>>>>>>>>>>>>>", directory, fileName);
} catch (SftpException e) {
log.error("ftp create directory fail" + directory);
return false;
} catch (JSchException e) {
log.error("JSch error-->" + e.getMessage());
return false;
} finally {
disconnect(channel);
}
return true;
}
public static Channel openChannel(Session session, String type) throws JSchException {
if (!session.isConnected()) {
session.connect(SESSION_TIMEOUT);
}
return session.openChannel(type);
}
public static ChannelSftp openSftpChannel(Session session) throws JSchException {
return (ChannelSftp) openChannel(session, "sftp");
}
public static ChannelExec openExecChannel(Session session) throws JSchException {
return (ChannelExec) openChannel(session, "exec");
}
/**
* disconnect
*
* @param session
*/
public static void disconnect(Session session) {
if (session != null) {
if (session.isConnected()) {
try {
session.disconnect();
log.info("session disconnect successfully");
} catch (Exception e) {
log.error("JSch session disconnect error:", e);
}
}
}
}
/**
* close connection
*
* @param channel channel connection
*/
public static void disconnect(Channel channel) {
if (channel != null) {
if (channel.isConnected()) {
try {
channel.disconnect();
log.info("channel is closed already");
} catch (Exception e) {
log.error("JSch channel disconnect error:", e);
}
}
}
}
public static int checkAck(InputStream in) throws IOException {
int b = in.read();
// b may be 0 for success,
// 1 for error,
// 2 for fatal error,
// -1
if (b == 0) {
return b;
}
if (b == -1) {
return b;
}
if (b == 1 || b == 2) {
StringBuilder sb = new StringBuilder();
int c;
do {
c = in.read();
sb.append((char) c);
}
while (c != '\n');
if (b == 1) { // error
log.debug(sb.toString());
}
if (b == 2) { // fatal error
log.debug(sb.toString());
}
}
return b;
}
public static void closeInputStream(InputStream in) {
if (in != null) {
try {
in.close();
} catch (IOException e) {
log.error("Close input stream error." + e.getMessage());
}
}
}
public static void closeOutputStream(OutputStream out) {
if (out != null) {
try {
out.close();
} catch (IOException e) {
log.error("Close output stream error." + e.getMessage());
}
}
}
}启动类Application
1
2
3
4
5
6
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}测试
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
76package org.example;
import com.alibaba.fastjson.JSONObject;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import org.junit.After;
import org.junit.Before;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
public class AppTest {
private Logger log = LoggerFactory.getLogger(getClass());
Session session;
public void before() throws JSchException {
Remote remote= new Remote();
remote.setHost("xxxx");
remote.setUser("xxxx");
remote.setPassword("xxxx");
session= JschUtils.getSession(remote);
}
public void after(){
JschUtils.disconnect(session);
}
public void remoteExecute() throws JSchException {
List<String> list= JschUtils.remoteExecute(session,"ls");
System.out.println(JSONObject.toJSON(list));
}
public void uploadFile() throws JSchException, FileNotFoundException {
String filestr ="D:\\tmp\\test\\file_utils\\file1.txt";
File file = new File(filestr);
InputStream in = new FileInputStream(file);
String directory="/root/test";
String fileName="test.txt";
boolean flag= JschUtils.uploadFile(session,in,directory,fileName);
System.out.println(flag);
}
public void deleteFile() throws JSchException, FileNotFoundException {
String directory="/root/test";
String fileName="test.txt";
boolean flag= JschUtils.deleteFile(session,directory,fileName);
System.out.println(flag);
}
public void scpFrom() throws JSchException, FileNotFoundException {
String source="/root/test/file1.txt";
String destination ="D:\\tmp\\scfFrom.txt";
long filesize= JschUtils.scpFrom(session,source,destination);
System.out.println(filesize);
}
public void scpTo() throws JSchException, FileNotFoundException {
String filestr ="D:\\tmp\\test\\file_utils\\file1.txt";
String destination="/root/test/file1.txt";
long filesize= JschUtils.scpTo(session,filestr,destination);
System.out.println(filesize);
}
}