DaryaDu 17 февраля 2020

Как управлять компьютером со смартфона по Wi-Fi: пишем Android-приложение на С#

Пример использования инструмента Xamarin.Forms для создания пользовательского интерфейса системного администратора, удаленно управляющего рабочими станциями с телефона.
3
7225

Введение

Неправильное использование рабочих компьютеров компании является обычной практикой среди пользователей во многих компаниях, где отсутствуют надлежащие механизмы мониторинга. Это приводит к напрасной трате ресурсов и снижает скорость достижения общих целей организации. Таким образом, для достижения желаемых результатов необходим эффективный механизм, с помощью которого системный администратор может отслеживать и контролировать рабочие станции сотрудников.

Целью рассматриваемого проекта является разработка приложения на основе Xamarin.Forms, которое позволит системному администратору проводить удаленное наблюдение и контроль рабочих станций на рабочем месте. Проект будет включать разработку двух приложений: одно будет работать на рабочих станциях, другое – на мобильном телефоне администратора. Внедрение механизма позволит администратору не только следить за действиями пользователей, но и даст возможность удаленно контролировать конкретные рабочие станции.

Идея проекта

Итак, в проекте мы создадим два приложения, серверное и клиентское.

Серверное приложение будет постоянно работать на каждой рабочей станции. Приложение сервера будет связано с клиентским приложением через Wi-Fi соединение. Программа делает скриншот активности пользователя по требованию администратора и отправляет его в клиентское приложение. Серверное приложение будет принимать, интерпретировать и выполнять команды, полученные от мобильного (клиентского) приложения.

Клиентское приложение на Xamarin.Forms – Android-приложение на смартфоне администратора, предоставляющее интерфейс, через который администратор может управлять серверным приложением. С помощью клиентского приложения администратор даёт команду серверному приложению сделать скриншот и отправить изображение на смартфон. С помощью клиентского приложения администратор может контролировать каждую рабочую станцию, отправлять сообщения, выключать или переводить компьютер в спящий режим и т. д.

1. Серверное приложение

1.1. Создаем консольное приложение

Начнём с создания проекта консольного приложения с .NET Framework.

1.2. Добавляем соответствующее пространство имен

Создаем экземпляр TcpClient и TcpListener. Объявляем строку локальной переменной с именем ipString.

        public static TcpClient client;  
private static TcpListener listener;  
private static string ipString;  
    

1.3. Узнаем IP адрес вашей машины

Напишем метод внутри функции Main, который будет перехватывать и возвращать IP-адрес машины.

        IPAddress[] localIp = Dns.GetHostAddresses(Dns.GetHostName());  
foreach(IPAddress address in localIp) {  
    if (address.AddressFamily == AddressFamily.InterNetwork) {  
        ipString = address.ToString();  
    }  
}  

    

1.4. Слушаем порт 1234

Теперь мы готовы принимать сообщения, например, по порту 1234. Этот порт и IP-адрес текущего компьютера будет использоваться в качестве конечных точек. Тогда мы сможем связаться с клиентом по TCP.

        IPEndPoint ep = new IPEndPoint(IPAddress.Parse(ipString), 1234);  
listener = new TcpListener(ep);  
listener.Start();  
Console.WriteLine(@"  
===================================================  
Started listening requests at: {0}:{1}  
===================================================",  
ep.Address, ep.Port);  
client = listener.AcceptTcpClient();  
Console.WriteLine("Connected to client!" + " \n"); 
    

1.5. Создаем соединение с клиентом

Создадим функцию приема и обработки сообщения о том, что рабочая станция должна быть переведена в спящий режим. В качестве команды от клиента мы должны получить строку SLP2.

        while (client.Connected) {  
    try {  
        const int bytesize = 1024 * 1024;  
        byte[] buffer = new byte[bytesize];  
        string x = client.GetStream().Read(buffer, 0, bytesize).ToString();  
        var data = ASCIIEncoding.ASCII.GetString(buffer);  
        if (data.ToUpper().Contains("SLP2")) {  
            Console.WriteLine("Pc is going to Sleep Mode!" + " \n");  
            Sleep();  
        }  
    } catch (Exception exc) {  
        client.Dispose();  
        client.Close();  
    }  
}  

    

1.6. Пишем метод Sleep

Расширяем пространство имён проекта. Добавляем внутри функции Main метод Sleep:

        using System.Windows.Forms;  
void Sleep() {  
    Application.SetSuspendState(PowerState.Suspend, true, true);  
}  

    

2. Клиентское приложение

2.1. Создаем проект Xamarin.Forms

Открываем Visual Studio и переходим в New Project-> Cross-platform-> Xamarin.Forms-> Blank app. Даем ему имя, например, XamarinFormsClient.

2.2. Создаем класс соединения

Далее в нашем проекте нужно определить класс соединения для создания экземпляра TCP Client. Создаем новый класс с именем Connection.cs и записываем в него следующий код.

Connection.cs
        using System;    
using System.Collections.Generic;    
using System.Net.Sockets;    
    
namespace XamarinForms.Client    
{    
    public class Connection    
    {    
        private static Connection _instance;    
        public static Connection Instance    
        {    
            get    
            {    
                if (_instance == null) _instance = new Connection();    
                return _instance;    
            }    
        }    
        public TcpClient client { get; set; }    
    }    
}    

    

2.3. Создание пользовательского интерфейса

Нам нужны два редактируемых текстовых поля для IP-адреса и порта, и одна кнопка для соединения с сервером. Откроем файл макета MainPage и заменим код на следующий:

        <?xml version="1.0" encoding="utf-8" ?>    
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"    
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"    
             xmlns:local="clr-namespace:XamarinForms.Client"    
             x:Class="XamarinForms.Client.MainPage">    
    <StackLayout>    
    <Label Text="Connect to Server"     
           FontSize="Medium"    
           HorizontalOptions="Center" />    
    <Entry x:Name="IPAddress" Placeholder="IP Address"/>    
    <Entry x:Name="Port" Placeholder="Port Number"/>    
    <Button x:Name="Connect" Text="Connect" Clicked="Connect_Clicked"/>    
    </StackLayout>    
</ContentPage>    

    

2.4. Описываем метод соединения

Опишем взаимосвязь элементов управления пользовательского интерфейса с классом MainPage. Далее свяжемся с приложением сервера, используя IP-адрес и порт рабочей станции.

MainPage.xaml
        using System;    
using System.Net.Sockets;    
using Xamarin.Forms;    
    
    
namespace XamarinForms.Client    
{    
    public partial class MainPage : ContentPage    
    {    
        public MainPage()    
        {    
            InitializeComponent();    
        }    
    
        private async void Connect_Clicked(object sender, EventArgs e)    
        {    
            try    
            {    
                TcpClient client = new TcpClient();     
                    await client.ConnectAsync(IPAddress.Text, Convert.ToInt32(Port.Text));    
                if (client.Connected)    
                {    
                    Connection.Instance.client = client;    
                    Application.Current.MainPage = new NavigationPage(new OperationsPage());    
                        
                   await DisplayAlert("Connected", "Connected to server successfully!", "Ok");    
                }    
                else    
                {    
                    await DisplayAlert("Error", "Connection unsuccessful!", "Ok");    
                }    
            }    
            catch (Exception ex)    
            {    
                await DisplayAlert("Error", ""+ex.ToString(), "Ok");    
            }    
        }    
    }    
}    

    

2.5. Обновление класса приложения

Открываем файл App.xaml внутри этого класса и добавляем следующий код в конструктор класса App.

        MainPage = new NavigationPage(new MainPage()); 
    

3. Проверка соединения

Давайте проверим соединение сервера и клиентского приложения, которое мы только что создали. Основной модуль запуска приложения Xamarin.Forms отобразит главную страницу. Запустим серверное приложение, скопируем IP-адрес и порт рабочей станции из серверного приложения. Поместим в клиентское приложение и нажимаем Connect.

4. Работаем с функцией скриншота и выключения

Поговорим о том, как будет работать функция скриншота. Клиент запрашивает скриншот, отправляет команду в виде строки "TSC1", на рабочей станции серверное приложение делает скриншот. Размер изображения может быть большим, поэтому мы преобразуем данные снимка экрана в пакеты байтового типа и отправляем клиентскому приложению. Когда клиент получит пакеты байтового типа, он преобразует их в исходную форму.

Добавим функцию выключения рабочей станции. Если клиент отправляет команду “SHTD3” серверному приложению, сервер выполнит функцию выключения компьютера.

5. Возвращаемся к серверному приложению

Вернемся к серверному приложению и опишем функции снимка экрана выключения рабочей станции. Откроем файл program.cs и запишем следующий код внутри цикла while, который мы реализовали ранее.

        else if (data.ToUpper().Contains("SHTD3")) {  
    Console.WriteLine("Pc is going to Shutdown!" + " \n");  
    Shutdown();  
} else if (data.ToUpper().Contains("TSC1")) {  
    Console.WriteLine("Take Screenshot!" + " \n");  
    var bitmap = SaveScreenshot();  
    var stream = new MemoryStream();  
    bitmap.Save(stream, ImageFormat.Bmp);  
    sendData(stream.ToArray(), client.GetStream());  
}  

    

6. Функции съемки экрана и выключения

Добавим в серверное приложение следующие пространства имен. Иначе Visual Studio не распознает ключевое слово graphics функции снимка экрана.

        using System.Drawing;  
using System.Drawing.Imaging;  
    

Вернемся к program.cs и поместите эти функции в основной класс.

        // Функция выключения рабочей станции
void Shutdown() {  
    System.Diagnostics.Process.Start("Shutdown", "-s -t 10");  
}  
// Функция сохранения скриншота
Bitmap SaveScreenshot() {  
    var bmpScreenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb);  
    // Создание графического bitmap-объекта
    var gfxScreenshot = Graphics.FromImage(bmpScreenshot);  
    // Берем скриншот из Take the screenshot от верхнего левого до нижнего правого угла
    gfxScreenshot.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);  
    return bmpScreenshot;  
}  
// Преобразуем изображение в байтовый код.  
void sendData(byte[] data, NetworkStream stream) {  
    int bufferSize = 1024;  
    byte[] dataLength = BitConverter.GetBytes(data.Length);  
    stream.Write(dataLength, 0, 4);  
    int bytesSent = 0;  
    int bytesLeft = data.Length;  
    while (bytesLeft > 0) {  
        int curDataSize = Math.Min(bufferSize, bytesLeft);  
        stream.Write(data, bytesSent, curDataSize);  
        bytesSent += curDataSize;  
        bytesLeft -= curDataSize;  
    }  
}  

    

Итак, мы закончили с серверным приложением.

7. Возвращаемся к клиентскому приложению

7.1. Создаем страницу операций

Возвращаемся в клиентское приложение (Xamarin.Forms) и добавляем новый ContentPage с именем OperationsPage. Внутри этого макета добавим следующий код, чтобы создать больше кнопок.

        <?xml version="1.0" encoding="utf-8" ?>  
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"  
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
             xmlns:local="clr-namespace:XamarinForms.Client"  
             x:Class="XamarinForms.Client.OperationsPage">  
    <ContentPage.Content>  
        <StackLayout Orientation="Vertical">  
            <Button x:Name="Screenshot" Text="Screenshot" Clicked="Screenshot_Clicked"/>  
            <Button x:Name="Sleep" Text="Sleep" Clicked="Sleep_Clicked"/>  
            <Button x:Name="Shutdown" Text="Shutdown" Clicked="Shutdown_Clicked"/>  
            <Image x:Name="imageView"/>  
  
        </StackLayout>  
    </ContentPage.Content>  
</ContentPage>

    

7.2. Добавляем в класс операций методы ожидания

Открываем файл OperationPage.xaml и переносим следующий код.

        using System;  
using System.IO;  
using System.Net.Sockets;  
using System.Text;  
using Xamarin.Forms;  
using Xamarin.Forms.Xaml;  
  
namespace XamarinForms.Client  
{  
    [XamlCompilation(XamlCompilationOptions.Compile)]  
    public partial class OperationsPage : ContentPage  
    {  
        public OperationsPage ()  
        {  
            InitializeComponent ();  
        }  
        
        // Команда для кнопки Sleep
        private void Sleep_Clicked(object sender, EventArgs e)  
        {  
            var client = Connection.Instance.client;  
            NetworkStream stream = client.GetStream();  
            String s = "SLP2";  
            byte[] message = Encoding.ASCII.GetBytes(s);  
            stream.Write(message, 0, message.Length);  
        }  
 
        // Команда для кнопки Shutdown
        private void Shutdown_Clicked(object sender, EventArgs e)  
        {  
            var client = Connection.Instance.client;  
            NetworkStream stream = client.GetStream();  
            String s = "SHTD3";  
            byte[] message = Encoding.ASCII.GetBytes(s);  
            stream.Write(message, 0, message.Length);  
        }  
 
        // Команда для снимка экрана
        private void Screenshot_Clicked(object sender, EventArgs e)  
        {  
            var client = Connection.Instance.client;  
            NetworkStream stream = client.GetStream();  
            String s = "TSC1";  
            byte[] message = Encoding.ASCII.GetBytes(s);  
            stream.Write(message, 0, message.Length);  
            var data = getData(client);  
            imageView.Source = ImageSource.FromStream(() => new MemoryStream(data));  
        }  
  
        // Сбор данных с сервера
        public byte[] getData(TcpClient client)  
        {  
            NetworkStream stream = client.GetStream();  
            byte[] fileSizeBytes = new byte[4];  
            int bytes = stream.Read(fileSizeBytes, 0, fileSizeBytes.Length);  
            int dataLength = BitConverter.ToInt32(fileSizeBytes, 0);  
  
            int bytesLeft = dataLength;  
            byte[] data = new byte[dataLength];  
  
            int buffersize = 1024;  
            int bytesRead = 0;  
  
            while (bytesLeft > 0)  
            {  
                int curDataSize = Math.Min(buffersize, bytesLeft);  
                if (client.Available < curDataSize)  
                    curDataSize = client.Available;  
                bytes = stream.Read(data, bytesRead, curDataSize);  
                bytesRead += curDataSize;  
                bytesLeft -= curDataSize;  
            }  
            return data;  
        }  
    }  
}  

    

8. Тестируем!

Запускаем оба приложения – сервер и клиент. Вводим IP-адрес и порт рабочей станции, которые вы видите на экране вывода приложения сервера, в клиентское приложение и нажимаем Connect.

После подключения к серверу теперь мы можем выполнять различные команды.

Ура! Всё работает. При нажатии на Sleep рабочая станция переходит в спящий режим. При нажатии на Take Screenshot через пару мгновений скриншот рабочей станции оказывается внутри клиентского приложения.

***

Если вы любите C#, мы также советуем вам обратить внимание на другие статьи тега C#, например, недавние:

Источники

РУБРИКИ В СТАТЬЕ

МЕРОПРИЯТИЯ

Комментарии 3

ВАКАНСИИ

Python Data Engineer
Минск, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

BUG