Pull to refresh

Удаленное управление мышью с помощью .NET Remoting

Reading time 5 min
Views 9.3K
Пару лет назад я решил испытать Remoting в сочетании с winapi и сделать удаленное управление мышью. Решение должно состоять из 2 приложений, взаимодействующих через .NET Remoting. Серверное приложение должно быть в виде службы Windows.

Сервер аналогичен 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, т.к. ограничения сильвера не дают его использовать) и отобразим в браузере.

Исходники можно скачать здесь.
Tags:
Hubs:
+10
Comments 11
Comments Comments 11

Articles