Headless удаленный рабочий стол за NAT для разработчиков и бесплатно (часть 2, примеры) +3


Heredes — библиотека для разработчиков


Heredes — библиотека для облегчения решения ряда конкретных задач, связанных с прямым обменом данных в сети без использования ipv6.


Из встроенных возможностей можно выделить:

  • облегченная установка соединения между двумя ПК не зависимо от того есть ли у них «белые» адреса или они за NAT;
  • гарантированная (с подтверждением) прямая передача файлов с одного ПК на другой;
  • реалтайм аудиосвязь между ПК за NAT;
  • реалтайм демонстрация экрана ПК за NAT;
  • проброс мыши и клавиатуры между ПК;
  • прямая передача произвольных пользовательских данных между ПК за NAT;
  • запись звука с удаленного ПК и сохранение скрина с удаленного монитора на локальной машине.

Тема пробоя NAT, на самом деле, не нова. Но она требует от разработчика определенных навыков, наличие STUN сервера, а лучше двух, и времени для реализации.
Heredes берет на себя вышеописанную часть, облегчая тем самым разработку приложений и позволяя сосредоточиться на задаче, будь-то прямой файлообмен, мессенджер или функционал удаленного администрирования, встроенный в Ваше приложение.


Скептики в целом могут возразить — зачем Heredes если есть WebRTC с крайне широкими возможностями.


Тем не менее, если звезды зажигают, значит это кому нибудь нужно.


Прежде всего Heredes не нацелен на межбраузерное взаимодействие и в этом его преимущество в десктопных приложениях. А именно — простота использования при написании «настольных» приложений, неприхотливость в вопросах ресурсов и да, мы постарались облегчить работу с тем, что не стандартизовано в WebRTC.


Разумеется, примеры реализации с помощью WebRTC найдутся и для нашего функционала (например, Screen Capture API реализует захват экрана частично или полностью) и может показаться что нет разницы, чье готовое решение использовать. Попробуйте, выберите наиболее удобное при разработке десктопного приложения решение именно для Вас.


Мы нацелены не на WEB — в приоритете именно десктоп и использование библиотеки в приложениях не требующих установки, экономичность в ресурсах и скорость выполнения задач.



Перейдем к краткому обзору?



Как писать с использованием Heredes

Часть 1. Прямая передача файлов за NAT это просто. Или 9 строк кода для передачи 8 эксабайт данных… или чуть более?


Рассмотрим как просто установить прямое соединение на примере вполне жизнеспособной задачи — передача фала между двумя ПК за NAT без использования облачных хранилищ, торрентов, файлообменных ресурсов, личных промежуточных серверов и других вспомогательных средств.


Ситуация вполне жизненная, кому из нас никогда не требовалось передать несколько эксабайт по сети? Задача даже при наличии промежуточного облачного хранилища еще та… ускорим ее в 2 раза, как минимум, за несколько минут...



Пример реализации на С++ ниже



//обмен файлами почти неограниченного размера между клиентами за NAT
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma comment(lib, "C:\\WORK\\P2P\\HEREDES\\heredes.lib")
#include <windows.h>
#include <C:\WORK\P2P\HEREDES\heredes.h>


int main(void)
{
	InitIONLibrary();	//инициализация библиотек и интерфейсов HEREDES
	DWORD Valid = 0;
	DWORD Id = 0;
	DWORD HightDword = 0;

	RegIONId(&Valid, &Id);	//регистрация нового пользователя
	//запуск асинхроннго сервера для ожидания входящих соединений
	HANDLE hConn = IONWaitConnection(&Valid, &Id, NULL); 

	//запуск синхронного интерфейса для передачи файла
	DWORD lowDword =IONSendFileToId(&Valid, &Id, NULL, &HightDword); 

	IONTerminate(hConn); //прерывание ожидания входящих соединений
	CloseIONHandle(hConn);
	return 0;
}

Не правда ли, очень лаконично вышло?


Есть явные недостатки, такие как:

  • Heredes не нацелен на установление прямого коннекта между двумя ПК из одной локальной сети;
  • могут возникнуть сложности в случае если оба ПК за симметричным NAT;
  • данный пример не предусматривает передачу нескольких файлов за раз.

Тем не менее есть и очевидный плюс — это вполне рабочий пример с функционалом применимым даже в виде «как есть из коробки». Как минимум удобно перекинуть на край света файл размером… в пределах 8 ЭБт, хотя я лично и не пробовал пробросить такой файл — попробуете Вы и расскажите чем закончилось ))…

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


Скачать пример

Часть 2. Простой UDP сервер и UDP клиент за NAT


Предположим наличие вполне жизненной ситуации — есть некий ПК, находящийся за NAT и мы хотим что б он периодически принимал входящие соединения из внешнего мира, а конкретно миром для него будет наш же UDP клиент. От слов к делу.


Пример реализации на С++ ниже


//UDP сервер, который в течении минуты слушает сеть и ожидает входящих соединений
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma comment(lib, "C:\\HEREDES\\heredes.lib")
#include <windows.h>
#include <C:\HEREDES\heredes.h>

int main(void)
{
	InitIONLibrary();	//инициализация библиотек и интерфейсов HEREDES
	DWORD Valid = 0;
	DWORD Id = 0;
	DWORD HightDword = 0;

	RegIONId(&Valid, &Id);	//регистрация нового пользователя

	//запуск асинхроннго сервера для ожидания входящих соединений
	HANDLE hConn = IONWaitConnection(&Valid, &Id, NULL); 
	HANDLE hEv=CreateEventA(NULL,1,NULL,NULL);
	
	WaitForSingleObject(hEv,60000);
	CloseHandle(hEv);

	IONTerminate(hConn); //прерывание ожидания входящих соединений
	CloseIONHandlw(hConn); //прерывание ожидания входящих соединений
	
	return 0;
}

//простейший UDP клиент, который ничего не делает
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma comment(lib, "C:\\HEREDES\\heredes.lib")
#include <windows.h>
#include <C:\HEREDES\heredes.h>

int main(void)
{
	InitIONLibrary();		//инициализация библиотек и интерфейсов HEREDES
	CHAR SecKey[8];		//8-ми байтный массив с ключем шифрования
	DWORD Valid = 0;
	DWORD Id = 0;
	HANDLE hConn;
	RegIONId(&Valid, &Id);	//регистрация нового пользователя
	
	
	hConn=CreateIONHandle();
		if (IONConnectToId(hConn, NULL, &Valid, &Id, &SecKey, NULL, NULL, NULL)==TRUE){
				MessageBoxA(NULL,"коннет установлен","состояние подключения", 48);
				HANDLE hEv=CreateEventA(NULL,1,NULL,NULL);
				WaitForSingleObject(hEv,60000);
				CloseHandle(hEv);

		}else{
				MessageBoxA(NULL,"ошибка соединения","состояние подключения", 48);
		};

	IONDisconnect(hConn); //прерывание ожидания входящих соединений
	CloseIONHandle(hConn); //прерывание ожидания входящих соединений
	return 0;
}

Данный пример очень бесполезен. Причина в том что такой сервер будет тихо реагировать на входящие соединения и обрабатывать все «по умолчанию», то есть игнорировать )), а клиент так же тихо удерживать соединение.

Часть 3. МФУ или клиент и сервер в одном лице


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



//клиент-сервер over NAT
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma comment(lib, "C:\\HEREDES\\heredes.lib")
#include <windows.h>
#include <C:\HEREDES\heredes.h>


int main(void)
{
	InitIONLibrary();			//инициализация библиотек и интерфейсов HEREDES

	CHAR SecKey[8];			//8-ми байтный массив с ключем шифрования
	DWORD Valid = 0;
	DWORD Id = 0;
	RegIONId(&Valid, &Id);		//регистрация нового пользователя


	HANDLE hCON_IN=IONWaitConnection(&Valid, &Id, NULL);
	HANDLE hCON_OUT=CreateIONHandle();


	if (IONConnectToId(hCON_OUT, NULL, &Valid, &Id, &SecKey, NULL, NULL, NULL==TRUE)){

				



		}else{
		MessageBoxA(NULL,"ошибка соединения","состояние подключения", 48);
		};

		IONTerminate(hCON_IN);
		IONDisconnect(hCON_OUT);
		CloseIONHandle(hCON_IN); 
		CloseIONHandle(hCON_OUT); 
	FreeConsole();
	return 0;
}

Часть 4. Простой консольный мессенджер за NAT


После примера бесполезного будет очень полезно написать что то наглядное. В предыдущих примерах и функция сервера IONWaitConnection и функция клиента IONConnectToId содержат параметр CBUserData установленный в NULL. тем не менее это не обязано быть так и должно так не быть если клиент-сервер обмениваются данными нестандартного формата. При передаче пользовательских данных это должна быть функция обработчик уведомления о пользовательских данных



//написать за NAT
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma comment(lib, "C:\\HEREDES\\heredes.lib")
#include <windows.h>
#include <C:\HEREDES\heredes.h>

HANDLE hOutput;
CBUDATA __stdcall UserDataProc(HANDLE hConn, LPVOID pAddrUserData, int datSize)
{
	DWORD inSize;
	WriteConsoleA(hOutput,pAddrUserData, datSize, &inSize, NULL);
	return 0;
}

int main(void)
{
	InitIONLibrary();				//инициализация библиотек и интерфейсов HEREDES

	CHAR buffer[0x3E0];			//максимальный размер блока пользовательских данных 0х3Е0
	DWORD bSize=0;

	CHAR SecKey[8];			//8-ми байтный массив с ключем шифрования
	DWORD Valid = 0;
	DWORD Id = 0;
	RegIONId(&Valid, &Id);		//регистрация нового пользователя

	AllocConsole();
	HANDLE hInput=GetStdHandle(-10);
	hOutput=GetStdHandle(-11);


	HANDLE hCON_IN=IONWaitConnection(&Valid, &Id, (CBUDATA) &UserDataProc);
	HANDLE hCON_OUT=CreateIONHandle();


	if (IONConnectToId(hCON_OUT, NULL, &Valid, &Id, &SecKey, \
			NULL, NULL, (CBUDATA) &UserDataProc)==TRUE){


			while (bSize!=-1){
				ReadConsoleA(hInput,&buffer,0x3E0,&bSize,NULL);
				if (IONGetStatus(hCON_OUT)==6) {SendUserData(hCON_OUT,&buffer,bSize);};
				if (IONGetStatus(hCON_IN)==6) {SendUserData(hCON_IN,&buffer,bSize);};
						}
				
		}else{
				MessageBoxA(NULL,"ошибка соединения","состояние подключения", 48);
		};

		IONTerminate(hCON_IN);
		IONDisconnect(hCON_OUT);
		CloseIONHandle(hCON_IN); 
		CloseIONHandle(hCON_OUT); 
	FreeConsole();
	return 0;
}

Пусть интерфейс и неказист и прост, но это уже кое-что не оторванное от жизни.


Скачать пример

Часть 5. А поговорить? Попробуем реализовать простую звонику за NAT


Притянем за уши потребность — пусть нам нужно не много и не мало а позвонить другу в деревню за NAT.
Heredes реализует передачу PCM аудио почти так же просто как и пользовательские данные.


//поговорить за NAT
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma comment(lib, "C:\\HEREDES\\heredes.lib")
#include <windows.h>
#include <C:\HEREDES\heredes.h>


int main(void)
{
	InitIONLibrary();			//инициализация библиотек и интерфейсов HEREDES

	CHAR SecKey[8];			//8-ми байтный массив с ключем шифрования
	DWORD Valid = 0;
	DWORD Id = 0;
	RegIONId(&Valid, &Id);		//регистрация нового пользователя


	HANDLE hCON_IN=IONWaitConnection(&Valid, &Id, NULL);
	HANDLE hCON_OUT=CreateIONHandle();

	
	IONSetAudioParam(hCON_OUT,3,"aud_in.wav","aud_sp.wav",NULL);
	IONSetAudioParam(hCON_IN,3,"aud_in.wav","aud_sp.wav",NULL);


		if (IONConnectToId(hCON_OUT, NULL, &Valid, &Id, &SecKey, NULL, NULL, NULL)==TRUE){

			IONStartAudioStream(hCON_OUT,11025);
			HANDLE hEv=CreateEventA(NULL,1,NULL,NULL);
			WaitForSingleObject(hEv,60000);
			CloseHandle(hEv);

				
		}else{
				MessageBoxA(NULL,"ошибка соединения","состояние подключения", 48);
		};
		IONStopAudioStream(hCON_IN);
		IONStopAudioStream(hCON_OUT);
		IONTerminate(hCON_IN);
		IONDisconnect(hCON_OUT);
		CloseIONHandle(hCON_IN); 
		CloseIONHandle(hCON_OUT); 
	return 0;
}

Скачать пример

Часть 6. От разговоров к администрированию или CMD over NAT


Еще один относительно быстро реализуемый пример это командная строка за NAT или удаленное перенаправление ввода/вывода. Приступим?



//CMD за NAT
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma comment(lib, "C:\\HEREDES\\heredes.lib")
#include <windows.h>
#include <C:\HEREDES\heredes.h>

CHAR buffer[0x3E0];			//максимальный размер блока пользовательских данных 0х3Е0
HANDLE hOutput;
HANDLE outpipe;
HANDLE inpipe;
HANDLE hEv;
HANDLE hCON_IN;
HANDLE hCON_OUT;

CBUDATA __stdcall UserDataProc(HANDLE hConn, LPVOID pAddrUserData, int datSize)
{
	
	DWORD cRcv=1;
	if (IONGetStatus(hCON_IN)==6){
		SendCommandLine(outpipe,pAddrUserData);
		WaitForSingleObject(hEv,2000);
		
			while(cRcv!=0){
				PeekNamedPipe(inpipe,NULL,NULL,NULL,&cRcv,NULL);
				if (cRcv!=0){
					ReadFile(inpipe,&buffer,cRcv,&cRcv,NULL);
					}
				SendUserData(hCON_IN,&buffer,cRcv);
				}
	};

	if (IONGetStatus(hCON_OUT)==6){
				WriteConsoleA(hOutput,pAddrUserData,datSize,&cRcv,NULL);
			
	};



	DWORD inSize;
	WriteConsoleA(hOutput,pAddrUserData, datSize, &inSize, NULL);
	return 0;
}

int main(void)
{
	HANDLE hInCons;
	HANDLE hOutCons;
		
	hEv=CreateEventA(NULL,1,NULL,NULL);
	StartHiddenConsoleProcess("cmd.exe",&outpipe, &inpipe, &hOutCons, &hInCons);

	InitIONLibrary();			//инициализация библиотек и интерфейсов HEREDES

	DWORD bSize=0;

	CHAR SecKey[8];			//8-ми байтный массив с ключем шифрования
	DWORD Valid = 0;
	DWORD Id = 0;
	RegIONId(&Valid, &Id);		//регистрация нового пользователя



	hCON_IN=IONWaitConnection(&Valid, &Id, (CBUDATA) &UserDataProc);
	hCON_OUT=CreateIONHandle();


	if (IONConnectToId(hCON_OUT, NULL, &Valid, &Id, &SecKey, \
			NULL, NULL, (CBUDATA) &UserDataProc)==TRUE){

			
			AllocConsole();
			HANDLE hInput=GetStdHandle(-10);
			hOutput=GetStdHandle(-11);
			WriteConsoleA(hOutput,"REMOTE CMD >",12,&bSize,NULL);


			while (bSize!=-1){
				ReadConsoleA(hInput,&buffer,0x3E0,&bSize,NULL);
				SendUserData(hCON_OUT,&buffer,bSize);
						}
				
			FreeConsole();

		}else{
				MessageBoxA(NULL,"ошибка соединения","состояние подключения", 48);
		};

		IONTerminate(hCON_IN);
		IONDisconnect(hCON_OUT);
		CloseIONHandle(hCON_IN); 
		CloseIONHandle(hCON_OUT); 
	return 0;
}

Из новенького и интересненького здесь функции StartHiddenConsoleProcess и SendCommandLine. Для деталей смотрите документацию (ссылки внизу).


Скачать пример

Часть 7. Займемся фотографией. Сфоткаем часть экрана удаленного рабочего стола


Вот мы и приблизились к названию библиотеки и, собственно, к чему все шло до этого. Удаленный рабочий стол без головы. Все слышали про безголовые хромы, проекты вроде селениум, но в памяти не всплывает всадник безголовы — Бил в виде Windows отрисованный на виртуальном DC.


Библиотека так же содержит функционал имитации действий мышей и клавиатур на удаленном ПК, но в данном примере мы ограничимся отрисовкой на виртуальном DC рабочего стола удаленной машины. После чего сохраним содержимое DC в BMP-файл.

Со времени выхода первой статьи кое-что изменилось. Теперь мы все же заморачиваемся с указанием не только координат и размера пробрасываемого прямоугольника, но и указанием DC с которого мы срисовываем. Опустив ностальгические размышления о том, как мы докатились до обезглавливания Windows перейдем к делу.



//безголовый скрин
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma comment(lib, "C:\\HEREDES\\heredes.lib")
#include <windows.h>
#include <C:\HEREDES\heredes.h>


int main(void)
{
	InitIONLibrary();			//инициализация библиотек и интерфейсов HEREDES

	CHAR SecKey[8];			//8-ми байтный массив с ключем шифрования
	DWORD Valid = 0;
	DWORD Id = 0;
	RegIONId(&Valid, &Id);		//регистрация нового пользователя

	HDC hCDC;
	HDC hDC=GetDC(NULL);
	HANDLE hCON_IN=IONWaitConnection(&Valid, &Id, NULL);
	HANDLE hCON_OUT=CreateIONHandle();
	SetINVISTParam(hCON_IN,hDC,64, 64, 256, 256,NULL);//установим параметры прямоугольника 
																		//разрешенного для отправки по сети


	if (IONConnectToId(hCON_OUT, NULL, &Valid, &Id, &SecKey, NULL, NULL, NULL)==TRUE)
		{
			hCDC=CreateCompatibleDC(hDC);
			StartRemoteINVIST(hCON_OUT,hCDC,512, 512, 1);	//размеры изображения 
										//я решил увеличить, 
																		//но можно было оставить и оригинальные или напротив уменьшить
			
	for (int i=0; i<3; i++){GetRemoteINVISTService(hCON_OUT);};	//получаю видео из 3х кадра 
										//из за специфики кодека
			int sB=SaveINVISTtoBPM(hCON_OUT,"screen.bmp");


		}else{
		MessageBoxA(NULL,"ошибка соединения","состояние подключения", 48);
		};
		DeleteDC(hCDC);
		IONTerminate(hCON_IN);
		IONDisconnect(hCON_OUT);
		CloseIONHandle(hCON_IN); 
		CloseIONHandle(hCON_OUT); 

	return 0;
}

В данном примере я получил несколько кадров для будущего битмепа. Вы можете получить 1,2 и сделать выводы из того что выйдет — лучше один раз пощупать чем 10 раз посмотреть.


Скачать пример

Часть 8. И в заключение статьи — напишем свой маленький TeamViewer.


Графические приложения для удаленного управления Windows посредством еще более графического интерфейса — прочно засели в быту. Попробуем реализовать этот функционал в нашем маленьком приложении RDP over NAT


//свой маленький TeamViewer
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma comment(lib, "C:\\HEREDES\\heredes.lib")
#include <windows.h>
#include <C:\HEREDES\heredes.h>

HANDLE hEv;
WNDPROC __stdcall ProcClose(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){

	SetEvent(hEv);

	return 0;
}

int main(void)
{
	hEv=CreateEventA(NULL,1,NULL,NULL);
	InitIONLibrary();			//инициализация библиотек и интерфейсов HEREDES

	CHAR SecKey[8];			//8-ми байтный массив с ключем шифрования
	DWORD Valid = 0;
	DWORD Id = 0;
	RegIONId(&Valid, &Id);		//регистрация нового пользователя

	HDC hCDC;
	HDC hDC=GetDC(NULL);
	HANDLE hCON_IN=IONWaitConnection(&Valid, &Id, NULL);
	HANDLE hCON_OUT=CreateIONHandle();
	SetINVISTParam(hCON_IN,hDC,64, 64, GetSystemMetrics(0), GetSystemMetrics(1),NULL);					//установим параметры прямоугольника 
									
	if (IONConnectToId(hCON_OUT, NULL, &Valid, &Id, &SecKey, NULL, NULL, NULL)==TRUE)
		{
	hCDC=CreateCompatibleDC(hDC);
	StartRemoteINVIST(hCON_OUT,hCDC,512, 512, 1);	//размеры изображения я решил увеличить, 
																		//но можно было оставить и оригинальные или напротив уменьшить
			HWND hWin;
			CreateINPRT(hCON_OUT, 64, 32, 512, 300, 0x00CF0000, NULL, &hWin, 1, (WNDPROC) ProcClose);

			WaitForSingleObject(hEv,-1);

		}else{
		MessageBoxA(NULL,"ошибка соединения","состояние подключения", 48);
		};
		
		CloseHandle(hEv);
		IONTerminate(hCON_IN);
		IONDisconnect(hCON_OUT);
		CloseIONHandle(hCON_IN); 
		CloseIONHandle(hCON_OUT); 

	return 0;
}

Скачать пример

В данном случае мы познакомились с функцией CreateINPRT — это по сути встроенный интерактивный плейер реального времени. Этот же пример можно было реализовать посредством отрисовки на DC какого либо окна и использовать функции передачи мыши и клавиатуры библиотеки Heredes, но так, по моему, даже удобнее.



Ссылки: на проект, скачать архив, документация.




К сожалению, не доступен сервер mySQL