网络篇12 | SSH2协议应用,禁SFTP子模式实现文件传输
网络篇12 | SSH2的应用
- 解决的业务问题
- 协议选定
- SSH2(Secure Shell 2,目前基本用这个)
- SSH1(Secure Shell 1)
- Telnet
- 代码实现
- 落地方案1:ganymed-ssh2
- maven坐标
- 关键源代码
- 技术效果验证
- 连接高版本OpenSSH报错分析
- 落地方案2:jsch
- maven坐标
- jsch客户端功能
- 不墨迹,直接上源码
- Controller层(接口文件内容)
- service 层(将文件落地,连接管道,发送sh命令,执行curl回调)
- JSchUtils管道连接的工具类是灵魂,就免费赠送大家了(画重点)
- 进阶应用
- 方案1:rsync调试实现文件的传输
- 1. 使用此组件前简单验证,安装sshpass工具,并尝试两台机器之间的文件交互。
- 2. 上传文件,结合expect实现免密效果
- 3. 下载文件,结合expect实现免密效果
- 方案2:sftp调试实现文件的传输
- 方案3:scp模式
解决的业务问题
在禁使用STP子模式的情况下,研发一套“平台”级的程序,实现与10万多台的Linux机器交互,包含但不限于通道连接、shell命令执行、文件传输等。
具体的业务需求为:获取Linux主机上的shadow文件、以及上传shell的基线检测脚本并将检查结果下载到系统中。
协议选定
SSH2、SSH1和Telnet都是网络协议,它们在远程通信和数据传输方面发挥着重要作用,但各自具有不同的特点和用途。以下是对这三个协议的详细解释:
SSH2(Secure Shell 2,目前基本用这个)
定义与特点:
SSH2(Secure Shell 2)是一种用于计算机网络的加密协议,旨在在不安全的网络中安全地传输数据。它是SSH协议的升级版本,提供了更强大的加密和认证机制。
SSH2协议通过加密通道来传输数据,防止数据在传输过程中被窃听、篡改或伪造。它采用了公钥加密、对称加密和消息认证码等多种加密技术,保障了数据的机密性、完整性和可靠性。
SSH2还提供了强大的身份认证机制,包括密码认证、公钥认证和基于密钥的认证等,确保了通信双方的身份合法性和安全性。
SSH2协议支持多种加密算法和密钥长度,可以根据实际需求选择合适的加密方式,提高了系统的灵活性和安全性。
应用场景:
SSH2协议被广泛用于远程登录和管理服务器、网络设备。
它也用于安全文件传输等场景,支持端口转发和X11转发等功能,可以实现安全的远程访问和数据传输。
SSH1(Secure Shell 1)
定义与特点:
SSH1是SSH协议的第一个版本,由芬兰赫尔辛基工业大学的研究员Tatu Ylönen设计。
它最初提出的目的是替代非安全的Telnet、rsh、rexec等远程Shell协议。
SSH1虽然在一定程度上提高了远程通信的安全性,但随着时间的推移,其安全性漏洞逐渐暴露,因此被SSH2所取代。
应用现状:
由于SSH1存在安全漏洞,目前已被SSH2广泛替代。然而,在一些旧系统或特定场景下,SSH1可能仍然被使用,但建议尽快升级到SSH2以提高安全性。
Telnet
定义与特点:
Telnet是一种网络协议,用于远程登录到另一台计算机并执行命令。
它可以在本地与远程计算机之间建立一个虚拟终端会话,使用户可以像在本地计算机上一样操作远程计算机。
使用Telnet连接远程计算机需要知道目标计算机的IP地址或域名,并开启远程登录服务。
安全隐患:
Telnet协议本身并不提供加密功能,因此数据传输过程中存在被窃听的风险。
这使得Telnet在安全性要求较高的场景下不再适用,而是被SSH等更安全的协议所取代。
代码实现
落地方案1:ganymed-ssh2
maven坐标
中央仓库最新版本262,2014年的版本,有点旧了。
<dependency><groupId>ch.ethz.ganymed</groupId><artifactId>ganymed-ssh2</artifactId><version>262</version>
</dependency>
为什么选型这个第三方的实现? 此处是基于mqcloud搜狐开源的中间件管理框架,他集成这种技术用来管理rocketmq的broker与ns的发布与运维管理。
关键源代码
技术效果验证
以下效果是本机环境,通过传入ip与账户,获取服务器上jdk的版本号功能(远程登录服务器,并执行获取jdk版本的shell命令:source /etc/profile;javap -version)
以下是通过类似xshell的客户端工具,root登录后获取jdk的版本号:
到此为止,你认为成功了吗?我们发到正式环境试一下吧。
由于正式环境OpenSSH版本比较高,所以直接报错了。(原因是算法不对等)
连接高版本OpenSSH报错分析
正式环境服务器的ssh版本,OpenSSH_9.6p1支持的算法
[myhome@paas-core-hu5-a-2 ~]$ ssh -V
OpenSSH_9.6p1, OpenSSL 1.1.1k FIPS 25 Mar 2021
[myhome@paas-core-hu5-a-2 ~]$ ssh -Q kex
diffie-hellman-group1-sha1
diffie-hellman-group14-sha1
diffie-hellman-group14-sha256
diffie-hellman-group16-sha512
diffie-hellman-group18-sha512
diffie-hellman-group-exchange-sha1
diffie-hellman-group-exchange-sha256
ecdh-sha2-nistp256
ecdh-sha2-nistp384
ecdh-sha2-nistp521
curve25519-sha256
curve25519-sha256@libssh.org
sntrup761x25519-sha512@openssh.com
[myhome@paas-core-hu5-a-2 ~]$ ssh -Q mac
hmac-sha1
hmac-sha1-96
hmac-sha2-256
hmac-sha2-512
hmac-md5
hmac-md5-96
umac-64@openssh.com
umac-128@openssh.com
hmac-sha1-etm@openssh.com
hmac-sha1-96-etm@openssh.com
hmac-sha2-256-etm@openssh.com
hmac-sha2-512-etm@openssh.com
hmac-md5-etm@openssh.com
hmac-md5-96-etm@openssh.com
umac-64-etm@openssh.com
umac-128-etm@openssh.com
[myhome@paas-core-hu5-a-2 ~]$ ssh -Q cipher
3des-cbc
aes128-cbc
aes192-cbc
aes256-cbc
aes128-ctr
aes192-ctr
aes256-ctr
aes128-gcm@openssh.com
aes256-gcm@openssh.com
chacha20-poly1305@openssh.com
开发环境服务器的ssh版本,OpenSSH_7.4p1支持的算法
[root@AC-SEC-01 ~]# ssh -V
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017
[root@AC-SEC-01 ~]# ssh -Q kex
diffie-hellman-group1-sha1
diffie-hellman-group14-sha1
diffie-hellman-group14-sha256
diffie-hellman-group16-sha512
diffie-hellman-group18-sha512
diffie-hellman-group-exchange-sha1
diffie-hellman-group-exchange-sha256
ecdh-sha2-nistp256
ecdh-sha2-nistp384
ecdh-sha2-nistp521
curve25519-sha256
curve25519-sha256@libssh.org
gss-gex-sha1-
gss-group1-sha1-
gss-group14-sha1-
[root@AC-SEC-01 ~]# ssh -Q mac
hmac-sha1
hmac-sha1-96
hmac-sha2-256
hmac-sha2-512
hmac-md5
hmac-md5-96
hmac-ripemd160
hmac-ripemd160@openssh.com
umac-64@openssh.com
umac-128@openssh.com
hmac-sha1-etm@openssh.com
hmac-sha1-96-etm@openssh.com
hmac-sha2-256-etm@openssh.com
hmac-sha2-512-etm@openssh.com
hmac-md5-etm@openssh.com
hmac-md5-96-etm@openssh.com
hmac-ripemd160-etm@openssh.com
umac-64-etm@openssh.com
umac-128-etm@openssh.com
[root@AC-SEC-01 ~]# ssh -Q cipher
3des-cbc
blowfish-cbc
cast128-cbc
arcfour
arcfour128
arcfour256
aes128-cbc
aes192-cbc
aes256-cbc
rijndael-cbc@lysator.liu.se
aes128-ctr
aes192-ctr
aes256-ctr
aes128-gcm@openssh.com
aes256-gcm@openssh.com
chacha20-poly1305@openssh.com
服务器的生效配置 sudo cat /etc/ssh/sshd_config
MACs hmac-sha1
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521
Ciphers aes128-ctr,aes192-ctr,aes256-ctr
客户端算法 ganymed-ssh2 262版本(从源码中抓取出来)
MACs getMacList() "hmac-sha1-96", "hmac-sha1", "hmac-md5-96", "hmac-md5"
KexAlgorithms getDefaultClientKexAlgorithmList() "diffie-hellman-group-exchange-sha1", "diffie-hellman-group14-sha1", "diffie-hellman-group1-sha1" ecdh-sha2-nistp256
Ciphers aes128-ctr,aes192-ctr,aes256-ctr,blowfish-ctr,3des-ctr,3des-cbc
结论就是KexAlgorithms算法不匹配
因为客户端diffie系列算法不安全,所有高版本的OpenSSH服务端已经默认去除了这些算法,如果想用也是可以的,就是容易被攻击,比如直接在服务端的sshd_config的KexAlgorithms 最后一个算法加上客户端你想用的算法,比如, “diffie-hellman-group1-sha1”。
为了安全,我只能使用加密等级比较高的非对称椭圆算法,一个选择是在262版本上继续二开,加入高版本的算法支持,或者找找别人遇到类似的问题,他升级了此包。当然还有一条路,换第三方实现,我最终换成了jsch的实现。
落地方案2:jsch
maven坐标
<dependency><groupId>com.github.mwiede</groupId><artifactId>jsch</artifactId><version>0.2.19</version>
</dependency>
jsch客户端功能
session: 一个基础的会话通道,通常用来建立一个基础的交互式命令行会话。
shell: 提供了一个登录shell的交互环境,允许用户执行命令。
exec: 用于执行远程命令,不提供交互式shell。
x11: 允许X11图形应用通过SSH隧道进行转发。
auth-agent@openssh.com: 用于SSH认证代理转发,这样远程服务器可以使用本地机器的SSH密钥来访问其他系统。
direct-tcpip: 用于创建一个TCP/IP端口转发通道,允许通过SSH隧道转发TCP流量。
forwarded-tcpip: 类似于direct-tcpip,但是它是由远程服务器发起的连接请求到指定的地址和端口。
sftp: 用于安全文件传输协议(SFTP),允许文件传输。useWriteFlushWorkaround是一个配置选项,可能与某些SFTP服务器的兼容性有关。
subsystem: 允许运行注册了的子系统,如sftp子系统。
direct-streamlocal@openssh.com: 用于本地流套接字的直接连接,通常用于非TCP协议或特定于平台的服务。
不墨迹,直接上源码
Controller层(接口文件内容)
@Data
public class ScriptUploadRequest {private String fileContent;private String filePath;private String fileName;private String resourceHost;private String bastionHost;
}@Slf4j
@Api(value = "代理层通用接口", tags = {"代理层通用接口"})
@RestController
@RequestMapping("/api/xshell")
public class BasicLineController {@Autowiredprivate basicLineService basicLineService;@ApiOperation(value = "脚本上传")@PostMapping("/script/upload")public AjaxResult uploadScript(@RequestBody ScriptUploadRequest suRequest) {if(ChannelCache.isNotEmpty(testResult))return testResult;return basicLineService.uploadScript(suRequest);}
}
service 层(将文件落地,连接管道,发送sh命令,执行curl回调)
/*** 上传脚本,接受应用系统推送过来的文件内容,将文本内容写入本地,并连上Linux通道,通过curl方式回调getfile接口抓取并写入到主机的本机上* @param suRequest fileContent 脚本文件内容* @param suRequest filePath 脚本文件在前置服务器上保存的目录* @param suRequest fileName 脚本文件的文件名(很重要)* @param suRequest resourceHost 主机的IP* @param suRequest bastionHost 堡垒机的IP(没有的堡垒机的兄弟们,直接忽略此IP)* **/public AjaxResult uploadScript(ScriptUploadRequest suRequest) {try {// 获取请求参数
// MultipartFile file = suRequest.getFile(); //也可以传文件嘛String fileContent = AESUtil.decryptStr(suRequest.getFileContent());String filePath = suRequest.getFilePath();String fileName = suRequest.getFileName();String resourceHost = suRequest.getResourceHost();String bastionHost = suRequest.getBastionHost();ResHosts resHosts = ProxyUtils.getResHosts(proxyProperties, bastionHost, resourceHost);String cacheKey = ProxyUtils.getCacheKey(resHosts);// 1. 保存文件到本地Path path = Paths.get(filePath, fileName);Files.createDirectories(path.getParent());// 创建父目录(如果它们不存在)Files.write(path, fileContent.getBytes());// 写入内容到文件,如果文件不存在则创建文件ChannelShell channelShell = JSchUtils.remoteConnectShell(cacheKey, resHosts, proxyProperties, proxyProperties.getSocketOverwrite());log.info("通道连接...cacheKey={},channel={}" , cacheKey, channelShell.isConnected());Thread.sleep(2000);if(!channelShell.isConnected()){return AjaxResult.error("连接失败");}// 获取当前接口的URL与端口String callUrl = JSchUtils.getCallUrl(proxyProperties, Constants.callGetFile);String command1 = "mkdir -p "+Constants.dirScript;String command2 = "curl --location "+callUrl+"'?filePath="+Constants.dirproxyscript+"&filename="+fileName+"' --header 'access_token: "+Constants.access_token+"' -o "+Constants.dirScript + File.separator + fileName;String command3 = "chmod +x "+Constants.dirScript + File.separator + fileName;String command = command1+"&&"+command2+"&&"+command3;AjaxResult ajaxResult = JSchUtils.remoteExecuteShell(ProxyUtils.getCacheKey(resHosts), resHosts, command, proxyProperties);log.info("指令执行(2)...cacheKey={},channel={},msg={}" , cacheKey, channelShell.isConnected(), ajaxResult.get(AjaxResult.MSG_TAG));return ajaxResult;} catch (IOException | InterruptedException e) {log.error("连接失败", e);return AjaxResult.error("连接失败"+e.getMessage());}}
JSchUtils管道连接的工具类是灵魂,就免费赠送大家了(画重点)
为了这里面的实现,花了3天没日没夜的各种姿势尝试,相信遇到一个技术卡点的技术爱好者,又缺少对方系统的各种信息,这个滋味应该很酸爽,放出来的目的也是为了让有相同近况的,少走一些弯路。
package com.why.proxy.app.util;import com.why.app.entity.ResHosts;
import com.why.app.service.ChannelCache;
import com.why.ganymed.util.Result;
import com.why.ganymed.vo.AjaxResult;
import com.why.proxy.app.ProxyProperties;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;@Slf4j
public class JSchUtils {public static ChannelShell remoteConnectShell(String cacheKey, ResHosts resHosts, ProxyProperties proxyProperties, String overwrite) {if("1".equals(overwrite)){ChannelCache.disconnectChannelShell(cacheKey);ChannelCache.disconnectSessionShell(cacheKey);}//1. socket连接Session session = connectSessionShell(cacheKey, resHosts, proxyProperties);//2. channel连接ChannelShell channelShell = connectChannelShell(cacheKey, proxyProperties, session);return channelShell;}public static AjaxResult remoteExecuteShell(String cacheKey, ResHosts resHosts, String command, ProxyProperties proxyProperties) {return remoteExecute4aShell(cacheKey, resHosts, command, proxyProperties);}public static AjaxResult remoteExecute4aShell(String cacheKey, ResHosts resHosts, String command, ProxyProperties proxyProperties) {//1. command命令发送ChannelShell channel = getChannelShell(cacheKey, resHosts, proxyProperties, 3);if(ChannelCache.isEmpty(channel)){return AjaxResult.error("channel无法连接...");}//2. command命令发送InputStream inputStream = null;OutputStream outputStream = null;try {inputStream = channel.getInputStream();outputStream = channel.getOutputStream();// 向远程服务器发送命令byte[] cmdBytes = (command + "\n").getBytes(StandardCharsets.UTF_8);outputStream.write(cmdBytes);outputStream.flush();log.info("---------{}发送命令 >> {}", cacheKey, command);Thread.sleep(200);} catch (IOException e) {ChannelCache.disconnectChannelShell(cacheKey);ChannelCache.disconnectSessionShell(cacheKey);log.info("shell命令:"+command+" 发送失败...{}", e.getMessage(), e);return AjaxResult.error(e.getMessage());} catch (InterruptedException e) {throw new RuntimeException(e);}//3. command命令回执String inputLine = "";try {inputLine = JSchUtils.readLineWithTimeout(inputStream, channel, proxyProperties.getCommandReadTimeOut());} catch (Exception e) {ChannelCache.disconnectChannelShell(cacheKey);ChannelCache.disconnectSessionShell(cacheKey);return AjaxResult.error(e.getMessage());}if(ChannelCache.isEmpty(inputLine) || !channel.isConnected()){log.info("执行命令失败..."+command);return AjaxResult.error("4a命令执行未获取到结果...");}return AjaxResult.success(inputLine);}private static ChannelShell getChannelShell(String cacheKey, ResHosts resHosts, ProxyProperties proxyProperties, int retry) {if(retry <= 0){log.info("获取服务器连接失败...cacheKey="+cacheKey);return null;}retry --;ChannelShell channel = ChannelCache.cacheChannelShellMap.get(cacheKey);if(ChannelCache.isEmpty(channel) || !channel.isConnected()){log.info("当前channel={},cacheKey={},重连中...", ChannelCache.isEmpty(channel)?"null" :channel.isConnected(), cacheKey);remoteConnectShell(resHosts.getHost()+"+"+ resHosts.getUser(), resHosts, proxyProperties,"1");channel = ChannelCache.cacheChannelShellMap.get(cacheKey);if(ChannelCache.isEmpty(channel) || !channel.isConnected()){getChannelShell(cacheKey, resHosts, proxyProperties, retry);}}return channel;}private static ChannelShell connectChannelShell(String cacheKey, ProxyProperties proxyProperties, Session session) {ChannelShell channel = ChannelCache.cacheChannelShellMap.get(cacheKey);try {if(ChannelCache.isEmpty(channel) || !channel.isConnected()){channel = (ChannelShell) session.openChannel("shell");channel.setPty(true); // 伪终端开启,发送请求 PTY 来确保输出立即可用channel.connect(proxyProperties.getChannelConnectTimeout());log.info("channel连接成功...cacheKey={}", cacheKey);Thread.sleep(200);// JSchUtils.readConnectWithTimeout(channel, proxyProperties.getConnectReadTimeOut(), result);boolean sessionConnected = session.isConnected();boolean channelConnected = channel.isConnected();log.info("channel连接消息读取...cacheKey={},session.isConnected={},channel.isConnected={}", cacheKey, sessionConnected+"", channelConnected+"");ChannelCache.cacheChannelShellMap.put(cacheKey, channel);}else {boolean sessionConnected = session.isConnected();boolean channelConnected = channel.isConnected();log.info("channel连接成功(cache)...cacheKey={},session.isConnected={},channel.isConnected={}", cacheKey, sessionConnected+"", channelConnected+"");}} catch (Exception e) {log.info("channel连接失败...cacheKey={}", cacheKey);ChannelCache.disconnectChannelShell(cacheKey);throw new RuntimeException(e);}return channel;}private static Session connectSessionShell(String cacheKey, ResHosts resHosts, ProxyProperties proxyProperties) {Session session = ChannelCache.cacheSessionMap.get(cacheKey);try {if(ChannelCache.isEmpty(session) || !session.isConnected()){if(ChannelCache.isNotEmpty(session) && !session.isConnected()){ChannelCache.disconnectChannelShell(cacheKey);}JSch jsch = new JSch();session = jsch.getSession(resHosts.getUser(), resHosts.getHost(), resHosts.getPort());session.setPassword(resHosts.getPassword());session.setConfig("StrictHostKeyChecking", "no");session.setConfig("PreferredAuthentications", "password");session.connect(proxyProperties.getSessionConnectTimeout());ChannelCache.cacheSessionMap.put(cacheKey, session);log.info("Socket连接成功...cacheKey={}", cacheKey);}else {
// log.info("获取缓存的Session对象成功...ip={}", cacheKey);}} catch (JSchException e) {log.info("Socket连接失败...ip={}", cacheKey, e);ChannelCache.disconnectChannelShell(cacheKey);ChannelCache.disconnectSessionShell(cacheKey);throw new RuntimeException(e);}return session;}public static void readConnectWithTimeout(InputStream inputStream, ChannelShell channel, int timeout, StringBuffer result) throws InterruptedException, IOException {if(!channel.isConnected()){result.append("连接失败...");return;}String readLineStr = readLineWithTimeout(inputStream, channel, timeout);if(ChannelCache.isNotEmpty(readLineStr)){result.append(readLineStr);}//连接返回值的结束字符串
// if(readLineStr.indexOf("-bash: PROMPT_COMMAND: readonly variable") < 0){
// readConnectWithTimeout(channel, timeout, result);
// }}public static String readLineWithTimeout(InputStream inputStream, ChannelShell channel, int timeout) throws InterruptedException, IOException {BufferedReader inputReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));StringBuffer line = new StringBuffer();long start = System.currentTimeMillis();Thread thread = new Thread(() -> {try {while (channel.isConnected()) {String str = inputReader.readLine();if(ChannelCache.isEmpty(str)){return;}
// log.info("-------------------inputReader.readLine--->>" + str);line.append(str + "\n");}} catch (IOException e) {if(e instanceof java.io.InterruptedIOException){}else {log.error("Error reading from BufferedReader:", e);}}});thread.start();//3秒的超时触发,强制退出监听io事件while (System.currentTimeMillis() - start < timeout) {TimeUnit.MILLISECONDS.sleep(10);} thread.interrupt();// if(inputReader != null){
// inputReader.close();
// }
// if(inputStream != null){
// inputStream.close();
// }if(ChannelCache.isNotEmpty(line)){log.info("---------接收数据 channel.isConnected={} >> {}", channel.isConnected(), line);}return line.toString();}public static String getIpFromUrl(String urlString) {// 解析URL字符串String host = "";try {// 使用URL类解析URLURL url = new URL(urlString);host = url.getHost(); // 获取主机名部分} catch (MalformedURLException e) {System.err.println("Invalid URL: " + urlString);e.printStackTrace();}// 如果主机名包含'['和']',则可能是IPv6地址,去除方括号if (host.startsWith("[") && host.endsWith("]")) {host = host.substring(1, host.length() - 1);}return host;}public static String getCallUrl(ProxyProperties proxyProperties, String uri) {String callUrl = proxyProperties.getProxyCallUrl() + uri;log.info("回调地址...callUrl={}", callUrl);return callUrl;}/*** excute remote command** @param resHosts resHosts* @param command command* @return /* @throws JSchException /*/public static Result<?> remoteExecuteExec(ResHosts resHosts, String command, ProxyProperties proxyProperties) {Integer readTimeOut = proxyProperties.getConnectReadTimeOut();Integer sessionConnectTimeout = proxyProperties.getSessionConnectTimeout();Integer channelConnectTimeout = proxyProperties.getChannelConnectTimeout();log.info(">> {}", command);StringBuffer result = new StringBuffer();Session session = null;ChannelExec channel = null;try {JSch jsch = new JSch();// 如果需要使用私钥认证,则加载私钥if (Files.exists(Paths.get(resHosts.getIdentity()))) {jsch.addIdentity(resHosts.getIdentity(), resHosts.getPassphrase());}session = jsch.getSession(resHosts.getUser(), resHosts.getHost(), resHosts.getPort());session.setConfig("StrictHostKeyChecking", "no");session.setConfig("PreferredAuthentications", "password");MyUserInfo myUserInfo = new MyUserInfo();myUserInfo.setPassword(resHosts.getPassword());session.setUserInfo(myUserInfo);session.connect(sessionConnectTimeout);// 设置连接超时时间log.info("ip={} Session connected.", resHosts.getHost());channel = (ChannelExec)session.openChannel("exec");channel.setCommand(command);channel.setPty(true);channel.setInputStream(null);// 设置标准错误输出流channel.setErrStream(System.err);InputStream input = channel.getInputStream();// 设置通道连接超时时间log.info("ip={} Channel connecting...", resHosts.getHost());channel.connect(channelConnectTimeout);log.info("ip={} Channel connected.", resHosts.getHost());BufferedReader inputReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));String inputLine;while ((inputLine = inputReader.readLine()) != null) {log.info(" {}", inputLine);result.append(inputLine + "\n");}// 获取命令执行的退出状态log.info("ip={} ExitStatus={} result={}", resHosts.getHost(), channel.getExitStatus(), result);} catch (IOException | JSchException e) {log.info("Error executing command: {}", e.getMessage(), e);return Result.getWebErrorResult(e);} finally {if (channel != null && channel.isConnected()) {channel.disconnect();
// log.info("ip={} Channel disconnected.", resHosts.getHost());}if (session != null && session.isConnected()) {disconnect(session);
// log.info("ip={} Session disconnected.", resHosts.getHost());}}return Result.success(result);}public static Result<?> remoteExecute_1(ResHosts resHosts, String command) {log.info(">> {}", command);StringBuffer result = new StringBuffer();Session session = null;ChannelExec channel = null;try {JSch jsch = new JSch();// 如果需要使用私钥认证,则加载私钥if (Files.exists(Paths.get(resHosts.getIdentity()))) {jsch.addIdentity(resHosts.getIdentity(), resHosts.getPassphrase());}session = jsch.getSession(resHosts.getUser(), resHosts.getHost(), resHosts.getPort());session.setPassword(resHosts.getPassword());session.setConfig("StrictHostKeyChecking", "no");session.setConfig("PreferredAuthentications", "password");// 设置连接超时时间int SESSION_TIMEOUT = 5000; // 示例超时时间为5秒session.connect(SESSION_TIMEOUT);log.info("ip={} Session connected.", resHosts.getHost());channel = (ChannelExec) session.openChannel("exec");channel.setCommand(command);channel.setPty(true);// 设置标准错误输出流ByteArrayOutputStream baos = new ByteArrayOutputStream();PrintStream ps = new PrintStream(baos);channel.setErrStream(ps);InputStream input = channel.getInputStream();// 设置通道连接超时时间int CONNECT_TIMEOUT = 5000; // 示例超时时间为5秒log.info("ip={} Channel connecting...", resHosts.getHost());channel.connect(CONNECT_TIMEOUT);log.info("ip={} Channel connected.", resHosts.getHost());String errorOutput = baos.toString();log.info("ErrStream: " + errorOutput);if (ChannelCache.isNotEmpty(errorOutput)) {return Result.success(errorOutput);}BufferedReader inputReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));String inputLine;while ((inputLine = inputReader.readLine()) != null) {log.info(" {}", inputLine);result.append(inputLine + "\n");}// 获取命令执行的退出状态log.info("ip={} ExitStatus={} result={}", resHosts.getHost(), channel.getExitStatus(), result);} catch (IOException | JSchException e) {log.error("Error executing command: {}", e.getMessage(), e);return Result.getWebErrorResult(e);} finally {if (channel != null && channel.isConnected()) {channel.disconnect();log.info("ip={} Channel disconnected.", resHosts.getHost());}if (session != null && session.isConnected()) {disconnect(session);log.info("ip={} Session disconnected.", resHosts.getHost());}}return Result.success(result);}/*** get session** @param remote ssh server info* @return session* @throws JSchException /*/public static Session connectSessionShell(ResHosts remote, ProxyProperties proxyProperties) {Integer sessionConnectTimeout = proxyProperties.getSessionConnectTimeout();Session session = null;try {JSch jSch = new JSch();if (Files.exists(Paths.get(remote.getIdentity()))) {jSch.addIdentity(remote.getIdentity(), remote.getPassphrase());}session = jSch.getSession(remote.getUser(), remote.getHost(), remote.getPort());session.setPassword(remote.getPassword());session.setConfig("StrictHostKeyChecking", "no");session.setConfig("PreferredAuthentications", "password");session.connect(sessionConnectTimeout);} catch (JSchException e) {throw new RuntimeException(e);}return session;}public static Result<?> proxyExecute(ResHosts jumpHost, ResHosts targetHost, String command, ProxyProperties proxyProperties) {Integer readTimeOut = proxyProperties.getConnectReadTimeOut();Integer sessionConnectTimeout = proxyProperties.getSessionConnectTimeout();Integer channelConnectTimeout = proxyProperties.getChannelConnectTimeout();log.info(">> {}", command);StringBuilder result = new StringBuilder();Session jumpSession = null;ChannelDirectTCPIP jumpChannel = null;Session targetSession = null;ChannelExec targetChannel = null;try {// 创建跳板机的会话JSch jumpJsch = new JSch();jumpSession = jumpJsch.getSession(jumpHost.getUser(), jumpHost.getHost(), jumpHost.getPort());jumpSession.setPassword(jumpHost.getPassword());jumpSession.setConfig("StrictHostKeyChecking", "no");jumpSession.setConfig("PreferredAuthentications", "password");jumpSession.connect(sessionConnectTimeout);log.info("Connected to the jump host: {}@{}:{}", jumpHost.getUser(), jumpHost.getHost(), jumpHost.getPort());// 创建跳板机上的ChannelDirectTCPIPjumpChannel = (ChannelDirectTCPIP) jumpSession.openChannel("direct-tcpip");jumpChannel.setOrgPort(0); // 自动选择本地端口jumpChannel.setHost(targetHost.getHost()); // 设置目标主机的IP地址jumpChannel.setPort(targetHost.getPort()); // 设置目标主机端口// 连接通道jumpChannel.connect(channelConnectTimeout);log.info("TCP/IP forwarding established.");Socket socket = new Socket();socket.connect(new InetSocketAddress(jumpHost.getHost(), jumpHost.getPort()));int localPort = ((InetSocketAddress) socket.getLocalSocketAddress()).getPort();socket.close();log.info("Local port used for forwarding: {}", localPort);// 创建目标主机的会话JSch targetJsch = new JSch();targetSession = targetJsch.getSession(targetHost.getUser(), "localhost", localPort);targetSession.setPassword(targetHost.getPassword());targetSession.setConfig("StrictHostKeyChecking", "no");targetSession.setConfig("PreferredAuthentications", "password");targetSession.connect(sessionConnectTimeout);log.info("Connected to the target host via direct-tcpip.");// 创建目标主机上的ChannelExectargetChannel = (ChannelExec) targetSession.openChannel("exec");targetChannel.setCommand(command);// 请求 PTY 来确保输出立即可用targetChannel.setPty(true);InputStream input = targetChannel.getInputStream();targetChannel.connect(channelConnectTimeout);log.info("Channel connected.");BufferedReader inputReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));String inputLine;while ((inputLine = inputReader.readLine()) != null) {log.info(" {}", inputLine);result.append(inputLine).append("\n");}// 获取命令执行的退出状态int exitStatus = targetChannel.getExitStatus();log.info("Command exit status: {}", exitStatus);} catch (JSchException | IOException e) {log.error("Error executing command:", e);return Result.getWebErrorResult(e);} finally {if (targetChannel != null && targetChannel.isConnected()) {targetChannel.disconnect();log.info("Target channel disconnected.");}if (targetSession != null && targetSession.isConnected()) {targetSession.disconnect();log.info("Target session disconnected.");}if (jumpSession != null && jumpSession.isConnected()) {jumpSession.disconnect();log.info("Jump session disconnected.");}}return Result.success(result);}/*** 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, ProxyProperties proxyProperties) {Integer channelConnectTimeout = proxyProperties.getChannelConnectTimeout();FileInputStream fileInputStream = null;ChannelExec channel = null;try {channel = openExecChannel(session, proxyProperties);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(channelConnectTimeout);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 filefileInputStream = 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, ProxyProperties proxyProperties) {FileOutputStream fileOutputStream = null;ChannelExec channel = null;try {channel = openExecChannel(session, proxyProperties);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 lfileif (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, ProxyProperties proxyProperties) {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 remoteString tmpSource = System.getProperty("java.io.tmpdir") + session.getHost() + "-" + fileName;scpFrom(session, source, tmpSource, proxyProperties);in = new FileInputStream(tmpSource);//edit file according function processString 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 remotescpTo(session, tmpDestination, source, proxyProperties);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, ProxyProperties proxyProperties) {log.info(">>>>>>>>uploadFile--ftp start>>>>>>>>>>>>>");ChannelSftp channel = null;try {channel = openSftpChannel(session, proxyProperties);channel.connect(proxyProperties.getChannelConnectTimeout());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, ProxyProperties proxyProperties) {try {channel.connect(proxyProperties.getChannelConnectTimeout());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, ProxyProperties proxyProperties) {log.info(">>>>>>>>deleteFile--ftp delete file end>>>>>>>>>>>>>");ChannelSftp channel = null;try {channel = openSftpChannel(session, proxyProperties);channel.connect(proxyProperties.getChannelConnectTimeout());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, ProxyProperties proxyProperties) throws JSchException {if (!session.isConnected()) {session.connect(proxyProperties.getSessionConnectTimeout());}return session.openChannel(type);}public static ChannelSftp openSftpChannel(Session session, ProxyProperties proxyProperties) throws JSchException {return (ChannelSftp) openChannel(session, "sftp", proxyProperties);}public static ChannelExec openExecChannel(Session session, ProxyProperties proxyProperties) throws JSchException {return (ChannelExec) openChannel(session, "exec", proxyProperties);}/*** 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,// -1if (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) { // errorlog.debug(sb.toString());}if (b == 2) { // fatal errorlog.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());}}}public static void transferToFile(MultipartFile file, String filePath, String filename) {try {// 构建目标文件File targetFile = new File(filePath, filename);// 如果目标目录不存在,则创建它if (!targetFile.getParentFile().exists()) {targetFile.getParentFile().mkdirs();}// 将上传的文件保存到指定位置file.transferTo(targetFile);log.info("本地文件生成成功...filePath={},filename={}", filePath, filename);} catch (IOException e) {log.error("文件上传失败!", e);}}public static class MyUserInfo implements UserInfo, UIKeyboardInteractive {@Overridepublic String getPassword() {return passwd;}public void setPassword(String passwd) {this.passwd = passwd;}@Overridepublic boolean promptYesNo(String str) {Object[] options = {"yes", "no"};int foo = JOptionPane.showOptionDialog(null, str, "Warning", JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE, null, options, options[0]);return foo == 0;}String passwd;JTextField passwordField = new JPasswordField(20);@Overridepublic String getPassphrase() {return null;}@Overridepublic boolean promptPassphrase(String message) {return true;}@Overridepublic boolean promptPassword(String message) {
// Object[] ob = {passwordField};
// int result = JOptionPane.showConfirmDialog(null, ob, message, JOptionPane.OK_CANCEL_OPTION);
// if (result == JOptionPane.OK_OPTION) {
// passwd = passwordField.getText();
// return true;
// } else {
// return false;
// }return true;}@Overridepublic void showMessage(String message) {JOptionPane.showMessageDialog(null, message);}final GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1, 1,GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0);private Container panel;@Overridepublic String[] promptKeyboardInteractive(String destination, String name, String instruction,String[] prompt, boolean[] echo) {panel = new JPanel();panel.setLayout(new GridBagLayout());gbc.weightx = 1.0;gbc.gridwidth = GridBagConstraints.REMAINDER;gbc.gridx = 0;panel.add(new JLabel(instruction), gbc);gbc.gridy++;gbc.gridwidth = GridBagConstraints.RELATIVE;JTextField[] texts = new JTextField[prompt.length];for (int i = 0; i < prompt.length; i++) {gbc.fill = GridBagConstraints.NONE;gbc.gridx = 0;gbc.weightx = 1;panel.add(new JLabel(prompt[i]), gbc);gbc.gridx = 1;gbc.fill = GridBagConstraints.HORIZONTAL;gbc.weighty = 1;if (echo[i]) {texts[i] = new JTextField(20);} else {texts[i] = new JPasswordField(20);}panel.add(texts[i], gbc);gbc.gridy++;}if (JOptionPane.showConfirmDialog(null, panel, destination + ": " + name,JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) {String[] response = new String[prompt.length];for (int i = 0; i < prompt.length; i++) {response[i] = texts[i].getText();}return response;} else {return null; // cancel}}}
}
进阶应用
方案1:rsync调试实现文件的传输
我们是部署了一套代码,由代码获取到主机的ssh连接shell命令执行对象,故执行一些特殊命令会弹出密码验证,那么直接发送两条命令,或者使用&连接命令是否可行?答案是不行,但Linux提供了一些工具,比如sshpass,支持所谓的“免密”的登陆效果。
1. 使用此组件前简单验证,安装sshpass工具,并尝试两台机器之间的文件交互。
sshpass --version
sudo yum install sshpass
--往前置机发送文件
sshpass -p '密码' rsync -avz --progress -e "ssh -p 22050" /apps/system_lae.sh root@42.10.10.101:/apps/system_lae.sh
--从前置机抓取文件
sshpass -p '密码' rsync -avz --progress -e "ssh -p 22050" root@42.10.10.101:/apps/system_lae.sh /apps/system_lae.sh
2. 上传文件,结合expect实现免密效果
mkdir /apps/script
touch /apps/script/system_lae_push.exp
chmod +x /apps/script/system_lae_push.expcat > /apps/script/system_lae_push.exp <<EOF
#!/usr/bin/expect
spawn rsync -avz --progress -e "ssh -p 22" myhome@10.10.10.101:/apps/proxy/script/system_lae.sh /apps/script/system_lae.sh
expect "myhome@10.10.10.101's password:"
send -- "Wzw123!\r"
expect {"myhome@10.10.10.101's password:" { send -- "Wzw123!\r"; exp_continue }"Permission denied" { puts "Error: Permission denied."; exit }eof { puts "Error: Unexpected end of file."; exit }
}
interact
EOF
cat /apps/script/system_lae_push.exp | expect
chmod +x /apps/script/system_lae.sh
3. 下载文件,结合expect实现免密效果
------生产(下载文件)
cat > /apps/script/system_lae_pull.exp <<EOF
#!/usr/bin/expect
spawn rsync -avz --progress -e "ssh -p 22" /apps/system_lae.sh myhome@10.10.10.101:/apps/system_lae.sh
expect "myhome@10.10.10.101's password:"
send -- "Wzw123!\r"
expect {"myhome@10.10.10.101's password:" { send -- "Wzw123!\r"; exp_continue }"Permission denied" { puts "Error: Permission denied."; exit }eof { puts "Error: Unexpected end of file."; exit }
}
interact
EOF
cat /apps/script/system_lae_pull.exp | expect
方案2:sftp调试实现文件的传输
这种方式,同样存在要输入密码的问题,可结合sshpass解决。
以下是简单列出sftp的使用方式,这种方式很常见,唯一的区别是将ftp搭建在前置服务器上,将命令发送到上千台主机服务器,反向连接到此ftp上并执行主机上的文件上传与下载操作。
#sftp
1、查看sftp服务端grep -i 'sftp-server' /etc/ssh/sshd_config2、创建sftp专用帐号2.1 生成账户sudo useradd -m -s /sbin/nologin sftpusersudo passwd sftpuser sftpuser9988992.2 配置sftp专用帐号访问权限,编辑 /etc/ssh/sshd_config 文件,添加或修改以下配置行: Match User sftpuserChrootDirectory /appsForceCommand internal-sftp2.3 设置了 /apps 目录的所有者为 root,组为 sftpuser,并赋予了正确的读写权限。sudo mkdir -p /appssudo chown root:sftpuser /appssudo chmod 775 /apps2.4 重启 SSH 服务sudo systemctl restart sshd#3、测试机验证sftp:
sftp sftpuser@60.204.111.222
sftpuser998899
put /apps/data/linux/system_lae.sh /apps/system_lae.sh
exit
方案3:scp模式
此模式与rsync类似,如果是正向上传,直接调用客户端的接口接口,如果反向让资源主机访问前置服务,那么就类似于方案1了。
相关文章:
网络篇12 | SSH2协议应用,禁SFTP子模式实现文件传输
网络篇12 | SSH2的应用 解决的业务问题协议选定SSH2(Secure Shell 2,目前基本用这个)SSH1(Secure Shell 1)Telnet 代码实现落地方案1:ganymed-ssh2maven坐标关键源代码技术效果验证连接高版本OpenSSH报错分…...
MetaGPT实现多动作Agent
异步编程学习链接 智能体 LLM观察思考行动记忆 多智能体 智能体环境SOP评审路由订阅经济 教程地址 多动作的agent的本质是react,这包括了think(考虑接下来该采取啥动作)act(采取行动) 在MetaGPT的examples/write_…...
docker更新镜像源
常用的国内 Docker 镜像加速器 1. 阿里云镜像加速器:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors 2. 腾讯云镜像加速器:https://cloud.tencent.com/document/product/457/33221 3. 网易云镜像加速器:https://hub-mirror…...
TSmaster Trace 窗口
文章目录 1、设置显示刷新率2、设置显示报文格式3、报文过滤3.1 基于报文通道3.2 基于报文 ID过滤3.3 基于过滤字符串(FilterString)过滤 4、信号的折叠与展开5、固定显示和时间顺序显示切换6、关闭窗体 1、设置显示刷新率 为了降低软件 CPU 占用率&…...
【Python模拟websocket登陆-拆包封包】
Python模拟websocket登陆-拆包封包 解析一个网站获取wss原始数据拆包wss数据封包wss数据发送接收websocket的常驻后台脚本总结 解析一个网站 这里所用的网站是我一个内测的网站,主要手段是chrome devtools,用得很多,但我玩的不深,…...
速盾:海外服务器使用CDN加速有什么好处?
随着互联网的快速发展和全球化的需求增加,海外服务器的使用已经成为许多公司和个人的首选。与此同时,为了提供更好的用户体验和更快的网页加载速度,使用CDN(内容分发网络)加速海外服务器已经成为一个普遍的选择。CDN可…...
windows系统中实现对于appium的依赖搭建
Node.js:Appium是基于Node.js的,因此需要安装Node.js。可以从Node.js官网下载并安装。 Java Development Kit (JDK):用于Android应用的自动化测试,需要安装JDK。可以从Oracle官网下载并安装。 Android SDK:进行Andro…...
使用MATLAB进行字符串处理
MATLAB是一个强大的数学和计算机科学的软件工具包。它拥有一个灵活的字符串处理工具,可以用于处理和转换不同格式的字符串,例如,数值、日期、时间等。本文将探讨如何使用MATLAB进行字符串处理,以及如何利用它来解决实际问题。 在…...
Sourcetree登录GitLab账号
1. 在GitLab上创建个人访问令牌 在gitlab中点击右上角的头像图标,选择设置进入 Access Tokens(访问令牌) 页面填写令牌名称和到期时间,指定Scopes(范围)。一般选择read_repository和api点击 Create person…...
Linux进阶:软件安装、网络操作、端口、进程等
软件安装 yum 和 apt 均需要root权限 CentOS系统使用: yum [install remove search] [-y] 软件名称 install 安装remove 卸载search 搜索-y,自动确认 Ubuntu系统使用 apt [install remove search] [-y] 软件名称 install 安装remove 卸载search 搜索-y&…...
光猫、路由器、交换机之连接使用(Connection and Usage of Optical Cats, Routers, and Switches)
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…...
2025蓝桥杯(单片机)备赛--扩展外设之超声波测距原理与应用(十一)
1 超声波测距原理 接收器接到超声波的时间差。超声波发射器想某一方向发射波,再发射时刻开始计时 超声波在空气中传播,遇到障碍物则返回,超声波接收器收到反射波,立即停止计时。 SOR4原理: 通过IO口(TRIG…...
分布式数据库中间件可以用在哪些场景呢
在数字化转型的浪潮中,企业面临着海量数据的存储、管理和分析挑战。华为云分布式数据库中间件(DDM)作为一款高效的数据管理解决方案,致力于帮助企业在多个场景中实现数据的高效管理和应用,提升业务效率和用户体验。九河…...
MyBatis-Plus分页插件IPage用法
首先就是service接口需要继承IService<entity> 然后就是业务类实现类中需要继承ServiceImpl<Mapper,entity> Mapper正常写法,继承baseMapepr<entity> IPage的使用方式 QueryWrapper<MdSaleDayPhone> queryWrappernew QueryWrapper<>…...
使用MATLAB进行遗传算法设计
遗传算法是一种基于自然进化的计算方法,在解决各种优化问题方面具有广泛的应用。MATLAB作为一种强大的数学软件,可以方便快捷地实现遗传算法,并且通过可视化的方式直观地展现算法运行过程和结果。本文将介绍使用MATLAB进行遗传算法设计的步骤…...
mindtorch study
安装 pip install mindtorch mindtorch 用于帮助迁移torch模型到mindspore 大部分都可以直接把mindtorch的torch搞成torch,就和以前的代码一致,注意下面 只有静态图有点点差异 step也有差异 自定义优化器就麻烦了。 pyttorch还是牛啊 并行计算还是用的…...
java八股-SpringCloud微服务-Eureka理论
文章目录 SpringCloud架构Eureka流程Nacos和Eureka的区别是?CAP定理Ribbon负载均衡策略自定义负载均衡策略如何实现?本章小结 SpringCloud架构 Eureka流程 服务提供者向Eureka注册服务信息服务消费者向注册中心拉取服务信息服务消费者使用负载均衡算法挑…...
2024信创数据库TOP30之蚂蚁集团OceanBase
数据库作为存储、管理和分析这些数据的关键工具,其地位自然不言而喻。随着信息技术的日新月异,数据库技术也在不断演进,以满足日益复杂多变的市场需求。近日,备受瞩目的“2024信创数据库TOP30”榜单由DBC联合CIW/CIS权威发布&…...
查找redis数据库的路径
Redis 数据库的路径通常由配置文件中的 dir 参数指定 查找 Redis 配置文件: Redis 配置文件通常命名为 redis.conf。您可以在以下位置查找它: /etc/redis/redis.conf(Linux 系统上的常见位置)/usr/local/etc/redis/redis.conf&…...
DrugLLM——利用大规模语言模型通过 Few-Shot 生成生物制药小分子
摘要 小分子由于能够与特定的生物靶点结合并调节其功能,因此在药物发现领域发挥着至关重要的作用。根据美国食品和药物管理局(FDA)过去十年的审批记录,小分子药物占所有获批上市药物的 76%。小分子药物的特点是合成相对容易&…...
【蓝桥杯C/C++】翻转游戏:多种实现与解法解析
博客主页: [小ᶻZ࿆] 本文专栏: 蓝桥杯C/C 文章目录 💯题目💯问题分析解法一:减法法解法二:位运算解法解法三:逻辑非解法解法四:条件运算符解法解法五:数组映射法不同解法的比较…...
【AI系统】核心计算之矩阵乘
核心计算之矩阵乘 AI 模型中往往包含大量的矩阵乘运算,该算子的计算过程表现为较高的内存搬移和计算密度需求,所以矩阵乘的效率是 AI 芯片设计时性能评估的主要参考依据。本文我们一起来看一下矩阵乘运算在 AI 芯片的具体过程,了解它的执行性…...
Vue.js 自定义指令:从零开始创建自己的指令
vue使用directive 前言vue2使用vue3使用 前言 关于使用自定义指令在官网中是这样描述的 vue2:对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。 vue3:自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑。 在 Vue.js 中使用自定义指令…...
策略模式
定义:即定义一系列的算法,算法1,算法2,...,算法n,把他们封装起来,使他们可以相互替换。 优点:使得一个类的行为或者其算法可以在运行时改变,而且使用Context类的人在外部…...
性能优化--CPU微架构
一 指令集架构 Intel X86, ARM v8, RISC-V 是当今广泛使用的指令架构的实例。 大多数现代架构可以归类为基于通用寄存器的加载和存储型架构,在这种架构下,操作数倍明确指定,只能使用夹在和存储指令访问内存。除提供基本的功能之外,…...
在 Sanic 框架中实现高效内存缓存的多种方法
在使用 Sanic 框架开发 Web 应用时,我们可以通过内存缓存来提升应用的性能,减少对数据库或其他外部服务的频繁请求。下面提供一些在 Sanic 中实现内存缓存的基本方法。 使用 Python 内置的 functools.lru_cache 如果你的缓存需求比较简单,且…...
Mac 环境变量配置基础教程
MacOS 下一般配置有多个 Shell,如 Bash、ZSH 等,不同的 Shell 其创建 Terminal 时使用的环境变量配置文件也不尽相同,但一般都会读取并执行脚本文件 /etc/profile 来加载系统级环境变量,而用户级别环境变量,一般都会在…...
Qt如何屏蔽工具栏(QToolBar)自动折叠功能
最近发现Qt上工具栏一行放不下的时候,会自动折叠起来。当用户点击展开功能的小三角按钮时,工具栏会展开成多行。这个功能本身没什么问题,但是当工具栏展开的时候,鼠标光标一旦不小心移动到了工具栏外面,这时候…...
【数据分享】中国统计摘要(1978-2024)
数据介绍 《中国统计摘要(1978 - 2024)》犹如一部浓缩的历史巨著,承载着中国几十年来的发展轨迹与辉煌成就。它是由国家统计局精心编纂的重要资料,为我们全方位地展现了中国在经济、社会、民生等各个领域的深刻变革。 这本统计摘…...
unity运行状态下移动、旋转、缩放控制模型
demo地址:https://download.csdn.net/download/elineSea/90017272 unity2021以上版本用下面的插件 https://download.csdn.net/download/elineSea/90017305...
湖北省建设安全协会网站/seo计费系统源码
一、序言 Opencv中提供Stitcher类,实现了多图像自动拼接,Opencv是开源的,程序实现的源代码都在Opencv安装文件中,以及Opencv提供的函数查询手册和Opencv教程都可以在官网上下载的到,通过这些文档和代码&#x…...
学校网站制作方案/腾讯企点客服
一. JDBC的批量插入使用MySQL的Batch批量处理,JDBC驱动版本需要5.1.13或以上测试使用的JDBC驱动版本:mysql-connector-java-5.1.18-bin(一开始我忽略掉这个jar包的条件要求了,使用的是mysql-connector-java-5.1.6-bin.jar在批处理插入时候效率和普通一样)测试表结构如下:CREATE…...
网吧手机网站模版/站长之家ip地址查询
// // Name: CameraClass.h // Des: 一个封装了实现虚拟摄像机的类的头文件 // 2013年 3月10日 Create by 浅墨 //#pragma once#include <d3d9.h> #include <d3dx9.h>class CameraClass { private://成员变量的声明D3DXVECTOR3 m_vRightVector; …...
wordpress的数据库在哪里/交友网站有哪些
iis跨域的问题很郁闷,估计程序因为不是php或.net的原因吧! 1)ngnix是反向代理服务器,它是代理,只是个向内网服务器传话的话筒;可以解决跨域; 2)IIS是微软公司的Web服务器。主要用来…...
摄像头监控设备企业网站模板/新网站怎么推广
简述mybatis框架可以通过xml文件的形式和注解的形式省去大量工作,这里我们使用注解的形式完成配置。流程新建一个项目,把使用xml的项目的配置文件全部拷过来,dao的xml配置可以删去。然后在dao接口的方法上方写上注解,注解里是sql语…...
乐都企业网站建设哪家好/百度账户托管公司
1.了解单点登录 需要源码 SSO 主要特点是: SSO 应用之间使用 Web 协议(如 HTTPS) ,并且只有一个登录入口. SSO 的体系中有下面三种角色: 1) User(多个) 2) Web 应用(多个) 3) SSO 认证中心(一个) 2.SSO 实现包含以下三个原则 1) 所有的登录都在 SSO 认证中心进…...