使用jsch进行sftp传输时遇到的问题com.jcraft.jsch.JSchException: Session.connect: java.net.SocketException: Connection reset
在做某个业务时,需要将文件传输到另一台服务器,指定使用sftp方式;于是在网上找到jsch包使用,原先代码大致如下:
1 ChannelSftp channelSftp = null; 2 try { 3 JSch jsch = new JSch(); 4 jsch.getSession("ftpUserName", "ftpHost", 22); 5 Session sshSession = jsch.getSession("ftpUserName", "ftpHost", 22); 6 System.out.println("Session created."); 7 sshSession.setPassword("ftpPassword"); 8 Properties sshConfig = new Properties(); 9 sshConfig.put("StrictHostKeyChecking", "no"); 10 sshSession.setConfig(sshConfig); 11 sshSession.connect(); 12 System.out.println("Session connected."); 13 System.out.println("Opening Channel."); 14 Channel channel = sshSession.openChannel("sftp"); 15 channel.connect(); 16 channelSftp = (ChannelSftp) channel; 17 18 //todo 上传文件 19 } catch (Exception e) { 20 //todo 异常处理 21 } finally { 22 //断开sftp连接 23 if (channelSftp != null) { 24 channelSftp.disconnect(); 25 } 26 }
程序运行后大约过了几天,发现日志产生大量连接异常的日志,主要是两类异常:SocketException和NoRouteToHostException
com.jcraft.jsch.JSchException: Session.connect: java.net.SocketException: Connection reset at com.jcraft.jsch.Session.connect(Session.java:534) at com.jcraft.jsch.Session.connect(Session.java:162) at sun.reflect.GeneratedMethodAccessor37.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)
com.jcraft.jsch.JSchException: java.net.NoRouteToHostException: Cannot assign requested address (Address not available) at com.jcraft.jsch.Util.createSocket(Util.java:344) at com.jcraft.jsch.Session.connect(Session.java:194) at com.jcraft.jsch.Session.connect(Session.java:162) at sun.reflect.GeneratedMethodAccessor37.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: java.net.NoRouteToHostException: Cannot assign requested address (Address not available) at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:589) at java.net.Socket.connect(Socket.java:538) at java.net.Socket.<init>(Socket.java:434) at java.net.Socket.<init>(Socket.java:211) at com.jcraft.jsch.Util.createSocket(Util.java:338) ... 17 common frames omitted
首先我百度了下,找到有人提到修改socket连接的配置,于是我尝试修改:
1. 修改端口释放后的等待时间为30s。echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
2. 修改/proc/sys/net/ipv4/tcp_tw_reuse为1。echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
3. 修改/proc/sys/net/ipv4/tcp_tw_recycle为1。echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
4.增加可用端口 vim /etc/sysctl.conf
net.ipv4.ip_local_port_range = 10000 65000 -----意味着10000~65000端口可用
改完后,执行命令“sysctl -p”使参数生效。
修改配置后,重启程序,发现过了一天,仍然出现上述的问题;继续百度查看,发现有人提到连接关闭的问题,于是查了下服务器的ssh连接数
netstat |grep ssh |wc -l
这一查,发现有5500多个连接未关闭(随着时间推移,空闲连接会自动关闭),于是问题的原因大致定位到了:程序生成连接的速度大于连接释放的速度;但是问题的根源还是在于,程序没有正确关闭连接。
不仅仅要断开channel,还要断开session。修改后的代码如下:
1 ChannelSftp channelSftp = null; 2 try { 3 JSch jsch = new JSch(); 4 jsch.getSession("ftpUserName", "ftpHost", 22); 5 Session sshSession = jsch.getSession("ftpUserName", "ftpHost", 22); 6 System.out.println("Session created."); 7 sshSession.setPassword("ftpPassword"); 8 Properties sshConfig = new Properties(); 9 sshConfig.put("StrictHostKeyChecking", "no"); 10 sshSession.setConfig(sshConfig); 11 sshSession.connect(); 12 System.out.println("Session connected."); 13 System.out.println("Opening Channel."); 14 Channel channel = sshSession.openChannel("sftp"); 15 channel.connect(); 16 channelSftp = (ChannelSftp) channel; 17 18 //todo 上传文件 19 } catch (Exception e) { 20 //todo 异常处理 21 } finally { 22 //断开sftp连接 23 if (channelSftp != null) { 24 try { 25 channelSftp.disconnect(); 26 //关闭会话 27 Session session = channelSftp.getSession(); 28 if (session != null) { 29 session.disconnect(); 30 } 31 } catch (Exception e) { 32 //todo 异常处理 33 } 34 } 35 }
重新运行,观察ssh连接数,发现恢复正常,每次文件传输完毕后,会话及时结束。