Как сделать частную связь между частными приложениями по сети? - PullRequest
2 голосов
/ 24 декабря 2008

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

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

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

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


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

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

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

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

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


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

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

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

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

[Редактировать 2009-02-19]

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

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

Ответы [ 12 ]

5 голосов
/ 24 декабря 2008

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

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

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

4 голосов
/ 17 февраля 2009

Хмм,

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

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

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

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

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

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

Edit:

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

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

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

3 голосов
/ 17 февраля 2009

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

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

3 голосов
/ 12 февраля 2009

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

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

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

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

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

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

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

2 голосов
/ 24 декабря 2008

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

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

1 голос
/ 18 февраля 2009

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

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

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

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

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

1 голос
/ 18 февраля 2009

Хорошо, поэтому 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

1 голос
/ 17 февраля 2009

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

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

0 голосов
/ 13 февраля 2009

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

...