Реалізація чату на ASP.NET за допомогою Long Polling
Я підключився до нього з декількох браузерів і здивувався, що другий браузер реагує на повідомлення першого практично миттєво, що виключало можливість роботи цього чату за допомогою періодичного опитування сервера скриптом на клієнтській сторінці. Ну, або частота цього опитування має бути настільки високою, що будь-який сервер просто захлинувся б у цих запитах.
І постало переді мною питання – як вони це зробили? Як вони забезпечили таку високу швидкість віддачі нових даних? Адже веб-сервер на те і сервер, що може лише відповідати на запити користувача, але ніяк не самостійно ініціювати запити.
Нетривалий пошук в Інтернеті дав мені відповідь. І відповідь ця "Long Polling ", а зустрічається ще й інша назва технології -Comet.
Отже, як це працює?
По суті це все той же старий добрий AJAX, і використання об'єктаXmlHttpRequest. З однією великою різницею на серверній стороні. Різниця полягає ось у чому. Клієнт посилає XHR-запит і тривалий час чекає на відповідь сервера. А "тривалий час" відгуку забезпечує сам сервер, який не відразу віддає дані клієнту, а лише тоді, коли в черзі з'являються свіжі дані, тобто коли йому дійсно є що сказати.
Клієнт, дочекавшись відповіді, обробляє його (наприклад, відображає повідомлення на сторінці), а потім, негайно, знову запитує сервер. І знову чекає.
Якщо ж подав запит клієнт так і не дочекався відповіді через тайм-аут, то він знову запитує сервер. А звідки цей таймаут? А просто серверу нам нема чого відповісти, ось він і не віддає клієнту результату запиту. Чекає. Але не дочекавшись, розриває з'єднання по таймууту. Така непроста схема.
Це і називаєтьсяLongPolling, тобто – тривале опитування.
Але від слів до діла. Зараз ми виготовимо серверну та клієнтську частину простого інтернет-чату, побудованого на описаній технології. Ми докладно розглянемо роботу всього серверного та клієнтського коду і навіть наприкінці статті отримаємо посилання на цілком працездатний продукт. Відразу скажу, він має деякі обмеження. Ви, наприклад, не зможете його використовувати в режимі сильного навантаження, тобто наявність одночасно великої кількості клієнтів. У ньому немає схеми очищення "втрачених" сполук. Це лише працездатний навчальний приклад. Всі аспекти коду, що бракують, пропоную вам на самостійне доопрацювання.
Отже… Без особливих роздумів наведу тут листинг коду та поясню принцип його дії.
6-14 рядки – інкапсулюють клас повідомлення від користувача, як бачимо, там ще є метод, що перетворює об'єкт у JSON-рядок. Взагалі, по-хорошому, тут треба скористатися звичайним JSON-серіалізатором, а не містити рядок вручну.
Далі йде опис класуCometServer, в якому і живе вся серверна логікаLongPolling -технології.
Зауважимо, що в змінній _clientStateList (рядок 24) зберігаються всі підключені на даний момент до сервера клієнти.
МетодPushMessage (рядок 24) пробігає по всьому списку клієнтів і пише у вихідний потік кожного передане йому як аргумент повідомлення, природно, попередньо серіалізувавши його. Після чого для кожного клієнта обов'язково викликається метод CompleteRequest(), який завершує асинхронний запит, передаючи його результати клієнту.
МетодUpdateClient (рядок 51) за отриманим ним гуїдом клієнта, знаходить клієнта в списку clientStateList і оновлює всі його параметри, такі як HttpContext-наприклад. Цимми завжди забезпечуємо актуальний стан списку клієнтів, після кожного їх реконнекту.
МетодRegicterClient (рядок 69) /друк не вийшов у його імені, самі виправте при необхідності:)/ додає нового клієнта в чергу.
МетодUnregisterClient (рядок 80) відповідно – видаляє клієнта з черги. Що цілком легально. Навіщо повідомляти клієнта про нові повідомлення у чаті, якщо він давно залишив чат?
Далі йде опис класуCometAsyncState. Навіщо ми його створили? Так, щоб зберігати такі параметри клієнта, якCurrentContext, AsyncCallback і ClientGuid. Ну, тобто, щоб окремий потік, породжений у пулі потоків і відповідальний за одного клієнта, міг власне функціонувати та взаємодіяти з клієнтом. Цей клас, за великим рахунком, лише набір заглушок для методів інтерфейсуIAsyncResult плюс методCompleteRequest (), який викликаєcallback -функцію при завершенні потоку.
Ну і зрозуміло, код серверного хендлераcomet.ashx, який обробляє команди клієнтського скрипта. Скажу відразу, хендлер цей асинхронний, а тому успадкований не відIHttpHandler, а відIHttpAsyncHandler
Основна логіка програми у методіRequestWorker (рядки 51-95), який залежно від отриманої з клієнта команди, виконує або реєстрацію/розреєстрацію клієнта, або відправлення повідомлення. Або оновлення даних клієнта при його реконнекте.
Тут ви можете завантажити готовий працюючий проект чату. Для того, щоб його протестувати, просто запустіть його на двох сторінках браузера і спробуйте в одному вікні надіслати повідомлення. Спостерігайте при цьому за другим віконцем.