Как управлять компьютером со смартфона по Wi-Fi: пишем Android-приложение на С#
Пример использования инструмента Xamarin.Forms для создания пользовательского интерфейса системного администратора, удаленно управляющего рабочими станциями с телефона.
Введение
Неправильное использование рабочих компьютеров компании является обычной практикой среди пользователей во многих компаниях, где отсутствуют надлежащие механизмы мониторинга. Это приводит к напрасной трате ресурсов и снижает скорость достижения общих целей организации. Таким образом, для достижения желаемых результатов необходим эффективный механизм, с помощью которого системный администратор может отслеживать и контролировать рабочие станции сотрудников.
Целью рассматриваемого проекта является разработка приложения на основе 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
и записываем в него следующий код.
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-адрес и порт рабочей станции.
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#, например, недавние:
- Что нового будет в C# 9? Результаты исследования Proposals на GitHub
- 10 самых популярных алгоритмов сортировки на C#
- Исчерпывающий видеокурс: структуры данных C#