Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Синхронизация приложений


Автор: former 9.9.2009, 22:27
Что подразумевается под синхронностью.
Предположим, что N компьютеров объединены в сеть через сервер. Требуется, что бы изменение объектов на форме любым пользователем мгновенно происходили у других пользователей. Каким образом это можно реализовать? С чего начать?
Буду благодарен за ссылки.

Автор: Fedia 9.9.2009, 22:53
Первое, что приходит на ум: использование http://delphiworld.narod.ru/base/sockets_in_delphi.html: TClientSocket & TServerSocket;
Второе: сетевая СУБД, в которую записывать вносимые пользователем изменения. На таймере в каждом открытом приложении считывать эти изменения и отображать их на форме.
Самый простой способ: общая сетевая папка, в ней файл в который записываются изменения. Опять же в таймере их считываем и отображаем на форме. Здесь нужно будет решать проблемы одновременного доступа к файлу. При работе с БД (не версионной типа MySql) такого быть не должно, поскольку там будет действовать принцип: кто последний тот и прав.

Автор: kami 9.9.2009, 22:55
Цитата(former @  9.9.2009,  22:27 Найти цитируемый пост)
Каким образом это можно реализовать? С чего начать?

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

Уточняющие вопросы:
1. Что подразумевается под объектами?
2. Один из пользователей изменил объект. В это же время (с запаздыванием на несколько микросекунд) этот же объект изменил другой пользователь. Если сеть локальная, то скорее всего положение будет следующим:
- команда от первого пользователя отправляется на сервер.
- команда от второго пользователя отправляется на сервер(для простоты будем именовать их №1 и №2).
- №1 рассылается всем пользователям (в т.ч. и №2, возможно - за исключением №1), они меняют состояние своих объектов.
- №2 отправляется всем пользователям (в т.ч. и №1, возможно - за исключением №2), состояние объектов опять меняется (это будет почти моментально)
Результат:
у пользователя 1 будет состояние от №2 (в принципе, как и положено - команда от 2 поступила чуть-чуть позднее)
у пользователя 2 будет состояние от №1. (это в том случае, если поступающая команда не "дублируется" обратно).

Имхо, это нужно будет предусмотреть как-то...

Автор: former 9.9.2009, 23:13
Fedia, не подходит.
kami
1. Технологическая схема, объектами которой являются задвижки, резервуары и т.д.
Параметрами, которые необходимо передавать, являются физические величины и некоторые другие.
Другими словами это параметры для модели тех. процесса.
2. Каждый пользователь отвечает за свой участок схемы.

Автор: kami 9.9.2009, 23:20
former, 
Тогда - чат на TCP, вместо сообщений - управляющие команды (пускай даже и текстовые, чтобы меньше переделывать. Типа "Add ZObject XParam=3211 YParam=-12 ZParam=4311". Хоть я и не люблю передавать данные текстом) и всё.
Проблемой будет непосредственно перевод действий в управляющие команды и обратно. Ну и отображение их, хотя это другая история. smile

Автор: former 9.9.2009, 23:32
kami, 
Цитата(kami @  9.9.2009,  23:20 Найти цитируемый пост)
Проблемой будет непосредственно перевод действий в управляющие команды и обратно.

Я думал так. Мат. модель обрабатывается непосредственно на сервере, а с клиентов передаются в нее изменения. Реакции этих изменений передаются обратно клиенту для визуализации.
Другими словами, если модель оставлять на каждом клиенте, то время на пересчет не будет обеспечивать синхронность. Но как в этом случае реализовать серверную часть, т.е. связать мат. модель с и предачу данных?
Стоит ли создавать отдельную службу для этих целей? Какие технологии использовать?

Автор: kami 9.9.2009, 23:48
former,  давайте, все-таки пока отделим котлеты от гарнира (в смысле - мат.модель с ее расчетами от синхронизации приложений)  smile Вообще - мне это представляется так: мат. модель - черный ящик, у которого один вход (к примеру - процедура DoChanges(Changes:string) ) и один выход (событие OnChangesCalculated(VisualChanges:string) ).
В этом случае компонент-сервер чата стыкуется с мат.моделью просто "на ура": сервер чата знает о "черном ящике", что у него есть вход и назначает на себя его выход. При любом сообщении от клиента сервер чата передает данные в процедуру входа мат.модели, а когда та отработала - вызывается событие окончания расчетов. Обработанные данные черного ящика просто рассылаются всем клиентам.

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

Автор: former 9.9.2009, 23:56
kami, меня беспокоит не столько время на расчеты, сколько какой объем данных возможно передавать с помощью чата . Или это будет определяться производительностью сервера и возможностями сети.

Автор: kami 10.9.2009, 00:03
Цитата(former @  9.9.2009,  23:56 Найти цитируемый пост)
какой объем данных возможно передавать с помощью чата .

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

Добавлено через 1 минуту и 1 секунду
Сейчас попробую написать работоспособный пример.

Автор: kami 10.9.2009, 01:05
Цитата(kami @  10.9.2009,  00:03 Найти цитируемый пост)
Сейчас попробую написать работоспособный пример.

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

Автор: kami 10.9.2009, 12:38
Сделал.
Это адаптированные вырезки из более мощного проекта, поддерживающего приоритеты передачи данных, шифрование "на лету" BlowFish-ем и т.д.
Комментариев нет, извините. Если что-то непонятно - спрашивайте.
Тестирование проводилось на:
 - передачу данных в обе стороны
 - адекватное реагирование на разрыв соединения в режиме простоя
 - отсутствие утечек при всех действиях.
Не проводилась проверка (лень, если честно) реакции компонентов на разрыв соединения непосредственно в процессе передачи данных, но думаю - ничего страшного не будет.

Ограничение:
Не стоит (но не значит, что нельзя) передавать данные в несколько сотен мегабайт от сервера клиентам - внутреннее хранилище данных основано на TMemoryStream, что при наличии десятков подключений приведет к задействованию памяти SourceStream.Size*ClientCount. В изначальных компонентах такого ограничения нет.

Передача данных корреспонденту поддерживается несколькими методами (буфер, строка, TStream). Прием - только TStream. При необходимости - расширить на события с приемными буферами других типов несложно. У сервера есть методы "Передать всем" и "передать конкретному".

Особенность:
При передаче данных через TStream сетевой компонент становится его владельцем и САМ уничтожит его. Посему - передали Stream в метод и ЗАБЫЛИ про него.
При приеме - наоборот. Получив TStream из сетевого компонента, владелец ОБЯЗАН его уничтожить.

Пример работы с этими компонентами так же во вложении.

P.S. Надеюсь, что гуру форума также не обойдут вниманием этот файл и подскажут, где в нем есть грабли и т.д.

Автор: former 10.9.2009, 14:47
kami, спасибо за пример. Посмотреть теперь уже, к сожалению, смогу только в выходные.
По результатам отпишу.

Автор: Akella 10.9.2009, 16:36
Цитата
xxx: "Попробую и обязательно отпишусь" - самое популярное последнее сообщение ветки форума
 бор (с)  smile 

Автор: former 10.9.2009, 17:04
Akella, вроде бы я тебя никогда плюсами в репу не обижал. И kami не обижу, когда тема будет исчерпана. Просто некоторые после повышения в репутацию сваливают из темы и получается висяк.

тише, а то нас THendle за флуд накажет  smile 

Автор: kami 10.9.2009, 17:37
Цитата(former @  10.9.2009,  14:47 Найти цитируемый пост)
 спасибо за пример.

Я бы сказал, что это не пример, а готовый шаблон для реализации Вашей задумки по 
Цитата(former @  9.9.2009,  23:32 Найти цитируемый пост)
Мат. модель обрабатывается непосредственно на сервере, а с клиентов передаются в нее изменения. Реакции этих изменений передаются обратно клиенту для визуализации.


Автор: former 13.9.2009, 17:07
kami, спасибо (+)! Хороший шаблон! Если возникнут какие-нибудь вопросы, то напишу.
Нашел еще вот http://www.devarticles.com/c/a/Delphi-Kylix/Sever-Side-Chat-Application-with-Borland-DelphiIndy/ несколько неплохих статей. Может ком-нибудь будет полезным.

Автор: kami 13.9.2009, 19:41
Цитата(former @  13.9.2009,  17:07 Найти цитируемый пост)
kami, спасибо

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

Цитата(former @  13.9.2009,  17:07 Найти цитируемый пост)
Нашел еще вот здесь несколько неплохих статей.

Не знаю почему, но к Indy у меня стойкая аллергия, и чем выше версия - тем больше smile (сразу скажу, что реальных аргументов "против" не имею).

Автор: former 13.9.2009, 20:36
Цитата(kami @  13.9.2009,  19:41 Найти цитируемый пост)
Давно уже собирался сделать что-то вроде выложенного здесь для простых задач сетевого обмена, но все руки не доходили. А тут такой случай.

Думаю, что универсальности в задаче сетевого обмена добиваться нет смысла, т.к. многое определяется конкретными условиями и потребностями. С другой стороны, можно классифицировать типовые задачи и создать для них шаблоны.
Цитата(kami @  13.9.2009,  19:41 Найти цитируемый пост)
Для полного счастья (мне) осталось их чуть-чуть доработать, дописав пару свойств.

О! Это уже интересно.

Автор: kami 13.9.2009, 22:59
Цитата(former @  13.9.2009,  20:36 Найти цитируемый пост)
 универсальности в задаче сетевого обмена добиваться нет смысла

Ну, я бы не был так категоричен smile

Цитата(former @  13.9.2009,  20:36 Найти цитируемый пост)
О! Это уже интересно.

Да ничего интересного. Главное при добавлении не дать доступа непосредственно к Server и Client сокетам (и их составляющим по возможности, особенно - касающихся приема и передачи). Пару свойств для идентификации соединений на server-side (к примеру, IP[ConnectionIndex:integer]:string)
В общем - дело 20 минут.

Автор: former 14.9.2009, 22:36
kami, откомпилированный вариант работает как часы.
Возможно, что это связано с самим компилятором. Я компилирую в D2009. При компиляции предупреждения и сообщения об ошибках не появляются.
Вот мой вариант компиляции.

Автор: kami 14.9.2009, 23:41
Цитата(former @  14.9.2009,  22:36 Найти цитируемый пост)
Я компилирую в D2009. 

Надо было сразу говорить. У меня D7.
Вроде, единственное, что нужно сделать - заменить везде String на AnsiString. Завтра гляну.

Автор: Romikgy 14.9.2009, 23:48
Цитата(kami @  14.9.2009,  22:41 Найти цитируемый пост)
заменить везде String на AnsiString.

тогда уж на WideString...

Автор: former 14.9.2009, 23:53
Цитата(kami @  14.9.2009,  23:41 Найти цитируемый пост)
Вроде, единственное, что нужно сделать - заменить везде String на AnsiString

Это не помогло. 
Замана PChar на PAnsiChar позволила передать данные, но
user posted image

Автор: kami 15.9.2009, 07:30
Цитата(Romikgy @  14.9.2009,  23:48 Найти цитируемый пост)
тогда уж на WideString...

Вот честно - не сталкивался с D2009, посему - могу только предполагать, но думаю, что не везде.
В методах, взаимодействующих с пользователем - да, желательно бы на WideString. Но я так понимаю, что при  этом нужно учитывать, что Length(myWideString)=2*Length(myAnsiString) при копировании из байтовых буферов, к примеру - TStream.Read?

И кстати, какой условной директивой определяется, что это D2009 ?

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

Автор: former 15.9.2009, 08:43
Цитата(kami @  15.9.2009,  07:30 Найти цитируемый пост)
И кстати, какой условной директивой определяется, что это D2009 ?

Если этот вопрос адресован мне. Лицензией. smile 
Цитата(kami @  15.9.2009,  07:30 Найти цитируемый пост)
former, Ваш экзешник вообще ничего не выводит, хотя передача данных между клиентом и сервером идет smile

Так я об этом и говорил.

Автор: kami 15.9.2009, 12:15
Цитата(former @  15.9.2009,  08:43 Найти цитируемый пост)
Если этот вопрос адресован мне. Лицензией

Не, я немного не про то. smile
Я имел ввиду директивы условной компиляции типа
Код

{$IFDEF VER150}

Чуствую, надо будет поставить на виртуалку 2009, потому что иначе много вопросов возникает...

Автор: bems 15.9.2009, 17:01
Цитата(kami @  15.9.2009,  12:15 Найти цитируемый пост)
{$IFDEF VER150}
это проверка версии компилятора. А тебе наверное нужно RTLVersion, или вообще SizeOf(Char)

Цитата(kami @  15.9.2009,  07:30 Найти цитируемый пост)
Length(myWideString)=2*Length(myAnsiString)

длина же в символах а не байтах. Так что тут умножение на 2 ни к чему

Автор: kami 15.9.2009, 19:03
Цитата(bems @  15.9.2009,  17:01 Найти цитируемый пост)
длина же в символах а не байтах

Я имел ввиду
Цитата(kami @  15.9.2009,  07:30 Найти цитируемый пост)
при копировании ... к примеру - TStream.Read

Потому что стандартный код для D7 (опуская проверки длины строки и размера потока)
Код

SetLength(str, Stream.Size);
Stream.Read(str[1], Length(str));

с учетом

Цитата(bems @  15.9.2009,  17:01 Найти цитируемый пост)
SizeOf(Char)

будет выглядеть как-то так:
Код

len:=Stream.Size;
SetLength(str, len div SizeOf(Char));
Stream.Read(str[1], Len);

???

Автор: former 15.9.2009, 20:00
kami, все, нашел причину. smile 
В модулях компонента поменял String - AnsiString и Char - AnsiChar, а в главном нет. Вот в этом и была загвоздка. Моя вина. smile 

Автор: kami 15.9.2009, 21:19
Цитата(former @  15.9.2009,  20:00 Найти цитируемый пост)
все, нашел причину.

Тоже переделал, протестировал на D2009.
Замена на Ansi коснулась только буферов в private секциях.
Для упрощения использования в примере (при приеме данных) добавил функцию StreamToString

Автор: bems 15.9.2009, 21:43
Цитата(kami @  15.9.2009,  19:03 Найти цитируемый пост)
len:=Stream.Size;
SetLength(str, len div SizeOf(Char));
Stream.Read(str[1], Len);

ну да, верно. Если записывалось этим же кодом, скомпилированом в той же версии.

Автор: kami 15.9.2009, 21:59
Цитата(bems @  15.9.2009,  21:43 Найти цитируемый пост)
 Если записывалось этим же кодом, скомпилированом в той же версии.

Действительно.
То есть и здесь нужно делать "соглашение о вызовах"... либо и там и там WideString либо AnsiString...
Как все непросто с этим unicode...

Автор: bems 15.9.2009, 22:14
имхо лучше везде записывать unicode, а код продублировать для разный делфей. Ну а там где встроенного нет - преобразовывыть руками. Зато никаких неопределенностей между модулями.

Автор: former 15.9.2009, 22:56
Цитата(bems @  15.9.2009,  22:14 Найти цитируемый пост)
имхо лучше везде записывать unicode, а код продублировать для разный делфей. Ну а там где встроенного нет - преобразовывыть руками. Зато никаких неопределенностей между модулями. 

Это уже к вопросу о переносимости кода. А если таких сток много и под несколько версий нужно делать.
ИМХО: лучше все же использовать 
Цитата(kami @  15.9.2009,  21:59 Найти цитируемый пост)
"соглашение о вызовах"


Автор: bems 15.9.2009, 23:05
Ну и чем же лучше?
Если используется это ваше соглашение (в начале строки есть информация о кодировке), то модуль скампилированый в ansi-версии должен уметь преобразоватьб для себя в ansi. Аналогично уникодный модуль - в уникод (и при этом возникает весьмя явная неопределенность с кодовой страницей, которую тоже нужно включать в соглашение).

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

Добавлено через 3 минуты и 8 секунд
Или (лучше всего) все машины в сети скомпилированы в одной версии. Если есть исключения - это их проблема и им нужно обновиться. И лучше бы этой версией была уникодная.

Автор: former 16.9.2009, 01:52
Цитата(bems @  15.9.2009,  23:05 Найти цитируемый пост)
Или (лучше всего) все машины в сети скомпилированы в одной версии.

Само собой.
Цитата(bems @  15.9.2009,  23:05 Найти цитируемый пост)
И лучше бы этой версией была уникодная. 

Так оно и есть.
Я имел виду  при создании компонент (для примера).

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)