Pusher
Код лежит тут
У меня на работе встал вопрос с реализацией удаленных прямых телевизионных
трансляций . Нужно было написать TCP клиент-серверное приложение для их поддержки.
Этот проект мы назвали Pusher - от слова толкать .
Рассмотрим схему:
============ ================
| клиент 1 | <--> | приемник 1 |
============ ================
============== ============ ============ ================
| источник | <--> | клиент 2 | <--- ... Интернет ... ----> | сервер | <--> | приемник 2 |
============== ============ ============ ================
============ ================
| клиент 3 | <--> | приемник 3 |
============ ================
Имеется веб-сервер с 'белым' ip-шником , к которому коннектятся
приемники трансляции. На другом конце - источник трансляции , физически расположенный
на клиентском компьютере с 'серым' ip-шником . Задача - передавать в real-time
видео-поток с клиента на сервер .
Эта схема укладывается в традиционную архитектуру 'TCP client-server' .
На сервере размещены 2 прослушивающих сокета : правый слушает запросы с серверного
приемника , левый слушает запросы с клиентской машины. На клиенте - также 2 сокета ,
правый клиентский сокет коннектится к левому сокету сервера , левый клиентский сокет
коннектится к источнику .
клиент
================
<---->| 8003 | 8005 |<---->
================
сервер
================
<---->| 8005 | 8007 |<---->
================
Каждый раз , когда будет присоединяться новый приемник , на левом клиенте будет создаваться новый поток ,
который будет коннектиться к источнику и обслуживать данный приемник .
Для того , чтобы протестировать эту модель локально , мы возьмем в качестве источника
и приемника медиа-проигрыватель VLC , который умеет вещать и делать http-запросы .
Так , в качестве источника запустим VLC с параметрами :
vlc -vvv film.avi --sout '#standard{access=http,mux=asf,dst=127.0.0.1:8003}'
В нашей схеме источник будет вещать на порт 8003 , к которому будет коннектиться
левый клиент .
Общение между правым клиентом и левым сервером будет происходить по порту 8005.
В качестве приемника запустим VLC с параметрами - можно запустить сразу несколько команд :
vlc http://127.0.0.1:8007
VLC в качестве приемника будет делать запросы на порт 8007 , который будет обслуживать правый сервер.
Если вы сразу замените в локальном варианте порт 8007 на 8003 , то приемник
непосредственно обратится к источнику , откроется окно VLC , и вы начнете смотреть фильм .
В нашей схеме , если все правильно написано , в результате должно произойти то же самое -
откроется VLC-ное окно для просмотра фильма .
Я написал сначала версию на питоне , позже сделал си-шный вариант .
Рассмотрим вариант на С .
Пушер имеет многопоточную архитектуру на базе pthread : такой выбор я сделал для удобства разделения логики работы.
В проекте 2 многопоточных приложения - клиент и сервер .
Два сокета внутри сервера разнесены по разным потокам , равно как и два сокета внутри клиента .
Обмен данными между двумя потоками как внутри сервера , так и внутри клиента ,
выполняется через глобальные связные списки.
Потоки внутри приложения также обращаются к глобальным структурам , доступ к которым
синхронизирован с помощью стандартных блокировок .
В заголовок каждого фрейма записывается id-шник дескриптора приемника , которому предназначен этот фрейм .
В нужный момент он парсится при получении .
Особенность локальной реализации , которая завязана на VLC , обуславливает следующую
последовательность событий :
1. Правый слушающий сокет получает от приемника специфическую VLC-шную строку коннекта
и сохраняет ее в глобальной переменной.
2. Левый слушающий сокет проверяет эту переменную и передает строку коннекта
правому клиенту , который сохраняет ее в свою очередь также в глобальной переменной.
3. Левый клиент читает эту глобальную переменную и отдает строку источнику
4. Получив 'нормальную' строку коннекта , VLC начинает отдавать видео-данные левому клиенту,
при этом создается отдельный поток для обслуживания данного приемника .
5. Левый tcp-клиент получает видео-данные и добавляет фрейм в связаный список client_frame.
Он увеличивает счетчик фреймов и сообщает об правому клиенту , который может находиться в спячке .
6. Правый tcp-клиент , получив сообщение , просыпается , извлекает видео-данные из связного списка client_frame ,
сохраняет id-шник дескриптора приемника в заголовке фрейма, записывает фрейм в свой дескриптор ,
после чего уменьшает счетчик фреймов .
7. Левый tcp-сервер читает свой дескриптор и извлекает видео-данные из фрейма,
получает id-шник дескриптора приемника из заголовка фрейма , после чего добавляет фрейм в связный список server_frame ,
затем увеличивает счетчик фреймов и сообщает об этом правому серверу , который может находиться в спячке .
8. Правый tcp-сервер просыпается , извлекает видео-данные из связного списка server_frame и записывает
фрейм в свой дескриптор данного приемника , уменьшая счетчик фреймов .
9. Приемник получает свои данные , и вы начинаете смотреть фильм .
Если вы посмотрите в топ , то увидите , что и клиент , и сервер практически не потребляют мощности процессора.
Вы сможете открыть столько приемников , сколько позволит ваш компьютер и графическая система .
В этой версии еще есть над чем поработать :
выключение работающего приемника в данной версии некорректное и приводит к перегрузке сервера .
|