Пару лет назад я решил испытать Remoting в сочетании с winapi и сделать удаленное управление мышью. Решение должно состоять из 2 приложений, взаимодействующих через .NET Remoting. Серверное приложение должно быть в виде службы Windows.
Сервер аналогичен RAdmin серверу, слушает порт и ждёт подключения клиента. Когда клиент подключается, сервер принимает информацию о необходимости изменения положения мыши или нажатии кнопки.
Клиент, в свою очередь, подключается к указанному серверу и, в случае изменения положения мыши в окне клиента, передает информацию на сервер.
На данный момент я не пользуюсь Windows, поэтому восстановлю всё по памяти.
Я использовал Remoting, т.к. на тот момент это была новая интересная технология, позволяющая абстрагироваться от уровня сокетов и общаться клиенту и серверу на уровне объектов/интерфейсов. То есть, вместо того чтобы открывать сокет, обмениваться сообщениями мы просто объявляем объект, который хотим расшарить на сервере и используем его методы на клиенте. Если сравнить сокеты с SQL и непосредственным взаимодействием с БД, то .NET Remoting похож на ORM, красивую и удобную обёртку.
Клиент прост до безобразия. При запуске инициализируем Remoting, при нажатии кнопки подключаемся к серверу или отключаемся от него. Есть окно и форма на нём. Перехватываем движения мыши по ней и нажатие/отпускание клавиши мыши. Клиент знает только интерфейс, с которым он взаимодействует, передает ему в функциях изменения у нашей мышки.
Везде в статье – код на шарпе.
При запуске – загружаем конфигурацию, затем, при клике на кнопку подключаем удаленный объект. При движении и клике вызываем соответствующую функцию этого объекта.
Сервер чуть похитрее. У него есть объект, реализующий выше упомянутый интерфейс, с помощью .NET Remoting он получает информацию об изменениях у мышки и сразу же посылает winapi SendInput для изменения положения мыши и нажатости ее кнопки (для нескольких кнопок мыши, передачи нажатий клавиш или изображения экрана очень просто изменить данный код – в аргументах передаем на сервер, а в возвращаемом значении передаем клиенту).
Сервер оформлен в виде службы Windows (при создании шаблона выбираем service).
В таком случае функция main выглядит так:
Службы это такие же приложения, но они запускаются отдельно, имеют некоторые привилегии и специальные функции для старта/остановки. Такой особый жизненный цикл. Так как эта служба должна запускаться от имени нашего пользователя (чтобы двигать курсор) – меняем пользователя на текущего вручную в панели управления службами.
В нашем случае при запуске службы должна производиться инициализация.
Здесь мы видим функцию запуска службы, в которой инициализируется Remoting. Объяснять особо нечего, загружаем конфиг, регистрируем канал, регистрируем объект. Можно использовать HTTP вместо TCP.
Функции управления курсором я взял готовые. Кажется, здесь.
И завернул их в Communication:
Всё, на сервере больше ничего не нужно, т.к. этот объект будет управляться через .NET Remoting
Есть вариант сделать специальный установщик для этой службы, но на мой взгляд проще создать 2 ярлыка с подобным содержимым:
Для установки:
Для удаления:
Данное приложение было протестировано на компьютерах с Windows XP SP2. На более старших версиях Windows (Vista, 7), по-моему, есть какие-то глюки из-за безопасности, но это решаемо.
Здесь не описан механизм обратной связи сервера с клиентом. Да, можно сделать 2 соединения – прямое и обратное или периодически опрашивать сервер, но это не очень элегантно. Если вам это будет интересно, то элегантное решение я покажу в следующей статье на данную тему. Там будем управлять точкой в трёхмерном пространстве таким образом, чтобы расчеты велись на сервере, а отображение велось на клиенте. А затем клиентскую часть перенесем на Silverlight (уже без Remoting, т.к. ограничения сильвера не дают его использовать) и отобразим в браузере.
Исходники можно скачать здесь.
Сервер аналогичен RAdmin серверу, слушает порт и ждёт подключения клиента. Когда клиент подключается, сервер принимает информацию о необходимости изменения положения мыши или нажатии кнопки.
Клиент, в свою очередь, подключается к указанному серверу и, в случае изменения положения мыши в окне клиента, передает информацию на сервер.
На данный момент я не пользуюсь Windows, поэтому восстановлю всё по памяти.
Я использовал Remoting, т.к. на тот момент это была новая интересная технология, позволяющая абстрагироваться от уровня сокетов и общаться клиенту и серверу на уровне объектов/интерфейсов. То есть, вместо того чтобы открывать сокет, обмениваться сообщениями мы просто объявляем объект, который хотим расшарить на сервере и используем его методы на клиенте. Если сравнить сокеты с SQL и непосредственным взаимодействием с БД, то .NET Remoting похож на ORM, красивую и удобную обёртку.
Клиент
Клиент прост до безобразия. При запуске инициализируем Remoting, при нажатии кнопки подключаемся к серверу или отключаемся от него. Есть окно и форма на нём. Перехватываем движения мыши по ней и нажатие/отпускание клавиши мыши. Клиент знает только интерфейс, с которым он взаимодействует, передает ему в функциях изменения у нашей мышки.
Конфигурация Remoting для клиента
<configuration> <system.runtime.remoting> <application> <lifetime leaseTime="20D" sponsorshipTimeout="1H" renewOnCallTime="1D" leaseManagerPollTime="1H" /> <client> <wellknown type="Communication.Communication,Communication" url="tcp://127.0.0.1:8000/Communication.rem" /> </client> <channels> <channel ref="tcp" port="0" clientConnectionLimit="20" > </channel> </channels> </application> </system.runtime.remoting> <appSettings> <add key="RemotingUrl" value="tcp://127.0.0.1:8000/Communication.rem"></add> </appSettings> </configuration>
Код клиента
Везде в статье – код на шарпе.
public partial class Form1 : Form { public Form1() { InitializeComponent(); RemotingConfiguration.Configure("Client.config"); } ICommunication iCommunication; bool connected; private void button1_Click(object sender, EventArgs e) { if (!connected) { iCommunication = (ICommunication)Activator.GetObject(typeof(ICommunication), textBox4.Text); connected = true; iCommunication.Move(100, 100); button1.Text = "Отключиться"; } else { button1.Text = "Подключиться"; connected = false; } } private void panel1_MouseMove(object sender, MouseEventArgs e) { if (!connected) return; iCommunication.Move(e.X, e.Y); } private void panel1_MouseDown(object sender, MouseEventArgs e) { if (!connected) return; iCommunication.Down(); } private void panel1_MouseUp(object sender, MouseEventArgs e) { if (!connected) return; iCommunication.Up(); } }
При запуске – загружаем конфигурацию, затем, при клике на кнопку подключаем удаленный объект. При движении и клике вызываем соответствующую функцию этого объекта.
Сервер
Сервер чуть похитрее. У него есть объект, реализующий выше упомянутый интерфейс, с помощью .NET Remoting он получает информацию об изменениях у мышки и сразу же посылает winapi SendInput для изменения положения мыши и нажатости ее кнопки (для нескольких кнопок мыши, передачи нажатий клавиш или изображения экрана очень просто изменить данный код – в аргументах передаем на сервер, а в возвращаемом значении передаем клиенту).
Конфигурация Remoting для сервера
<configuration> <system.runtime.remoting> <application> <lifetime leaseTime="20D" sponsorshipTimeout="1H" renewOnCallTime="1D" leaseManagerPollTime="1H" /> <service> <wellknown mode="Singleton" type="Communication.Communication,Communication" objectUri="Communication.rem"/> </service> </application> </system.runtime.remoting> </configuration>
Сервер оформлен в виде службы Windows (при создании шаблона выбираем service).
В таком случае функция main выглядит так:
static void Main() { ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new Service1() }; ServiceBase.Run(ServicesToRun); }
Службы это такие же приложения, но они запускаются отдельно, имеют некоторые привилегии и специальные функции для старта/остановки. Такой особый жизненный цикл. Так как эта служба должна запускаться от имени нашего пользователя (чтобы двигать курсор) – меняем пользователя на текущего вручную в панели управления службами.
В нашем случае при запуске службы должна производиться инициализация.
Код сервера
public partial class Service1 : ServiceBase { public Service1() { InitializeComponent(); } Communication communication; protected override void OnStart(string[] args) { System.Runtime.Remoting.RemotingConfiguration.Configure(Application.StartupPath+"\\Server.config"); System.Runtime.Remoting.Channels.ChannelServices.UnregisterChannel(channel); BinaryServerFormatterSinkProvider serverProv = new BinaryServerFormatterSinkProvider(); serverProv.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full; BinaryClientFormatterSinkProvider clientProv = new BinaryClientFormatterSinkProvider(); System.Collections.IDictionary props = new System.Collections.Hashtable(); props["port"] = 8625; channel = new TcpChannel(props, clientProv, serverProv); System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(channel); communication = new Communication(); ObjRef or = RemotingServices.Marshal(communication, "Communication.rem"); communication.Move(100, 100); } TcpChannel channel; protected override void OnStop() { } }
Здесь мы видим функцию запуска службы, в которой инициализируется Remoting. Объяснять особо нечего, загружаем конфиг, регистрируем канал, регистрируем объект. Можно использовать HTTP вместо TCP.
Функции управления курсором я взял готовые. Кажется, здесь.
И завернул их в Communication:
class Communication : MarshalByRefObject, ICommunication { //здесь много кода для движения мышки – вызова winapi user32.dll, кому интересно – есть в исходниках и по ссылке выше }
Всё, на сервере больше ничего не нужно, т.к. этот объект будет управляться через .NET Remoting
Установка/удаление службы
Есть вариант сделать специальный установщик для этой службы, но на мой взгляд проще создать 2 ярлыка с подобным содержимым:
Для установки:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe "G:\current\location\our_service.exe"Для удаления:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe "G:\current\location\our_service.exe" /uДанное приложение было протестировано на компьютерах с Windows XP SP2. На более старших версиях Windows (Vista, 7), по-моему, есть какие-то глюки из-за безопасности, но это решаемо.
Здесь не описан механизм обратной связи сервера с клиентом. Да, можно сделать 2 соединения – прямое и обратное или периодически опрашивать сервер, но это не очень элегантно. Если вам это будет интересно, то элегантное решение я покажу в следующей статье на данную тему. Там будем управлять точкой в трёхмерном пространстве таким образом, чтобы расчеты велись на сервере, а отображение велось на клиенте. А затем клиентскую часть перенесем на Silverlight (уже без Remoting, т.к. ограничения сильвера не дают его использовать) и отобразим в браузере.
Исходники можно скачать здесь.