在做某個業務時,需要將文件傳輸到另一臺伺服器,指定使用sftp方式;於是在網上找到jsch包使用,原先代碼大致如下: 1 ChannelSftp channelSftp = null; 2 try { 3 JSch jsch = new JSch(); 4 jsch.getSession("ftpU ...
在做某個業務時,需要將文件傳輸到另一臺伺服器,指定使用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連接數,發現恢復正常,每次文件傳輸完畢後,會話及時結束。