Статьи

Удаленное управление сервером Raspberry Pi

Облачная IoT-платформа Alibaba позволяет реализовать проникновение псевдо-интрасети и удаленное управление серверами Raspberry Pi без публичных IP-адресов. Эта статья использует удаленное управление сервером Raspberry Pi в качестве примера, чтобы продемонстрировать реализацию проникновения псевдо-Интранет и предоставляет примеры кода разработки.

Фон

Предположим, что вы хотите создать сервер на основе Raspberry Pi для выполнения некоторых простых задач, таких как запуск скрипта и загрузка файла. Однако, если у сервера Raspberry Pi нет общедоступного IP-адреса, вы можете управлять этим сервером только в офисе или дома. Если вы используете другие инструменты проникновения в интрасеть, у вас могут возникнуть частые проблемы с отключением. Для решения этой проблемы вы можете использовать функцию RRPC ( Восстановить удаленный вызов процедур или восстановить RPC) Alibaba Cloud IoT Platform с библиотекой JSch для реализации удаленного управления сервером Raspberry Pi.

Этапы реализации дистанционного управления

1

Используйте платформу 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 на стороне сервера и на стороне устройства.

Примечание . Примеры кода, представленные в этой статье, поддерживают только простые команды 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, вызова директив SSHShellrun 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());
        }
    }

}

На этом пока все! Надеюсь, вам понравилось, и дайте нам знать ваши мысли в комментариях ниже!