Облачная IoT-платформа Alibaba позволяет реализовать проникновение псевдо-интрасети и удаленное управление серверами Raspberry Pi без публичных IP-адресов. Эта статья использует удаленное управление сервером Raspberry Pi в качестве примера, чтобы продемонстрировать реализацию проникновения псевдо-Интранет и предоставляет примеры кода разработки.
Фон
Предположим, что вы хотите создать сервер на основе Raspberry Pi для выполнения некоторых простых задач, таких как запуск скрипта и загрузка файла. Однако, если у сервера Raspberry Pi нет общедоступного IP-адреса, вы можете управлять этим сервером только в офисе или дома. Если вы используете другие инструменты проникновения в интрасеть, у вас могут возникнуть частые проблемы с отключением. Для решения этой проблемы вы можете использовать функцию RRPC ( Восстановить удаленный вызов процедур или восстановить RPC) Alibaba Cloud IoT Platform с библиотекой JSch для реализации удаленного управления сервером Raspberry Pi.
Этапы реализации дистанционного управления
Используйте платформу IoT для реализации удаленного управления сервером Raspberry Pi, выполнив следующие действия:
- Вызовите интерфейс RRPC платформы IoT на компьютере, чтобы отправить директиву SSH.
- После получения этой директивы IoT Platform отправляет эту директиву SSH на сервер Raspberry Pi с использованием протокола MQTT.
- Сервер запускает директиву SSH.
- Сервер инкапсулирует результат этой директивы SSH в ответ RRPC и сообщает его платформе IoT с использованием протокола MQTT.
- Платформа IoT возвращает этот ответ RRPC на компьютер.
Примечание . Время ожидания RRPC составляет пять секунд. Если сервер не получает ответ в течение пяти секунд, возникает ошибка тайм-аута. Если выполнение вашей директивы занимает много времени, вы можете игнорировать ошибку тайм-аута.
Загрузите SDK и демо
Чтобы реализовать удаленное управление этим сервером Raspberry Pi на платформе IoT, необходимо разработать SDK на стороне сервера и SDK на стороне устройства.
- Установите серверный SDK платформы IoT на свой компьютер. Вы можете использовать демонстрацию Java SDK на стороне сервера для выполнения разработки на стороне сервера.
- Установите SDK на стороне устройства IoT на вашем сервере Raspberry Pi. Вы можете использовать демонстрацию Java SDK на стороне устройства для выполнения разработки на стороне устройства.
В следующих разделах приведены примеры разработки SDK на стороне сервера и на стороне устройства.
Примечание . Примеры кода, представленные в этой статье, поддерживают только простые команды Linux, такие как username, touch и mv, и не поддерживают сложные директивы, такие как редактирование файлов. Для запуска сложных директив вы можете написать свой собственный код.
Разработка SDK на стороне устройства
После загрузки и установки SDK на стороне устройства и загрузки SDK Demon необходимо добавить зависимости проекта и следующие файлы Java.
Проект можно экспортировать в виде jar-пакета для запуска на Raspberry Pi.
1. Добавьте зависимости в pom.xml
файл.
<! -- Device-side SDK -->
<dependency>
<groupId>com.aliyun.alink.linksdk</groupId>
<artifactId>iot-linkkit-java</artifactId>
<version>1.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.40</version>
<scope>compile</scope>
</dependency>
<! -- SSH client -->
<! -- https://mvnrepository.com/artifact/com.jcraft/jsch -->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
2. Добавьте SSHShell.java
файл для запуска SSH directives
.
public class SSHShell {
private String host;
private String username;
private String password;
private int port;
private Vector<String> stdout;
public SSHShell(final String ipAddress, final String username, final String password, final int port) {
this.host = ipAddress;
this.username = username;
this.password = password;
this.port = port;
this.stdout = new Vector<String>();
}
public int execute(final String command) {
System.out.println("ssh command: " + command);
int returnCode = 0;
JSch jsch = new JSch();
SSHUserInfo userInfo = new SSHUserInfo();
try {
Session session = jsch.getSession(username, host, port);
session.setPassword(password);
session.setUserInfo(userInfo);
session.connect();
Channel channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
channel.setInputStream(null);
BufferedReader input = new BufferedReader(new InputStreamReader(channel.getInputStream()));
channel.connect();
String line = null;
while ((line = input.readLine()) ! = null) {
stdout.add(line);
}
input.close();
if (channel.isClosed()) {
returnCode = channel.getExitStatus();
}
channel.disconnect();
session.disconnect();
} catch (JSchException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return returnCode;
}
public Vector<String> getStdout() {
return stdout;
}
}
3. Добавьте SSHUserInfo.java
файл для проверки SSH
учетной записи и пароля.
public class SSHUserInfo implements UserInfo {
@Override
public String getPassphrase() {
return null;
}
@Override
public String getPassword() {
return null;
}
@Override
public boolean promptPassphrase(final String arg0) {
return false;
}
@Override
public boolean promptPassword(final String arg0) {
return false;
}
@Override
public boolean promptYesNo(final String arg0) {
if (arg0.contains("The authenticity of host")) {
return true;
}
return false;
}
@Override
public void showMessage(final String arg0) {
}
}
4. Добавьте Device.java
файл для установления MQTT
соединения.
public class Device {
/**
* Establish a connection
*
* @param productKey: product key
* @param deviceName: device name
* @param deviceSecret: device secret
* @throws InterruptedException
*/
public static void connect(String productKey, String deviceName, String deviceSecret) throws InterruptedException {
// Initialization parameters
LinkKitInitParams params = new LinkKitInitParams();
// Set MQTT initialization parameters
IoTMqttClientConfig config = new IoTMqttClientConfig();
config.productKey = productKey;
config.deviceName = deviceName;
config.deviceSecret = deviceSecret;
params.mqttClientConfig = config;
// Configure initialization device certificate information and pass the following:
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey;
deviceInfo.deviceName = deviceName;
deviceInfo.deviceSecret = deviceSecret;
params.deviceInfo = deviceInfo;
// Initialization
LinkKit.getInstance().init(params, new ILinkKitConnectListener() {
public void onError(AError aError) {
System.out.println("init failed !! code=" + aError.getCode() + ",msg=" + aError.getMsg() + ",subCode="
+ aError.getSubCode() + ",subMsg=" + aError.getSubMsg());
}
public void onInitDone(InitResult initResult) {
System.out.println("init success !!") ;
}
});
// Make sure that you perform the following steps only after the initialization is completed. You can adjust the latency as needed for this step
Thread.sleep(2000);
}
/**
* Publish messages
*
* @param topic: the topic of the message to be sent
* @param payload: the content of the message to be sent
*/
public static void publish(String topic, String payload) {
MqttPublishRequest request = new MqttPublishRequest();
request.topic = topic;
request.payloadObj = payload;
request.qos = 0;
LinkKit.getInstance().getMqttClient().publish(request, new IConnectSendListener() {
@Override
public void onResponse(ARequest aRequest, AResponse aResponse) {
}
@Override
public void onFailure(ARequest aRequest, AError aError) {
}
});
}
/**
* Subscribe to messages
*
* @param topic: the topic of the subscribed message
*/
public static void subscribe(String topic) {
MqttSubscribeRequest request = new MqttSubscribeRequest();
request.topic = topic;
request.isSubscribe = true;
LinkKit.getInstance().getMqttClient().subscribe(request, new IConnectSubscribeListener() {
@Override
public void onSuccess() {
}
@Override
public void onFailure(AError aError) {
}
});
}
/**
* Unsubscribe from messages
*
* @param topic: the topic of the unsubscribed message
*/
public static void unsubscribe(String topic) {
MqttSubscribeRequest request = new MqttSubscribeRequest();
request.topic = topic;
request.isSubscribe = false;
LinkKit.getInstance().getMqttClient().unsubscribe(request, new IConnectUnscribeListener() {
@Override
public void onSuccess() {
}
@Override
public void onFailure(AError aError) {
}
});
}
/**
* Disconnect
*/
public static void disconnect() {
// Deinitialize
LinkKit.getInstance().deinit();
}
}
5. Добавьте SSHDevice.java
файл. SSHDevice.java
Файл включает в себя основной метод. Этот файл используется для получения директив revert-RPC, вызова директив SSHShell
run SSH
и возврата ответа revert-RPC. Информация о сертификате устройства (ProductKey, DeviceName и DeviceSecret), а также SSH
учетная запись и пароль требуются в SSHDevice.java
.
public class SSHDevice {
// ===================The list of required parameters begins here===========================
// productKey
private static String productKey = "";
//
private static String deviceName = "";
// deviceSecret
private static String deviceSecret = "";
// The topic of the communication message (You do not need to create or define one. It is directly available.)
private static String rrpcTopic = "/sys/" + productKey + "/" + deviceName + "/rrpc/request/+";
// The domain name or IP that SSH will access
private static String host = "127.0.0.1";
// SSH username
private static String username = "";
// SSH password
private static String password = "";
// SSH port number
private static int port = 22;
// ===================The list of required parameters ends here===========================
public static void main(String[] args) throws InterruptedException {
// Listen to downlink data
registerNotifyListener();
// Establish the connection
Device.connect(productKey, deviceName, deviceSecret);
// Subscribe to topics
Device.subscribe(rrpcTopic);
}
public static void registerNotifyListener() {
LinkKit.getInstance().registerOnNotifyListener(new IConnectNotifyListener() {
@Override
public boolean shouldHandle(String connectId, String topic) {
// Only process messages with a specific topic
if (topic.contains("/rrpc/request/")) {
return true;
} else {
return false;
}
}
@Override
public void onNotify(String connectId, String topic, AMessage aMessage) {
// Receive revert-RPC requests and return revert-RPC response
try {
// Run remote commands
String payload = new String((byte[]) aMessage.getData(), "UTF-8");
SSHShell sshExecutor = new SSHShell(host, username, password, port);
sshExecutor.execute(payload);
// Obtain command echo
StringBuffer sb = new StringBuffer();
Vector<String> stdout = sshExecutor.getStdout();
for (String str : stdout) {
sb.append(str);
sb.append("\n");
}
// Return the echo to the server side
String response = topic.replace("/request/", "/response/");
Device.publish(response, sb.toString());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
@Override
public void onConnectStateChange(String connectId, ConnectState connectState) {
}
});
}
}
Разработка SDK на стороне сервера
После загрузки и установки SDK на стороне сервера и загрузки SDK Demon необходимо добавить зависимости проекта и следующие файлы Java.
1. Добавьте зависимости в pom.xml
файл.
<! -- Server-side SDK -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-iot</artifactId>
<version>6.5.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>3.5.1</version>
</dependency>
<! -- commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.8</version>
</dependency>
2. Добавьте OpenApiClient.java
файл для вызова открытых интерфейсов на платформе IoT.
public class OpenApiClient {
private static DefaultAcsClient client = null;
public static DefaultAcsClient getClient(String accessKeyID, String accessKeySecret) {
if (client ! = null) {
return client;
}
try {
IClientProfile profile = DefaultProfile.getProfile("cn-shanghai", accessKeyID, accessKeySecret);
DefaultProfile.addEndpoint("cn-shanghai", "cn-shanghai", "Iot", "iot.cn-shanghai.aliyuncs.com");
client = new DefaultAcsClient(profile);
} catch (Exception e) {
System.out.println("create Open API Client failed !! exception:" + e.getMessage());
}
return client;
}
}
3. Добавьте SSHCommandSender.java
файл. SSHCommandSender.java
Файл включает в себя основной метод. Файл используется для отправки SSH
директив и получения ответа на SSH
директивы. Ваша учетная запись AccessKey, информация о сертификате устройства (ProductKey и DeviceName) и SSH
директивы требуются в SSHCommandSender.java
.
public class SSHCommandSender {
// ===================The list of required parameters begins here===========================
// User account AccessKey
private static String accessKeyID = "";
// User account AccesseKeySecret
private static String accessKeySecret = "";
// productKey
private static String productKey = "";
// deviceName
private static String deviceName = "";
// ===================The list of required parameters ends here===========================
public static void main(String[] args) throws ServerException, ClientException, UnsupportedEncodingException {
// Linux remote command
String payload = "uname -a";
// Build revert-RPC requests
RRpcRequest request = new RRpcRequest();
request.setProductKey(productKey);
request.setDeviceName(deviceName);
request.setRequestBase64Byte(Base64.encodeBase64String(payload.getBytes()));
request.setTimeout(5000);
// Obtain the server-side request client
DefaultAcsClient client = OpenApiClient.getClient(accessKeyID, accessKeySecret);
// Initiate a revert-RPC request
RRpcResponse response = (RRpcResponse) client.getAcsResponse(request);
// Process the revert-RPC response
// response.getSuccess() indicates that the revert-RPC request is sent successfully. It does not indicate that the device has successfully received the request or that the response is successful.
// Determination should be made based on RrpcCode. For more information, visit https://help.aliyun.com/document_detail/69797.html
if (response ! = null && "SUCCESS".equals(response.getRrpcCode())) {
// Echo
System.out.println(new String(Base64.decodeBase64(response.getPayloadBase64Byte()), "UTF-8"));
} else {
// Echo fails and RrpcCode is printed
System.out.println(response.getRrpcCode());
}
}
}
На этом пока все! Надеюсь, вам понравилось, и дайте нам знать ваши мысли в комментариях ниже!