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

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

Я проверил это, используя простые сокетные соединения и пользовательские буферы, но хочу, чтобы связи между приложениями соответствовали принятым стандартам, а также были безопасными / устойчивыми, и не пытались заново изобрести колесо.

Каков нормальный / стандартный способ сделать это приложение от приложения к приложению и где я могу узнать больше?

Кроме того, какие методы можно использовать для объявления и поиска других приложений в сети?


редактировать: (уточнение моей проблемы)

Модель pub/sub, на которую указывает ниже гимел, похоже, соответствует тому, что мне нужно. Тем не менее, он охватывает много вопросов, и я не знаю, что отнять и использовать от всего этого.

Похоже, мне нужно установить P2P-соединение, когда два или более приложений нашли друг друга - как мне это сделать?

Если есть примеры / учебники, пожалуйста, укажите их. Небольшие проекты / модули с открытым исходным кодом, которые реализуют что-то вроде того, что мне нужно, также послужат

Моя предпочтительная платформа - Linux, но примеры на основе Windows также будут очень полезны.


редактировать [09-01-06]:

В настоящее время я смотрю на следующие варианты:

  1. многоадресная рассылка (TLDP-Howto) - это кажется работоспособным, но мне нужно изучить его еще немного.
  2. используя бесплатные динамические DNS-серверы, хотя это кажется немного рискованным...
  3. использование некоторой бесплатной электронной почты, например, gmail / yahoo /..., и отправка / чтение почты оттуда, чтобы найти IP-адреса других приложений (может работать, но кажется грязным)
  4. Были предложены веб-сервисы, но я не знаю, как они работают, и мне придется изучить их

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

[Изменить 2009-02-19]

(Хотелось бы, чтобы я мог принять два / три ответа! Тот, который я принял, потому что он предлагает линии мысли и возможности, в то время как другие пришли с фиксированными, но применимыми решениями. Спасибо всем, кто ответил, все это помогает.)

Как и когда я найду / внедрю свое решение, я обновлю этот вопрос, и если решение будет адекватным, я создам для него проект sourceforge. (В любом случае это небольшая проблема в гораздо более крупном проекте.)

12 ответов

Решение

Хм,

Это немного похоже на математическую задачу. Вопрос о том, как два компьютера устанавливают соединение, когда они находят друг друга, довольно прост. Вы можете использовать любое количество протоколов P2P или клиент-сервер. SSL почти повсеместно доступен, но вы также можете обслуживать SSH, запускать Freenet или что-то еще. Как только вы устанавливаете соединение через один из этих протоколов, модель обмена / публикации для обмена данными может хорошо работать. Но есть

Вопрос в том, как компьютеры находят друг друга, где все становится сложнее. По сути, есть три случая:

  1. Все ваши компьютеры будут находиться в вашей локальной сети. В этом случае любой компьютер, подключенный к сети, может передать сообщение о том, что он находится в сети, и получить сообщение о том, какие другие машины подключены к сети. Помните, что любое широковещательное / многоадресное сообщение должно быть на каждой машине в сети, поскольку оно не знает, какова его цель. Вы не можете сделать это в Интернете, так как вы не можете отправить сообщение на каждый компьютер в сети.

  2. Ваши компьютеры находятся в произвольных узлах в Интернете **, и ** всегда будет хотя бы один из ваших компьютеров, подключенных к Интернету **, и ** все устройства регулярно подключаются к сети. В этом случае каждая машина может хранить текущий список IP-адресов. Когда машина, которая была в автономном режиме некоторое время, возвращается в оперативный режим, она проверяет известные адреса, подключаясь к первому действительному адресу - таким образом протокол emule находит серверы.

  3. Ваши компьютеры находятся на произвольных узлах в Интернете **, и ** все машины отключены вместе ** или ** многие из них отключаются на длительные периоды, пока другие переключают IP-адреса. Здесь я не думаю, что вы можете продемонстрировать, что новые машины не смогут найти друг друга без какого-либо центрального сервера для связи с общими IP-адресами. Невозможно передать сообщение через Интернет, потому что оно большое. В этих обстоятельствах компьютеры не имеют идентификационной информации для других компьютеров, подключенных к сети, поэтому вам нужно использовать центральный общий ресурс: адрес электронной почты, который вы упомянули, веб-сайт, FTP-сервер, IRC-канал. Динамический DNS - это еще один пример централизованного хранилища информации. Если вы должны использовать такой магазин, естественно, вы должны оценить все доступные магазины на предмет их надежности, скорости и постоянства. Если вы не предоставите больше информации о том, что нужно вашему приложению в этом отношении, я не думаю, что кто-либо другой может решить, какой постоянный магазин вам нужен.

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

Редактировать:

Судя по отзывам, случай 3 применим. Из рассуждений, которые вы видите выше, должно быть ясно, что нет способа избежать какой-либо формы внешнего сервера - единственный способ найти иголку в стоге сена - это отслеживать, где она находится. Качества, которые хотелось бы иметь в таком провайдере:

  • Надежность: как часто это происходит в данный день
  • Скорость: как быстро реагирует вещь
  • Постоянство: Как долго вы ожидаете, что вещь продлится? Вы потеряете доступ к нему по мере развития Интернета?

Как уже упоминалось, есть много легко доступных ресурсов, которые более или менее соответствуют этому счету. Почтовые серверы (как вы сейчас используете), веб-серверы, FTP-серверы, DNS-серверы, IRC-каналы, учетные записи Twitter, веб-форумы....

Проблема приложений, которые оживают через некоторое время и требуют обновления без центрального сервера, является распространенной проблемой, но она распространена в основном среди вирусописателей - практически в любой организации, у которой есть ресурсы для создания распределенного приложения, также есть ресурсы для поддержки центрального сервера. сервер. Тем не менее, стандартные решения на протяжении многих лет включали электронную почту, http-серверы, ftp-серверы, IRC-каналы и динамические DNS-серверы. Различные серверы в каждой из этих категорий различаются по своей скорости, надежности и постоянству, поэтому задача выбора одного из них возвращается к вашему мнению. IRC-каналы заслуживают упоминания, потому что они быстрые и простые в настройке, но они могут действительно исчезнуть по мере развития интернета.

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

Просто повторить. Я думаю, что есть три части вашей проблемы:

  1. Машины находят друг друга (см. Выше)
  2. Машины, устанавливающие соединение (опять же, SSL, SSH и другие легко доступны)
  3. Машины обмениваются данными. Вы можете использовать модель "публикация / подписка" или просто свернуть свой собственный простой протокол. Я работал в компании, у которой был клиент автообновления, и это то, что мы сделали. Причины для создания собственного протокола: 1) даже в самых простых ситуациях требования к скорости и надежности будут различаться в зависимости от обмена данными, 2. для простейшего обмена данными требуется всего несколько строк кода, поэтому никто не мешает протоколы для действительно простого обмена данными, 3. Поскольку разные приложения используют разные методы и языки, ни один протокол для простого обмена данными не является доминирующим. Для более сложных ситуаций действительно существует целый лес протоколов, но их разная сложность может затруднить их использование для простого обмена данными. Способ, которым git scm отправляет данные, является одним из примеров обновления протокола. Если так получилось, что ваша база данных напоминает исходный код, который отправляет git, вы можете использовать git для поддержки вашей базы данных. Но скорее всего, ваш подход к обновлению не будет напоминать то, что Git делает так близко. Другим примером протокола является та или иная версия веб-сервисов, таких как SOAP. Эти протоколы просто обертывают процесс вызова функции на одном компьютере с использованием xml и http. Если вы уже можете установить сокетную связь между вашими приложениями, то нет причин делать это. Помните, что для реализации веб-сервисов вам необходимо запустить сервер http и проанализировать XML-файл, который http-клиент использует в необработанных данных. Учитывая, что вы можете отправлять свои данные напрямую через сокет, нет причин делать это. Итак, вы вернулись к своей собственной работе.

В любом случае, примером простого протокола может быть:

  • одно приложение сначала отправляет индекс данных, которые оно имеет в виде массива индексов.
  • другое приложение отправляет список элементов, которые являются новыми, это
  • а затем первое приложение отправляет эти фактические элементы.

Затем приложения меняют роли и обмениваются данными другим способом. Данное "рукопожатие" в вашем протоколе будет выглядеть так в псевдокоде:

void update_database(in_stream, out_stream) {
  Get_Index_Of_Other_Machines_Items(in_stream);
  Send_Index_Of_Items_You_Need(out_stream);
  Get_Items_You_Need(in_stream);
}

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

Еще одним соображением является то, что если каждая запись в базе данных генерируется независимо, вам нужно будет сгенерировать уникальный для этого идентификатор без возможности ссылаться на все элементы в распределенной базе данных. Для этого вы можете сгенерировать GUID или просто большое случайное число.

Вам, без сомнения, придется настроить и развивать его дальше, если вы собираетесь его использовать.

Имейте в виду, однако, что, если все ваши приложения обновляются только время от времени, нет никакой уверенности в том, что у любых данных экземпляров будет какой-либо элемент данных. Например, предположим, что за день половина машин выходит в сеть только после 17:00, а другая половина - только до 17:00. В этом случае две группы машин не будут совместно использовать данные, если они напрямую обновляют друг друга. Если, с другой стороны, ваши машины действительно работают даже в распределенное время (а не в соответствии с шаблоном, как я описал), вполне вероятно, что каждая машина в конечном итоге получит все обновления (по крайней мере, все старые обновления),

Учитывая все это, я думаю, вы должны точно подумать, как редко ваши приложения будут подключаться и насколько важно, чтобы все данные были полностью синхронизированы. В чрезвычайных ситуациях вам может потребоваться использовать внешний сервер для перемещения ваших данных, а также для поиска ваших клиентов. Регулярная электронная почта является естественным решением этой проблемы. Одно дело использовать, если ни один компьютер не подключен к сети, а другой подключен к сети. Если вы беспокоитесь о доступе к определенным учетным записям электронной почты, вы можете запустить каждое приложение со списком из нескольких адресов электронной почты, со всеми проверяемыми адресами, и у вас будет возможность обновить приложение еще большим количеством адресов, поскольку данные адреса не работают. Использование электронной почты для всего будет иметь преимущество простоты. Вы могли бы использовать

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

См. Публикация / подписка парадигмы асинхронного обмена сообщениями.

Примером реализации является Apache ActiveMQ:

Apache ActiveMQ работает быстро, поддерживает множество межъязыковых клиентов и протоколов, поставляется с простыми в использовании шаблонами Enterprise Integration и множеством расширенных функций, полностью поддерживая JMS 1.1 и J2EE 1.4.

Я решил эту проблему несколько раз, находясь в управлении сетью. Похоже, ваша основная задача - "Обнаружение", как ваши приложения обнаруживают друг друга.

Честно говоря, самый простой способ - узнать ваш IP-адрес и маску (большинство из них класса c) и попытаться подключиться к каждой машине этого класса c.

Если вы по умолчанию используете класс C, это означает, что он почти всегда будет работать для большинства сетей. Затем вы можете разрешить переопределения, когда вы добавляете либо конкретные IP-адреса для подключения, либо дополнительные подсети.

Чтобы обнаружить класс C, вы просто выясняете свой IP-адрес (скажем, 192.168.2.77), а затем перебираете все в 192.168.2.(1-254), пытаясь открыть соединение с каждым.

Я сделал это с несколькими потоками (вы можете пропинговать все устройства одновременно и получить хорошие результаты в течение 3 секунд. Я обнаружил сеть класса B за 5 минут с несколькими сотнями потоков!), Или вы можете просто пойти от одного к другому в одном потоке - но если вы сделаете это, убедитесь, что ваш тайм-аут действительно очень мал (1/2 секунды или около того), иначе это займет вечность - даже на 1/2 секунды это займет минуту сделать обходы.

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

И кешируйте свои "Известные хорошие" IP-адреса для более быстрого запуска.

Это не сложная проблема, но и не тривиальная. Просто ожидайте немного поработать ногами.

Кроме того, вы, вероятно, захотите добавить новый IP/ маску для сканирования внешней подсети. Это просто невозможно, если вы хотите подключиться к устройствам в Интернете (хотя один компьютер обнаруживает сеть, он может отправлять адрес всем остальным, если хотите, и это может очень быстро вырасти!)

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

Я настроил "прослушиватель" для порта, который был выбран путем хеширования сервера базы данных и базы данных, к которой было подключено приложение. Это гарантирует, что любой, от кого я получаю широковещательную рассылку, будет использовать ту же базу данных, что и я, и позволит нескольким экземплярам приложения работать на рабочем столе (требование разработки).

Затем я настраиваю различные функции BroadcastMessage(), которые будут транслировать определенные события. Я даже дошел до того, что позволил разработчикам, использующим мой API, возможность создавать настраиваемые события с настраиваемыми данными полезной нагрузки, а затем заставлять программу регистрировать прослушиватель для этого события, который будет уведомлять регистратора о наступлении этого события и передавать его. данные, которые пришли с ним.

Например, когда приложение запускалось, оно передавало сообщение "Я здесь", и любой, кто слушал, мог съесть сообщение, проигнорировать его или ответить на него. В "Я здесь" он содержал IP-адрес запущенного приложения, так что любые клиенты МОГУТ подключиться к нему через TCP-соединение для дальнейших обновлений данных, которые ДОЛЖНЫ быть доставлены.

Я выбрал UDP, потому что это НЕ ТРЕБОВАНИЕ, чтобы эти трансляции были видны всем другим запущенным экземплярам. Это было удобнее, чем что-либо еще... Если бы кто-то добавил запись в БД, когда вы были на том же экране, новая запись просто "появилась бы" на вашем рабочем столе.

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

Настроить прослушиватель в потоке, который просто прослушивает сообщения такого типа, очень просто... Если вам нужен пример кода, я тоже могу это предоставить, но он написан на C++ и предназначен для Windows, но использует сырой wsock32..lib, так что он ДОЛЖЕН переноситься на любую платформу Unix довольно легко. (Нужно просто ввести DWORD, так как я часто этим пользовался..).

Вы хотите, чтобы это был полностью P2P или вы планируете иметь центральный сервер для чего-то большего, чем просто каталог?

Для безопасности связи, SSL должен быть в порядке. Java поддерживает их довольно простым способом, если это то, что вы используете. вот ссылка на SSL в Java 6

Хорошо, так что MQ и подобные вещи звучат как чрезмерное убийство.

Мое понимание вашего приложения:

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

Почему бы и нет:

1) UDP передает / слушает на регулярной основе, чтобы "найти другие машины в той же сети" - пример на Java: http://java.sun.com/docs/books/tutorial/networking/datagrams/index.html

2) Используйте SSL-сокеты для реального общения после обнаружения:
http://stilius.net/java/java_ssl.php....
http://www.exampledepot.com/egs/javax.net.ssl/Client.html

Хорошо. Как и было обещано, вот пример кода, который я скопировал из своего приложения. Это не ожидается для компиляции и запуска, это пример того, как я это сделал. Возможно, вам придется сделать свое совершенно другое. Кроме того, это было написано для Windows, и, как вы увидите в коде, оно использует Сообщения Windows для отправки данных между серверным потоком и основным приложением, но все зависит от того, как ВЫ планируете его использовать. Я оставил некоторые из наиболее интересных частей для вас, чтобы сослаться на них.

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

// Some defines that you may see in the code, all of which are user defined...
#define ADVERTISE_SERVER           0x12345678 // Some unique ID for your advertisement server
#define ACTIVITY_NONE              0x00000000
#define ACTIVITY_LOGON             0x00000001
#define ACTIVITY_LOGOFF            0x00000002
#define ACTIVITY_RUNNING           0x00000004
#define ACTIVITY_IDLE              0x00000005
#define ACTIVITY_SPECIFIC          0x00000006


enum Advertisements {
   ADVERTISE_SHUTDOWN,
   ADVERTISE_MESSAGE,
   ADVERTISE_DEBUG,
   ADVERTISE_OVERLAPPED,
   ADVERTISE_BROADCAST_IDENTITY,
   ADVERTISE_IDENTITY,
   ADVERTISE_PARAMETER_CHANGE
};

struct TAdvertiseServerPacket {
   UINT     uiAdvertisePacketType;
   DWORD    dwPacketLength;
   bool     bRequestReply;
   UINT     uiReplyType;
   bool     bOverlappedResult;
   int      iPacketId;
   bool     bBroadcast;
   char     GuidHash[35];
   BYTE     PacketData[1024];
};

struct TAdvertiseIdentity {
   TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
   char  szUserName[LEN_APPL_USERNAME + 1];
   char  szDatabase[MAX_PATH];
   char  szConfiguration[MAX_PATH];
   char  szVersion[16];
   long  nUserId;
   char  szApplication[MAX_PATH];
   char  szActivity[33];
   UINT  uiStartupIndc;
};

struct TAdvertiseMessage {
   char              MessageFrom[LEN_APPL_USERNAME + 1];
   char              MessageText[512];
};

struct TAdvertiseItemUpdate {
   NMHDR             pNMHDR;
   long              nItemId;
   long              nItemTypeId;
   char              szItemName[LEN_ITEM_NAME + 1];
   bool              bState;
};

struct TAdvertiseItemUpdateEx {
   NMHDR             pNMHDR;
   long              nItemId;
   bool              bState;
   bool              bBroadcast;
   DWORD             dwDataSize;
   void              *lpBuffer;
};

struct TOverlappedAdvertisement {
   int               iPacketId;
   BYTE              Data[1020];
};

DWORD WINAPI CAdvertiseServer::Go(void* tptr)
{
   CAdvertiseServer *pThis = (CAdvertiseServer*)tptr;

   /* Used and reused for Overlapped results, */
   DWORD BufferSize       = 0;
   BYTE *OverlappedBuffer = NULL;
   bool bOverlapped       = false;
   int  iOverlappedId     = 0;
   DWORD BufferPosition   = 0;
   DWORD BytesRecieved    = 0;
   TAdvertiseItemUpdateEx *itemex = NULL;
   UINT uiPacketNumber    = 0;

   bool Debug = false;
#ifdef _DEBUG
   Debug = true;
#endif
   {
      DWORD dwDebug = 0;
      dwDebug = GetParameter(ADVERTISE_SERVER_DEBUG); // GetParameter is part of the main program used to store running config values.
      if(dwDebug > 0)
      {
         Debug = true;
      }
   }
   WSAData wsaData;
   WSAStartup(MAKEWORD(1,1), &wsaData);
   ServerSocket = socket(PF_INET, SOCK_DGRAM, 0);
   if(ServerSocket == INVALID_SOCKET)
   {
      CLogging Log("Client.log");
      ServerSocket = NULL;
      Log.Log("Could not create server advertisement socket: %d", GetLastError());
      return -1;
   }
   sockaddr_in sin;
   ZeroMemory(&sin, sizeof(sin));
   sin.sin_family = AF_INET;
   sin.sin_port = htons(Port);
   sin.sin_addr.s_addr = INADDR_ANY;
   if(bind(ServerSocket, (sockaddr *)&sin, sizeof(sin)) != 0)
   {
      CLogging Log("Client.log");
      Log.Log("Could not bind server advertisement socket on port: %d Error: %d", Port, GetLastError());
      DWORD dwPort = 0;
      dwPort = GetParameter(ADVERTISE_SERVER_PORT); // Again, used to set the port number, if one could not be figured out.
      if(dwPort > 0)
      {
         return -1;
      }
      Port = 36221;
      sin.sin_port = htons(Port);
      if(bind(ServerSocket, (sockaddr *)&sin, sizeof(sin)) != 0)
      {
         CLogging Log("Client.log");
         Log.Log("Could not bind server advertisement socket on port: %d Error: %d Could not start AdvertiseServer after two attempts.  Server failed.", Port, GetLastError());
         return -1;
      }
   }

   SECURITY_ATTRIBUTES sa;
   sa.bInheritHandle = TRUE;
   sa.lpSecurityDescriptor = NULL;
   sa.nLength = sizeof(SECURITY_ATTRIBUTES);
   HANDLE mutex = CreateMutex(NULL, FALSE, "Client.Mutex"); // Used to keep and eye on the main program, if it shuts down, or dies, we need to die.
   while (1)
   {
      TAdvertiseServerPacket ap;
      sockaddr_in sin;
      int fromlen = sizeof(sin);
      fd_set fds;
      FD_ZERO(&fds);
      FD_SET(ServerSocket, &fds);
      timeval tv;
      tv.tv_sec = 15;
      tv.tv_usec = 0;
      int err = select(0, &fds, NULL, NULL, &tv);
      if(err == SOCKET_ERROR)
      {
         CLogging Log("Client.log");
         Log.Log("Advertise: Winsock error: %d", WSAGetLastError());
         Beep(800, 100);
         break;
      }
      if(err == 0)
      {
         if(WaitForSingleObject(mutex, 0) != WAIT_OBJECT_0)
         {
            continue; // Main app is still running
         }
         else
         {
            Beep(800, 100); // Main app has died, so exit our listen thread.
            break;
         }
      }

      int r = recvfrom(ServerSocket, (char *)&ap, sizeof(ap), 0, (sockaddr *)&sin, &fromlen);

      if(r != sizeof(TAdvertiseServerPacket))
      {
         continue;
      }
      switch(ap.uiAdvertisePacketType)
      {
         // This is where you respond to all your various broadcasts, etc.
         case ADVERTISE_BROADCAST_IDENTITY:
         {
            // None of this code is important, however you do it, is up to you.
            CDataAccess db(CDataAccess::DA_NONE);
            TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
            ZeroMemory(ComputerName, sizeof(ComputerName));
            DWORD len = MAX_COMPUTERNAME_LENGTH;
            GetComputerName(ComputerName, &len);
            if(pThis->szActivity) {
               CAdvertiseServer::AdvertiseIdentity(ComputerName, CDataAccess::GetLoggedInUserName(), CDataAccess::DatabaseConfiguration(), CDataAccess::DatabaseConfiguration(), ACTIVITY_SPECIFIC, pThis->szActivity, false);
            } else {
               CAdvertiseServer::AdvertiseIdentity(ComputerName, CDataAccess::GetLoggedInUserName(), CDataAccess::DatabaseConfiguration(), CDataAccess::DatabaseConfiguration(), ACTIVITY_RUNNING, NULL, false);
            }
         }
         case ADVERTISE_IDENTITY:
         {
            TAdvertiseIdentity ident;
            memcpy((void*)&ident, (void*)ap.PacketData, ap.dwPacketLength);
            Listener::iterator theIterator;
            theIterator = pThis->m_Listeners.find(ap.uiAdvertisePacketType);
            if(theIterator == pThis->m_Listeners.end())
            {

               //We got an Identity Broadcast, but we're not listening for them.
               continue;
            }
            {
               itemex = new TAdvertiseItemUpdateEx;
               ZeroMemory(itemex, sizeof(TAdvertiseItemUpdateEx));
               memcpy((void*)&ident, ap.PacketData, ap.dwPacketLength);
               itemex->pNMHDR.code     = (*theIterator).first;
               itemex->pNMHDR.hwndFrom = (*theIterator).second;
               itemex->pNMHDR.idFrom   = ADVERTISE_SERVER;
               itemex->dwDataSize      = sizeof(TAdvertiseIdentity);
               itemex->lpBuffer        = (void*)&ident;
               SendMessage((*theIterator).second, WM_NOTIFY, 0, (LPARAM)itemex);
               delete itemex;
            }
         }
         case ADVERTISE_SHUTDOWN:
         {
            TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
            ZeroMemory(ComputerName, sizeof(ComputerName));
            DWORD len = MAX_COMPUTERNAME_LENGTH;
            GetComputerName(ComputerName, &len);
            CString guid;
            guid.Format("%s%s", CDataAccess::DatabaseConfiguration(), ComputerName);
            if(stricmp(ap.GuidHash, CDataAccess::HashPassword(guid)) == 0)
            {
               return 1;
            }
         }
         case ADVERTISE_MESSAGE:
         {
            TAdvertiseMessage msg;
            memcpy((void*)&msg, (void*)ap.PacketData, ap.dwPacketLength);
            CString msgtext;
            msgtext.Format("Message from: %s\r\n\r\n%s", msg.MessageFrom, msg.MessageText);
            ::MessageBox(NULL, msgtext, "Broadcast Message", MB_ICONINFORMATION | MB_SYSTEMMODAL);
            break;
         }
         case ADVERTISE_OVERLAPPED:
         {
            // I left this code in here, as it's a good example of how you can send large amounts of data over a UDP socket, should you need to do it.
            BufferPosition = (1020 * ((ap.uiReplyType - 1) - 1));
            if(BufferPosition > BufferSize) {
               BufferPosition -= 1020;
            }
            TOverlappedAdvertisement item;
            ZeroMemory(&item, sizeof(TOverlappedAdvertisement));
            memcpy((void*)&item, (void*)ap.PacketData, ap.dwPacketLength);
            if(item.iPacketId == iOverlappedId)
            {
               DWORD ToCopy = (sizeof(item.Data) > (BufferSize - BytesRecieved) ? BufferSize - BytesRecieved : sizeof(item.Data));
               memcpy((void*)&OverlappedBuffer[BufferPosition], (void*)item.Data, ToCopy);
               BytesRecieved += ToCopy;
               if(BytesRecieved < BufferSize)
               {
                  continue;
               }
            }
         }
         default:
         {
            // What do we do if we get an advertisement we don't know about?
            Listener::iterator theIterator;
            if(bOverlapped == false)
            {
               theIterator = pThis->m_Listeners.find(ap.uiAdvertisePacketType);
               if(theIterator == pThis->m_Listeners.end())
               {
                  continue;
               }
            }

            // Or it could be a data packet
            TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
            ZeroMemory(ComputerName, sizeof(ComputerName));
            DWORD len = MAX_COMPUTERNAME_LENGTH;
            GetComputerName(ComputerName, &len);
            CString guid;
            guid.Format("%s%s", CDataAccess::DatabaseConfiguration(), ComputerName);
            bool FromUs = stricmp(ap.GuidHash, CDataAccess::HashPassword(guid)) == 0;
            if(((FromUs && Debug) || !FromUs) || ap.bBroadcast)
            {
               if(ap.bOverlappedResult)
               {
                  if(ap.uiReplyType == 1)
                  {
                     itemex = new TAdvertiseItemUpdateEx;
                     ZeroMemory(itemex, sizeof(TAdvertiseItemUpdateEx));
                     memcpy(itemex, ap.PacketData, ap.dwPacketLength);
                     OverlappedBuffer = (BYTE*)malloc(itemex->dwDataSize);
                     BufferSize = itemex->dwDataSize;
                     ZeroMemory(OverlappedBuffer, itemex->dwDataSize);
                     bOverlapped = true;
                     iOverlappedId = ap.iPacketId;
                     uiPacketNumber = ap.uiReplyType;
                  }
                  continue;
               }
               if(bOverlapped)
               {
                  itemex->pNMHDR.code     = (*theIterator).first;
                  itemex->pNMHDR.hwndFrom = (*theIterator).second;
                  itemex->pNMHDR.idFrom   = ADVERTISE_SERVER;
                  itemex->dwDataSize      = BufferSize;
                  itemex->lpBuffer        = (void*)OverlappedBuffer;
                  SendMessage((*theIterator).second, WM_NOTIFY, 0, (LPARAM)itemex);
                  delete itemex;
                  free(OverlappedBuffer);
                  BufferSize       = 0;
                  OverlappedBuffer = NULL;
                  bOverlapped      = false;
                  iOverlappedId    = 0;
                  BufferPosition   = 0;
                  BytesRecieved    = 0;
                  itemex           = NULL;
                  uiPacketNumber   = 0;
                  break;
               }
               TAdvertiseItemUpdate *item = new TAdvertiseItemUpdate;
               ZeroMemory(item, sizeof(TAdvertiseItemUpdate));
               memcpy(item, ap.PacketData, ap.dwPacketLength);

               item->pNMHDR.code     = (*theIterator).first;
               item->pNMHDR.hwndFrom = (*theIterator).second;
               item->pNMHDR.idFrom   = ADVERTISE_SERVER;
               SendMessage((*theIterator).second, WM_NOTIFY, 0, (LPARAM)item);
               delete item;
            }
            break;
         }
      }
   }
   try {
      ResetEvent(ServerMutex);
      CloseHandle(pThis->ServerMutex);
      closesocket(ServerSocket);
      return 0;
   }
   catch(...) {
      closesocket(ServerSocket);
      return -2;
   }
}

// Here's a couple of the helper functions that do the sending...
bool CAdvertiseServer::SendAdvertisement(TAdvertiseServerPacket packet)
{
   TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
   ZeroMemory(ComputerName, sizeof(ComputerName));
   DWORD len = MAX_COMPUTERNAME_LENGTH;
   GetComputerName(ComputerName, &len);
   CString guid;
   guid.Format("%s%s", CDataAccess::DatabaseConfiguration(), ComputerName);

   strcpy(packet.GuidHash, CDataAccess::HashPassword(guid));

   bool bRetval = false;
   SOCKET s = socket(PF_INET, SOCK_DGRAM, 0);
   if(s != INVALID_SOCKET)
   {
      BOOL tru = TRUE;
      setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *)&tru, sizeof(tru));
      sockaddr_in sin;
      ZeroMemory(&sin, sizeof(sin));
      sin.sin_family = PF_INET;
      sin.sin_port = htons(Port);
      sin.sin_addr.s_addr = INADDR_BROADCAST;
      if(sendto(s, (char *)&packet, sizeof(packet), 0, (sockaddr *)&sin, sizeof(sin)) > 0)
      {
         bRetval = true;
         if(packet.bRequestReply)
         {
           // Here is where your work comes in, in setting up a reply, or making a TCP connection back to the other client.
         }
      }
      closesocket(s);
   }
   return bRetval;
}

bool CAdvertiseServer::Advertise(UINT uiAdvertisement, long nItemId, bool bState, void *lpBuffer, DWORD dwDataSize, bool bBroadcast)
{
   TAdvertiseServerPacket packet;
   ZeroMemory(&packet, sizeof(packet));
   TAdvertiseItemUpdateEx   item;
   ZeroMemory(&item, sizeof(item));

   UINT packetnum = 1;
   packet.bOverlappedResult = true;
   packet.bRequestReply = false;
   packet.uiAdvertisePacketType = uiAdvertisement;
   packet.dwPacketLength = sizeof(item);
   packet.uiReplyType = packetnum;
   packet.bBroadcast = bBroadcast;
   item.nItemId = nItemId;
   item.bState  = bState;
   item.dwDataSize = dwDataSize;
   memcpy((void*)packet.PacketData, (void*)&item, sizeof(item));
   packet.iPacketId = GetTickCount();
   if(SendAdvertisement(packet))
   {
      BYTE *TempBuf = new BYTE[dwDataSize];
      memcpy(TempBuf, lpBuffer, dwDataSize);

      DWORD pos = 0;
      DWORD BytesLeft = dwDataSize;
      while(BytesLeft)
      {
         TOverlappedAdvertisement item;
         packet.uiAdvertisePacketType = ADVERTISE_OVERLAPPED;
         packet.bOverlappedResult = BytesLeft > 1020;
         item.iPacketId = packet.iPacketId;
         memcpy((void*)item.Data, (void*)&TempBuf[pos], (BytesLeft >= 1020 ? 1020 : BytesLeft));
         memcpy((void*)packet.PacketData, (void*)&item, sizeof(item));
         packet.dwPacketLength = sizeof(item);
         packet.uiReplyType++;
         if(SendAdvertisement(packet))
         {
            if(BytesLeft >= 1020)
            {
               BytesLeft -= 1020;
               pos += 1020;
            }
            else
            {
               BytesLeft = 0;
            }
         }
      }
      delete TempBuf;
   }
   return true;
}

void CAdvertiseServer::Shutdown()
{
   TAdvertiseServerPacket packet;
   packet.uiAdvertisePacketType = ADVERTISE_SHUTDOWN;
   SendAdvertisement(packet);
}

Рассматривали ли вы использовать установку типа Bittorrent?

Используемые принципы коммуникации должны дать вам достаточно прочную основу для построения вашего приложения. Все, что вам нужно, это чтобы два узла знали друг о друге, а затем он собирался оттуда. Я использую MonoTorrent для запуска частной (100-узловой) сети передачи данных, RSS-ленты, чтобы объявить, какие файлы должны быть где (модифицированная версия Wordpress) и делать все в туннелях SSH. У меня есть центральный сервер, который управляет сетью, но он может легко жить на любом из моих 100 узлов. Используя службу динамического DNS, первый живой узел устанавливает свой собственный трекер в качестве резервной копии, если мой сервер выходит из строя.

Вы можете использовать файлы XML в качестве схемы обмена сообщениями или изменить передачу сети Bittorrent для передачи пакетов данных непосредственно в ваше приложение. Я думаю, что концепция того, что вы ищете, находится в Битторренте. Первый запустившийся узел восстановит запись динамического DNS ( DynDNS имеет довольно простой в использовании API), если в сети не было активного хоста. (Есть недостаток... Я столкнулся с проблемами синхронизации, когда два трекера запускаются в окне TTL)

Существует довольно много ссылок на SSH-туннелирование, я просто использую его из-за забавных диаграмм. Туннелирование SSH - не самый эффективный доступный метод, но это очень хорошая альтернатива необходимости программно оборачивать ваши сообщения в туннеле SSL.

Я знаю, что мысли как-то перепутаны, я просто надеюсь, что это немного поможет вам в правильном направлении. PS... для полностью переносимого решения вы можете запустить его на Java или.Net (под управлением Mono. У меня даже AppleTV, работающие под Mono). Тогда ОС может даже гибкую часть вашей работы.

Похоже, вам нужен распределенный кеш или автономный функционал БД - в зависимости от вашего языка (java/ C#/...) вам доступны различные опции...

Возможно, я что-то здесь упустил, но не смог увидеть ваш выбор языка программирования. В среде на основе Windows, использующей.Net Framework, лучшим выбором будет использование WCF, который позволяет вам добавить безопасность / надежность с помощью простой конфигурации. Если вам нужно решение с компьютерами на базе Windows, которое не ориентировано на.Net, я бы хотел использовать MSMQ, коммуникационную среду, построенную в соответствии с этими стандартами.

В Amazon и Microsoft есть очереди, которые вы можете использовать в качестве точки встречи между произвольным числом подключенных взаимодействующих приложений. Амазонка коммерческая, а не бесплатная. Microsoft в настоящее время бесплатна, но не гарантируется, что так будет всегда. Это точно решает проблему, с которой вы сталкиваетесь. Предоставляет модель pub/sub для подключенных клиентов.

Здесь есть хорошая статья о P2P с WCF http://msdn.microsoft.com/en-us/magazine/cc188685.aspx. Он предоставляет код, но предполагает.Net3, Wcf, Vista и выше

Другие вопросы по тегам