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

StackOverflow https://stackoverflow.com/questions/390944

Вопрос

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

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

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

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


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

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

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

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

Я предпочитаю Linux, но примеры для Windows также будут очень полезны.


изменить [09-01-06]:

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

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

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

[Изменить 19 февраля 2009 г.]

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

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

Это было полезно?

Решение

Хм,

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

Вопрос о том, как компьютеры НАХОДЯТ друг друга, становится сложнее.По сути, есть три случая:

  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, вы можете использовать git для обслуживания своей базы данных.Но есть вероятность, что ваш подход к обновлению не будет сильно напоминать то, что делает git.Другим примером протокола является та или иная версия веб-сервисы например, МЫЛО.Эти протоколы просто сворачивать процесс вызова функции на одной машине с использованием 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);
}

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

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

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

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

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

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

Другие советы

Видеть Опубликовать/подписаться асинхронная парадигма обмена сообщениями.

Пример реализации: Апач ActiveMQ:

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

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

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

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

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

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

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

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

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

Честно говоря, самый простой способ — узнать свой 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/маску для сканирования внешней подсети.Обойти эту проблему просто невозможно, если вы хотите связаться с устройствами в Интернете (хотя, как только один компьютер обнаружит сеть, он может отправить адрес всем остальным, если вы захотите, и это может очень быстро вырасти!)

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

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

Хорошо.Как и обещал, вот пример кода, который я вытащил из своего приложения.Не ожидается, что это скомпилируется и запустится, это пример того, как я сделал это.Возможно, вам придется сделать совершенно по-другому.Кроме того, это было написано для 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);
}

Хорошо, 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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top