-
Подскажите плиз новичку.
Пишу сервер на базе компонента IdTCPServer.
К серверу цепляется клиент.
Клиент формирует запросы к серверу и получает ответы.
Но клиент может "подписаться" на определенные события сервера и в этом случае сервер высылает клиенту уведомление о наступлении конкретного события.
Обмен асинхронный. Т.е. клиент не ждет ответа сервера после запроса (хотя тайм-аут на запрос все-таки у клиента по моему есть).
В ответе сервера первое поле - информация о том, что именно пришло от сервера и потому клиент без проблем разгребает поток сообщений от сервера.
Пока реализована часть "запрос-ответ". Все стандартно. Тем более клиент один и мультиподключение не требуется.
Но дошли руки до событий. Они формируются в потоке основной программы , а не в потоке IdTCPServer1Execute. При этом нужно не давать одному ответу "вклинится" в процесс уже идущей передачи.
Как это лучше реализовать?
1) Всем кидать сообщения для отправки в список? А отдельным потоком выплевывать сообщения клиенту? С оформлением критической секции?
2) Отправлять сообщения в одном из потоков ( нитей), а между нитями перетаскивать TIdNotify? Я сейчас так логи из IdTCPServer1Execute в основную форму таскаю для вывода на экран.
3) Ещё другие варианты?
Подскажите по более идеологически правильному варианту ...
-
1. У каждого соединения с клиентом свой контекст.
2. В каждом контексте своя переменная содержащая список событий на которые клиент подписан.
3. В каждом контексте своя локальная очередь событий.
4. При появлении нового события, надо перебрать контексты подключенных клиентов и если новое событие имеется в списке на которые клиент подписан, то скопировать это событие в его локальную очередь.
-
Ну и соответственно поток обслуживающий соединение с клиентом периодически смотрит локальную очередь и отправляет события там находящиеся.
-
Ну это понятно. Немного "не то". Клиент 1. Всегда один. При коннекте сохраняю контекст потока в глобальной переменной для отправки сообщений.
В программе 2 потока (Thread): основной поток и поток приема сообщений TCPServer.
Организовывать отправку в потоке IdTCPServer смысла нет, так как поток основное время стоит в ожидании относительно редких событий readln. Пусть занимается своей прямой работой: ловит с команды с клиента, делает case на 40 с лишним вариантов, обрабатывает команды.
В основном приложении создаю отдельный TThread и в нем постоянно контролирую локальную очередь. Есть сообщения - отправляю.
Сейчас экспериментирую с разными вариантами локальной очереди и её потокобезопасность. Что-бы в каждый момент только один поток работал с очередью.
-
> DanilinS © (27.06.14 11:46) [3]
> Клиент 1. Всегда один.
Я бы не стал на это закладываться даже если клиент действительно планируется один, это чревато ошибками, тем более не стоит в глобальной переменной хранить контекст подключения. Список подключений он и так хранится сервером.
> В программе 2 потока (Thread): основной поток и поток приема
> сообщений TCPServer.
Их больше на самом деле. Как минимум 3. Есть еще поток слушающий сокет сервера.
> Сейчас экспериментирую с разными вариантами локальной очереди
> и её потокобезопасность.
Очередь - это одна из немногих структур данных, потокобезопасность которой обеспечить очень легко. Практически достаточно обернуть Pop и Push критическими секциями.
-
Наличие 2-х клиентов не предполагается да-же теоретически. И свойство MaxConnections стоит 1.
Но передавать контекст подключения через глобальную переменную не стоит - действительно Вы правы. Ошибочное и некрасивое решение. Перепишу.
Все сообщения имеют вид string. А потому и использую TList как буфер отправки. Естественно с критическими секциями для избежания проблем.
Пока нет четкого видения процесса отправки сообщений. Пока реализовано в отдельном TThread. Крутится, смотрит на размер TList. Есть начинка - отсылает. С очисткой TList.
.
-
> 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;
-
> Пока нет четкого видения процесса отправки сообщений. Пока
> реализовано в отдельном TThread. Крутится, смотрит на размер
> TList. Есть начинка - отсылает. С очисткой TList.
Я бы сделал так. Поток, который должен отсылать не крутится, а ждет взведения события (TEvent) прихода новых данных или события собственного завершения.
-
Спасибо. Буду пробовать. Не знал о существовании TThreadList.