Конференция "Сети" » Реализация очереди на отправку сообщений.
 
  • DanilinS © (25.06.14 14:02) [0]
    Подскажите плиз новичку.
    Пишу сервер на базе компонента IdTCPServer.
    К серверу цепляется клиент.
    Клиент формирует запросы к серверу и получает ответы.
    Но клиент может "подписаться" на определенные события сервера и в этом случае сервер высылает клиенту уведомление о наступлении конкретного события.
    Обмен асинхронный. Т.е. клиент не ждет ответа сервера после запроса (хотя тайм-аут на запрос все-таки у клиента по моему есть).
    В ответе сервера первое поле - информация о том, что именно пришло от сервера и потому клиент без проблем разгребает поток сообщений от сервера.

    Пока реализована часть "запрос-ответ". Все стандартно. Тем более клиент один и мультиподключение не требуется.

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

    Как это лучше реализовать?
    1) Всем кидать сообщения для отправки в список? А  отдельным потоком выплевывать сообщения клиенту? С оформлением критической секции?
    2) Отправлять сообщения в одном из потоков ( нитей), а между нитями перетаскивать TIdNotify? Я сейчас так логи из  IdTCPServer1Execute в основную форму таскаю для вывода на экран.
    3) Ещё другие варианты?

    Подскажите по более идеологически правильному варианту ...
  • DVM © (25.06.14 15:58) [1]
    1. У каждого соединения с клиентом свой контекст.
    2. В каждом контексте своя переменная содержащая список событий на которые клиент подписан.
    3. В каждом контексте своя локальная очередь событий.
    4. При появлении нового события, надо перебрать контексты подключенных клиентов и если новое событие имеется в списке на которые клиент подписан, то скопировать это событие в его локальную очередь.
  • DVM © (25.06.14 15:59) [2]
    Ну и соответственно поток обслуживающий соединение с клиентом периодически смотрит локальную очередь и отправляет события там находящиеся.
  • DanilinS © (27.06.14 11:46) [3]
    Ну это понятно. Немного "не то". Клиент 1. Всегда один. При коннекте сохраняю контекст потока в глобальной переменной для отправки сообщений.

    В программе 2 потока (Thread): основной поток и поток приема сообщений TCPServer.

    Организовывать отправку в потоке IdTCPServer смысла нет, так как поток основное время стоит в ожидании относительно редких событий readln. Пусть занимается своей прямой работой: ловит с команды с клиента, делает case на 40 с лишним вариантов, обрабатывает команды.

    В основном приложении создаю отдельный TThread и в нем постоянно контролирую локальную очередь. Есть сообщения - отправляю.

    Сейчас экспериментирую с разными вариантами локальной очереди и её потокобезопасность. Что-бы в каждый момент только один поток работал с очередью.
  • DVM © (27.06.14 12:19) [4]

    > DanilinS ©   (27.06.14 11:46) [3]


    > Клиент 1. Всегда один.

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


    > В программе 2 потока (Thread): основной поток и поток приема
    > сообщений TCPServer.

    Их больше на самом деле. Как минимум 3. Есть еще поток слушающий сокет сервера.


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

    Очередь - это одна из немногих структур данных, потокобезопасность которой обеспечить очень легко. Практически достаточно обернуть Pop и Push критическими секциями.
  • DanilinS © (27.06.14 14:36) [5]
    Наличие 2-х клиентов не предполагается да-же теоретически. И свойство MaxConnections стоит 1.
    Но передавать контекст подключения через глобальную переменную не стоит - действительно Вы правы. Ошибочное и некрасивое решение. Перепишу.

    Все сообщения имеют вид string. А потому и использую TList как буфер отправки. Естественно с критическими секциями для избежания проблем.

    Пока нет четкого видения процесса отправки сообщений. Пока реализовано в отдельном TThread. Крутится, смотрит на размер TList. Есть начинка - отсылает. С очисткой TList.

    .
  • DVM © (27.06.14 15:22) [6]

    > DanilinS ©   (27.06.14 14:36) [5]


    > А потому и использую TList как буфер отправки. Естественно
    > с критическими секциями для избежания проблем.

    А вот с потокобезопасностью TList как раз не так все просто. Лучше тогда взять TThreadList и работать по его правилам.


    > Пока реализовано в отдельном TThread. Крутится, смотрит
    > на размер TList. Есть начинка - отсылает. С очисткой TList.
    >

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

    Lock;
    Count := List.Count;
    Unlock;
    ... что еще а может и ничего вообще ...
    if Count > 0 then
     begin
       Lock;
       ... перебираем элементы списка ...
       Unlock;
     end;

    а вот так будет правильно:

    Lock;
    Count := List.Count;
    ... что еще а может и ничего вообще ...
    if Count > 0 then
     begin
       ... перебираем элементы списка ...
     end;
    Unlock;
  • DVM © (27.06.14 15:29) [7]

    > Пока нет четкого видения процесса отправки сообщений. Пока
    > реализовано в отдельном TThread. Крутится, смотрит на размер
    > TList. Есть начинка - отсылает. С очисткой TList.

    Я бы сделал так. Поток, который должен отсылать не крутится, а ждет взведения события (TEvent) прихода новых данных или события собственного завершения.
  • DanilinS © (27.06.14 15:38) [8]
    Спасибо. Буду пробовать. Не знал о существовании TThreadList.
 
Конференция "Сети" » Реализация очереди на отправку сообщений.
Есть новые Нет новых   [134427   +35][b:0][p:0]