Opened 8 years ago

Last modified 7 years ago

#482 closed задача

Алгоритм — at Version 29

Reported by: san Owned by: dimag
Priority: critical Milestone:
Component: ПО MC04-Dispatcher. Пульт диспетчера/техника Keywords:
Cc: alx

Description (last modified by san)

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

Пока в алгоритме обнаружены следующие недостатки:

  1. Следуюшую команду на сервер нельзя отправлять не дождавшись ответа, а программа так делает
  2. Зачем ждать ивента 60мс, если и так всё складывается в очередь. Нет объяснения.
  3. Каков смысл отправки двух команд "проверки связи". Нет объяснения.

Change History (29)

comment:1 by dimag, 8 years ago

Прежде всего есть 2 потока: поток комманд и поток сообщений.
Работа потока команд организованна следующим образом:
1)Приходит сообщение по которому генерируется команда для FreeSwitch сервера.
2)формируется команда, которая отправляется через ESL на FreeSwitch сервер с помощью функции esl_send_recv, определённую из функции esl_send_recv_timed:
#define esl_send_recv(_handle, _cmd) esl_send_recv_timed(_handle, _cmd, 0)
где handle - идентификатор соединения, _cmd-команда, 0-время ожидая, означающее что ждать пока от FreeSwtich не поступит ответ о поставке переданной команды на обработку. Если не будет ответа от сервера, например, связь разорвана, то будет возвращена ошибка.
3)Если в сообщение требует сделать 2 и более команды, то снова формируется команда как пункте 2, пока все команды не будут отосланы.
4)Сообщение обработано, поток команд переходит в ожидание нового сообщения

Last edited 8 years ago by dimag (previous) (diff)

comment:2 by dimag, 8 years ago

Работа потока сообщений организованна следующим образом:
Таймер вызывает функцию в потоке для считывания команд с интервалом 1 раз в 50 миллисекунд.
1)Раз в пять секунд проверяем наличие связи с сервером, если связь есть:
Для этого:
Отправляем первый запрос bgapi status, ответ на него не смотрим, смотрим принята ли команда на FreeSwitch сервере к обработке. Запрос отправляется с помощью функции ниже:
esl_send_recv_timed(handle, request.c_str(), iDelay),
где handle - идентификатор соединения, _cmd-команда, iDelay-время ожидания, для данного равно 200, что означает, что ждать ответа 200 мс, если нет, то считать команду не выполненной.
Если первый запрос был получен FreeSwitch сервером, неудачен, то топравляем повторный запрос но со временем ожидания 600 мс, если и этот запрос неудачен, то считаем что соединения нет, выводим соответствующее сообщение, выходим
2) Если нет соединения, то каждую секунду пытаемся его восстановить. Если соединение успешно восстановилось, скрываем ошибку, обновляем информацию о текущих пользователях и конференциях. Если не восстановилось, то ожидаем в течение 1 секунды, делаем новую попытку соединения.
3)Если соединение есть, то считываем накопленные сообщения с помощью
esl_recv_event_timed(handle, 60, 1, NULL), где handle - идентификатор соединения, 60 - время ожидания ответа с сервером, 1 - возвращать последнее сохранённое сообщение, NULL - не сохранять сообщение в специально отведённой структуре, а сохранять в handle.
Если сообщение считано то пробуем считать другие с помощью той же функции, пока все сообщения не будут выбраны из очереди.
Далее приступаем к обработке сообщений.

Last edited 8 years ago by dimag (previous) (diff)

comment:3 by san, 8 years ago

Cc: alx added

comment:4 by san, 8 years ago

Уточняю comment:1 "Поток команд"
пункт 2) Что произойдёт если ответ от сервера не придёт ?

comment:5 by dimag, 8 years ago

Дополнительные разъяснения в тексте первых двух комментариев.

in reply to:  2 ; comment:6 by san, 8 years ago

comment:2

1 раз в 50 секунд.

  1. или 50раз в секунду?

ждать ответа 200 мс, если нет, то считать команду не выполненной.
топравляем повторный запрос но со временем ожидания 600 мс,

  1. Запросы, я так понимаю, идут друг за другом в рамках одного соединения?

Какой смысл в различных тамаутах?

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

если и этот запрос неудачен, то считаем что соединения нет, выводим соответствующее сообщение, выходим

  1. Соединение разрываем?

esl_recv_event_timed(handle, 60, 1, NULL)

  1. Я так понимаю что эта функция просмотрит сначала очередь сообщений, и если там пусто то будет 60мс ждать не придёт ли что от сервера. Так?

Алексей, обращаюсь отдельно к тебе, ответь пожалуста тоже, на мой вопрос №3.

in reply to:  1 comment:7 by san, 8 years ago

Replying to dimag:

Если не будет ответа от сервера, например, связь разорвана, то будет возвращена ошибка.

  1. Каков таймаут ожидания ответа от сервера в этом случае?
  2. Если соединение разорвано и вызов функции возвратит ошибку, какова реакция программы на эту ошибку?

comment:8 by dimag, 8 years ago

2)Для улучшения реагирования на команды пользователя
4)Пробуем создать новое ESL соединение, после успешного создания ESL соединения старое разрывается, во всех дальнейших запросах используется новое соединение
5)Функция ждёт новых сообщений в течение 60 мс, если сообщение есть, то возвращает его с результатом функции ESL_SCUESS, если нет сообщений и не появилось, то возвращает ESL_FAIL.
6)Интервал 0 - время ожидания определяет ESL
7)Ничего дальше не делаем, выходим из обработчика сообщений в потоке команд.

in reply to:  6 comment:9 by alx, 8 years ago

Replying to san:

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

Алексей, обращаюсь отдельно к тебе, ответь пожалуста тоже, на мой вопрос №3.

Я смысла в такой "двойной" проверке не нахожу. Из каких соображений применен такой алгоритм, не могу себе представить.

Более того, когда мы беседовали устно в Зале Совещаний, без ответа остался вопрос о том, допускает ли вообще протокол event-socket'а передавать новый запрос до того как получен ответ на предыдущий. Если протокол этого не допускает (как, например, протокол HTTP), то описанный алгоритм просто не соответствует спецификации протокола и, следовательно, не будет работать.

comment:10 by dimag, 8 years ago

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

in reply to:  8 comment:11 by san, 8 years ago

До мьютексов мы ещё не добрались
Цитирую тебя

Отправляем первый запрос ... ждать ответа 200 мс, если нет, то считать команду не выполненной... то топравляем повторный запрос

  1. Правильно я понимаю суть:
  • Посылаем запрос, если ответа нет более 200мс, то посылаем новый запрос.

????

Если да, то:

2)Для улучшения реагирования на команды пользователя

  1. Каким образом реагировани на команды пользователя улучшится, благодаря отправке двух сообщений подряд?
  2. Повторю вопрос Алексея: Допускает ли вообще протокол event-socket'а передавать новый запрос до того как получен ответ на предыдущий?

comment:12 by dimag, 8 years ago

9)Есть и вторая причина о которой я уже говорил, Wi-Fi - иногда бывают разрывы связи, которые длятся несколько сотен миллисекунд. Если такой разрыв придётся на момент вызова, то будет ложная индикация разрыва связи, для этого предусмотрен второй длинной по времени ожидания запрос.
10)В документации которая попалась мне об этом не было написанно, но в примерах запрос выполнялся вместе с мутексами, то есть, скорее всего, в данный текущий момент времени можно посылать только 1 запрос.

in reply to:  12 comment:13 by san, 8 years ago

Replying to dimag:

9)Есть и вторая причина о которой я уже говорил, Wi-Fi - иногда бывают разрывы связи, которые длятся несколько сотен миллисекунд. Если такой разрыв придётся на момент вызова, то будет ложная индикация разрыва связи, для этого предусмотрен второй длинной по времени ожидания запрос.

  1. Чем в этом случае два идущих подряд запроса 200+600мс будут лучше чем один запрос с таймаутом 800мс ?

comment:14 by san, 8 years ago

  1. Уточни, как всё-таки ты считаешь, можно передавать новый запрос до того как получен ответ на предыдущий или нет?

comment:15 by dimag, 8 years ago

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

comment:16 by dimag, 8 years ago

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

comment:17 by san, 8 years ago

  1. Если ответ на команду не приходит 800мс, следовательно и другие ивенты не придут,

так? тогда о каком обновлении статусов речь?

in reply to:  14 ; comment:18 by alx, 8 years ago

Replying to san:

  1. Уточни, как всё-таки ты считаешь, можно передавать новый запрос до того как получен ответ на предыдущий или нет?

Я попробую ответить исходя из логики. Ответ сервера на команду имеет такой формат:

Content-Type: api/response
Content-Length: xxx

bla-bla-bla

+OK

Таким образом, в ответе сервера на запрос клиента не содержится информации, которая позволила бы однозначно соотнести полученный ответ с отправлявшимся ранее запросом. Следовательно, применять описанный Димой алгоритм нельзя, так как программа не имеет возможности определить, на какой из запросов получен ответ. Возможен такой сценарий:

  1. Клиент отправляет серверу запрос 1.
  2. Сервер получает из сокета запрос 1 и приступает к его обработке.
  3. Возникает таймаут, esl_send_recv_timed() завершается с ошибкой.
  4. Клиент отправляет серверу запрос 2.
  5. Сервер закончил обработку запроса 1 и передал клиенту ответ на запрос 1.
  6. Сервер читает из сокета запрос 2 и приступает к его обработке.
  7. Клиент получил ответ на запрос 1, и трактует его как ответ на запрос 2. esl_send_recv_timed() завершается успешно.
  8. Клиент отправляет серверу следующий запрос 3.
  9. Сервер закончил выполнение запроса 2 и передал клиенту ответ на запрос 2.
  10. Клиент получает ответ на запрос 2 и трактует его как ответ на запрос 3.

... и так далее...

in reply to:  16 comment:19 by alx, 8 years ago

Replying to dimag:

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

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

comment:20 by dimag, 8 years ago

Если ответ не придёт в течение 800 мс, то можно считать что соединение отсутствует, но зачем такая большая задержка, лучше быстро проверить наличие соединения за 200 мс, потом, при отсутствие, которое может быть вызвано проблемами Wi-Fi или неполадками на проводной линии, ждать ответа в течение 600 мс.

in reply to:  18 ; comment:21 by dimag, 8 years ago

Replying to alx:

Replying to san:

  1. Уточни, как всё-таки ты считаешь, можно передавать новый запрос до того как получен ответ на предыдущий или нет?

Я попробую ответить исходя из логики. Ответ сервера на команду имеет такой формат:

Content-Type: api/response
Content-Length: xxx

bla-bla-bla

+OK

Таким образом, в ответе сервера на запрос клиента не содержится информации, которая позволила бы однозначно соотнести полученный ответ с отправлявшимся ранее запросом. Следовательно, применять описанный Димой алгоритм нельзя, так как программа не имеет возможности определить, на какой из запросов получен ответ. Возможен такой сценарий:

  1. Клиент отправляет серверу запрос 1.
  2. Сервер получает из сокета запрос 1 и приступает к его обработке.
  3. Возникает таймаут, esl_send_recv_timed() завершается с ошибкой.
  4. Клиент отправляет серверу запрос 2.
  5. Сервер закончил обработку запроса 1 и передал клиенту ответ на запрос 1.
  6. Сервер читает из сокета запрос 2 и приступает к его обработке.
  7. Клиент получил ответ на запрос 1, и трактует его как ответ на запрос 2. esl_send_recv_timed() завершается успешно.
  8. Клиент отправляет серверу следующий запрос 3.
  9. Сервер закончил выполнение запроса 2 и передал клиенту ответ на запрос 2.
  10. Клиент получает ответ на запрос 2 и трактует его как ответ на запрос 3.

... и так далее...

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

in reply to:  20 comment:22 by alx, 8 years ago

Replying to dimag:

Если ответ не придёт в течение 800 мс, то можно считать что соединение отсутствует, но зачем такая большая задержка,

Для исключения ложного срабатывания.

Насколько я помню, заказчик требует определять отсутствие связи с сервером за время, не превышающее 10 секунд. Следовательно, если Вы сделаете таймаут 9 секунд (9000 миллисекунд), требование заказчика будет выполнено. Я не вижу причины делать таймаут меньше 9 секунд.

лучше быстро проверить наличие соединения за 200 мс,

Такой короткий таймаут будет давать много ложных срабатываний.

потом, при отсутствие, которое может быть вызвано проблемами Wi-Fi или неполадками на проводной линии, ждать ответа в течение 600 мс.

То есть Вы признаете тот факт, что таймаут 200 мс слишком короткий. В таком случае непонятно, нафига он используется.

in reply to:  21 comment:23 by alx, 8 years ago

Replying to dimag:

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

Объясните, пожалуйста, как в приведенном ниже сценарии клиент может определить, на какой именно запрос получен ответ. Сценарий такой:

  1. Клиент отправляет запрос 1:
    api command1
    
    
  1. Клиент отправляет запрос 2:
    api command2
    
    
  1. Клиент получает ответ:
Content-Type: api/response
Content-Length: xxx

...какие-то-данные...

comment:24 by dimag, 8 years ago

То что получает клиент в пункте 3, это сообщение, сообщения и команды разные вещи. Одна команда может сгенерировать много сообщений.
Посылка каждой команды всегда возвращает ответ в виде возвращаемого значения функции, показывающего, успешно доставлена ли команда и если успешно, то ответ сервера начинающегося с OK, если команда успешно принята сервером или ERR, если команда ошибочна в поле handle->last_sr_event->body.
На некоторые команды я жду ответа, например api list_users, api create_uuid, результат возвращается в том же поле.

comment:25 by dimag, 8 years ago

Я сделаю дальнейшее уточнение. В процессе работы я отсылаю на сервер команды, запросы и получаю сообщения.
Команда - то что я посылаю на сервер и жду на неё ответ в виде определённого сообщения, сообщений, например bgapi originate. Я посылаю эту команду и жду на неё ответа, по результатам ответа я обновляю структуры данных и перерисовываю картинку с состоянием конференций
Запрос - команда, результат которой возвращается и ответ на которую я жду в теле идентификатора соединения, это ответ обрабатывается и используется в дальнейшем, никакие сообщения на запрос не ожидаются, пример запроса - например api create_uuid. Жду пока появиться ответ, беру ответ в handle->last_sr_event->body и использую.
Сообщения - результат выполнения моих команд и команд других абонентов или операторов, по результатам сообщений я обновляю структуры данных и перерисовываю картинку с состоянием конференций.

comment:26 by dimag, 8 years ago

Если не будет дальнейших вопросов, то завтра 26.10.2016 закрою тикет.

in reply to:  1 comment:27 by san, 8 years ago

:-D

По какой причине?

comment:28 by san, 8 years ago

Replying to dimag:

2)формируется команда, которая отправляется через ESL на FreeSwitch сервер с помощью функции esl_send_recv, определённую из функции esl_send_recv_timed:
#define esl_send_recv(_handle, _cmd) esl_send_recv_timed(_handle, _cmd, 0)
где handle - идентификатор соединения, _cmd-команда, 0-время ожидая, означающее что ждать пока от FreeSwtich не поступит ответ о поставке переданной команды на обработку. Если не будет ответа от сервера, например, связь разорвана, то будет возвращена ошибка.

  1. Сколько будет длится ожидание ответа на команду? бесконечно?
  2. Что будет если в момент пока ответ ещё ожидается тикнет таймер "проверки связи"?

comment:29 by san, 8 years ago

Description: modified (diff)

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

Note: See TracTickets for help on using tickets.