Пишем Comet-чат

    Хочу поделиться своим опытом создания простого Comet-чата. Периодически читал про эту технологию, и сейчас решил попробовать сделать что-либо сам. Получился небольшой чат, интерфейс которого я старался сделать похожим на интерфейс irc-клиента mIRC. Так как подобную вещь пишу первый раз, просьба прокомментировать возможные ошибки в программе и статье и описать более оптимальные пути решения задач. Посмотреть на работающий чат можно здесь: http://94.127.68.84:6884/

    Как я это представил


    Отличительной особенностью comet-приложений является нахождение в состоянии постоянного опроса сервера, который на запросы от клиента отвечать не спешит и удерживает соединение. Такой подход называется long-polling и делает возможным server push — пересылку данных от сервера клиенту ровно в тот момент, когда на сервере произошло событие (в чат вошел новый участник, отправлено сообщение).

    Таким образом, в чате придется использовать минимум 2 соединения клиента с сервером, одно из которых отвечает только за получение данных, постоянно опрашивает сервер, и переустанавливается в случае получения данных, таймаута или разрыва, а второе — только за передачу данных на сервер. Данные будут передаваться в формате JSON и будут представлять собой массив хешей — действий, которые нужно выполнить клиенту или серверу (например, отобразить пришедшее сообщение или обработать запрос на авторизацию).

    Как я это реализовал


    Конфигурация nginx

    Между сервером чата и клиентом находится веб-сервер nginx. Клиенту чата можно, конечно, напрямую общаться с серверной частью, но nginx я решил вставить по нескольким причинам:
    • nginx берет на себя отдачу статики, поддержание соединений и вообще все общение по протоколу http
    • Не нужно светить лишним портом наружу
    • Защита от флуда малой кровью

        limit_req_zone $binary_remote_addr zone=one:2m rate=1r/s;

        server {
            listen 6884;

            location / {
                root /home/vk/CometChat/htdocs/;
            }

            # сервер чата будет доступен по урлу /chat, подключение через протокол FastCGI
            location /chat {
                # этих двух параметров достаточно для работы приложения
                fastcgi_param QUERY_STRING $query_string;
                fastcgi_param REMOTE_ADDR $remote_addr;

                fastcgi_intercept_errors on;
                fastcgi_connect_timeout 3;
                # разрывать соединение с клиентом и FastCGI-сервером чата после 40 секунд
                fastcgi_read_timeout 40;

                fastcgi_pass unix:/home/vk/chat.socket;

                # защита от флуда, ограничение на 1 запрос в секунду
                limit_req zone=one burst=5 nodelay;
            }
        }


    Серверная часть

    Для создания подобных вещей прекрасно подходит событийно-ориентированная архитектура, её и было решено использовать. Исходный код серверной части, хорошо сдобренный комментариями, прилагается. Про event loops и AnyEvent можно почитать в моем предыдущем топике.
    #!/usr/bin/perl

    use strict;
    use warnings;
    use utf8;

    # подключаем все необходимые модули
    use AnyEvent;
    use AnyEvent::FCGI;
    use JSON;
    use Digest::MD5 qw/md5_hex/;
    use URI::Escape;

    # константа для уведомления клиента о выходе из чата - используется довольно часто
    use constant LOGOUT => [{action => 'logout'}], 'Set-Cookie' => 'session=; path=/; expires=Thu, 01-Jan-70 00:00:01 GMT';
    # пустой ответ клиенту
    use constant NOTHING => [];

    # таймаут в секундах, после истечения которого участник считается вышедшим из чата
    use constant TIMEOUT => 100;
    # количество последних сообщений, сохраняемых для отправки вновь пришедшему участнику
    use constant MAX_MESSAGES_COUNT => 20;

    # хэш для хранения данных участников, находящихся в чате
    my %users;
    # последние сообщения
    my @messages;

    # здесь находятся функции-обработчики для всех возможных действий, которые может запросить выполнить клиент
    my %actions = (
        requestLogin => sub {
            # запрос на вход в чат
            my $params = shift;
            if (($params->{nick} && $params->{session} && $users{$params->{nick}} && $users{$params->{nick}}->{session} eq $params->{session})) {
                # обработка повторного запроса на вход в чат
                # в случае ошибки входа ответ происходит на этот же запрос, так как long-polling запроса ещё нет
                return [{action => 'loginError', message => 'Вы уже зашли в чат под другим ником'}];
            } elsif (!defined $params->{nickname} || !length $params->{nickname}) {
                # валидация ника
                return [{action => 'loginError', message => 'Ник не может быть пустым'}];
            } elsif (exists $users{$params->{nickname}}) {
                return [{action => 'loginError', message => 'Пользователь с таким ником уже находится в чате'}];
            } elsif (length $params->{nickname} < 2 || length $params->{nickname} > 20) {
                return [{action => 'loginError', message => 'Длина ника должна составлять от 2 до 20 символов'}];
            } elsif ($params->{nickname} !~ /^[\w\d\-]+$/) {
                return [{action => 'loginError', message => 'Ник содержит недопустимые символы'}];
            } else {
                # если все хорошо, вычисляем случайный идентификатор сессии - по нему мы будем идентифицировать пользователя
                my $session = md5_hex($params->{request}->param('REMOTE_ADDR') . time . rand);
                
                foreach my $nick (keys %users) {
                    # рассылка всем участникам чата уведомления...
                    push_actions(
                        $nick,
                        # ... о приходе нового участника (добавляет уведомление в список сообщений) ...
                        {action => 'join', nick => $params->{nickname}},
                        # ... и установлении нового списка пользователей в правой колонке
                        {action => 'setUserList', users => [sort {$a cmp $b} ($params->{nickname}, keys %users)]},
                    );
                }
                
                # добавляем нового участника в %users
                $users{$params->{nickname}} = {
                    session => $session,
                    # тут будем хранить объект long-polling запроса
                    polling_request => undef,
                    # а тут - очередь сообщений для отправки, если long-polling запроса от клиента нет в момент наступления события
                    queue => [],
                };
                
                # устанавливаем таймаут
                update_timeout($params->{nickname});
                
                # и отвечаем клиенту на этот же запрос, так как long-polling запроса ещё нет
                return (
                    [
                        # уведомление об успешном входе в чат
                        {action => 'loginOk'},
                        # список участников чата
                        {action => 'setUserList', users => [sort {$a cmp $b} keys %users]},
                        # последние MAX_MESSAGES_COUNT сообщений в чате
                        {action => 'setMessageList', messages => [@messages]},
                        # команда на подачу long-polling запроса, теперь только по нему будут уходить данные клиенту
                        {action => 'startPolling'},
                    ],
                    # установка кук с ником и идентификатором сессии
                    'Set-Cookie' => 'nick=' . uri_escape_utf8($params->{nickname}) . '; path=/',
                    'Set-Cookie' => 'session=' . $session . '; path=/',
                );
            }
        },
        restoreSession => sub {
            # эта команда приходит от клиента, если он обнаружил в куках идентификатор сессии
            # производится попытка восстановить сессию (может понадобиться при обновлении страницы с чатом)
            my $params = shift;
            # убиваем куку с идентификатором сессии еслии клиент прислал неверные данные
            return LOGOUT unless ($params->{nick} && $params->{session} && $users{$params->{nick}} && $users{$params->{nick}}->{session} eq $params->{session});
            
            # далее аналогично функции входа в чат
            update_timeout($params->{nick});
            
            return [
                {action => 'setUserList', users => [sort {$a cmp $b} keys %users]},
                {action => 'setMessageList', messages => [@messages]},
                {action => 'startPolling'},
            ];
        },
        sendMessage => sub {
            # запрос на отправку сообщения
            my $params = shift;
            return LOGOUT unless ($params->{nick} && $params->{session} && $users{$params->{nick}} && $users{$params->{nick}}->{session} eq $params->{session});
            
            # проверяем длину сообщения
            if (defined $params->{text} && length $params->{text} > 0 && length $params->{text} <= 300) {
                if ($params->{text} =~ /^\/quit\s*$/) {
                    # если пользователь ввел команду /quit
                    
                    # если для пользователя сохранен объект long-polling запроса
                    if ($users{$params->{nick}}->{polling_request} && $users{$params->{nick}}->{polling_request}->is_active) {
                        # ответить на запрос о выходе из чата
                        respond($users{$params->{nick}}->{polling_request}, LOGOUT);
                    }
                    
                    # удаление участника из списка
                    delete $users{$params->{nick}};
                    
                    # и рассылка всем оставшимся уведомления о выходе
                    foreach my $nick (keys %users) {
                        push_actions(
                            $nick,
                            {action => 'leave', nick => $params->{nick}},
                            {action => 'setUserList', users => [sort {$a cmp $b} keys %users]},
                        );
                    }
                    
                    return LOGOUT;
                } elsif ($params->{text} =~ /^\/me\s+(.+)$/) {
                    # обработка команды вида /me действие
                    my $action = {
                        action => 'me',
                        nick => $params->{nick},
                        text => $1,
                    };
                    # сохранение сообщения для отображения вновь пришедшим участникам
                    store_message($action);
                    
                    # и рассылка всем участниками чата
                    foreach my $nick (keys %users) {
                        push_actions($nick, $action);
                    }
                } else {
                    # обычное сообщение, аналогично команде /me
                    my $action = {
                        action => 'message',
                        nick => $params->{nick},
                        text => $params->{text},
                    };
                    store_message($action);
                    
                    foreach my $nick (keys %users) {
                        push_actions($nick, $action);
                    }
                }
            }
            
            # на соединение с запросом на отпарвку сообщения отвечаем пустым списком команд
            return NOTHING;
        },
        poll => sub {
            # обработка long-polling запроса
            my $params = shift;
            return LOGOUT unless ($params->{nick} && $params->{session} && $users{$params->{nick}} && $users{$params->{nick}}->{session} eq $params->{session});
            
            # если для участника уже сохранен активный long-polling запрос...
            if ($users{$params->{nick}}->{polling_request} && $users{$params->{nick}}->{polling_request}->is_active) {
                # ...ответить по нему о прекращении старой сессии, ибо такое может произойти только в случае входа вторым окном браузера
                respond($users{$params->{nick}}->{polling_request}, [
                    {action => 'logout'},
                    {action => 'loginError', message => 'Вы зашли в чат из другого окна браузера'},
                ]);
            }
            
            # сохраняем объект запроса
            $users{$params->{nick}}->{polling_request} = $params->{request};
            # отправляем клиенту все накопившиеся действия за время отсутствия соединения
            push_actions($params->{nick}) if scalar @{$users{$params->{nick}}->{queue}};
            # и обновляем таймаут
            update_timeout($params->{nick});
            
            # не отвечаем на запрос!
            return undef;
        },
        # обработчик неизвестного действия
        default => sub {return LOGOUT}
    );

    sub process_request {
        # вызывается при получении запроса от http-сервера
        my ($request) = @_;
        
        # разбор параметров запроса и извлечение значений кук - для этих простых операции подключать CGI.pm не стоит
        my %params;
        foreach (
            split(/;\s*/, $request->param('HTTP_COOKIE') || ''),
            split('&', $request->param('QUERY_STRING') || ''),
        ) {
            next unless $_;
            my ($key, $value) = split '=';
            if (defined $key && defined $value) {
                $value = uri_unescape($value);
                $value =~ tr/+/ /;
                utf8::decode($value) unless utf8::is_utf8($value);
                $params{$key} = $value;
            }
        }
        $params{request} = $request;
        
        # вызываем запрошенное действие, или default, если такого действия мы не знаем
        my ($response, @headers) = $actions{$params{action} && $actions{$params{action}} ? $params{action} : 'default'}->(\%params);
        # отвечаем клиенту, если нужно
        respond($request, $response, @headers) if $response;
    }

    sub respond {
        # функция ответа на запрос, преобразует входные данные в JSON и посылает клиенту
        my ($request, $response, @headers) = @_;
        
        my $output = "Content-Type: text/plain; charset=utf-8\n";
        while (scalar @headers) {
            $output .= shift(@headers) . ': ' . shift(@headers) . "\n";
        }
        $output .= "\n" . to_json($response);
        
        utf8::encode($output) if utf8::is_utf8($output);
        
        $request->print_stdout($output);
        $request->finish;
    }

    sub push_actions {
        # функция добавления действий в очередь на отправку
        # если определено активное long-polling соединение, отправить действия
        my ($nick, @actions) = @_;
        
        push @{$users{$nick}->{queue}}, @actions;
        
        if ($users{$nick}->{polling_request} && $users{$nick}->{polling_request}->is_active) {
            respond($users{$nick}->{polling_request}, $users{$nick}->{queue});
            
            $users{$nick}->{queue} = [];
        }
    }

    sub store_message {
        # сохранение сообщения для отображения вновь пришедшим участникам
        my ($action) = @_;
        
        push @messages, $action;
        shift @messages if scalar @messages > MAX_MESSAGES_COUNT;
    }

    sub update_timeout {
        my ($nick) = @_;
        
        # установка таймаута. если функцию не вызывать TIMEOUT секунд для пользователя...
        $users{$nick}->{timeout} = AnyEvent->timer(
            after => TIMEOUT,
            interval => 0,
            cb => sub {
                # ...то он будет считаться покинувшим чат
                delete $users{$nick};
                
                foreach my $user (keys %users) {
                    push_actions(
                        $user,
                        {action => 'leave', nick => $nick},
                        {action => 'setUserList', users => [sort {$a cmp $b} keys %users]},
                    );
                }
            },
        );
    }

    # основная программа - создание FastCGI-сервера
    umask(0);
    my $fcgi = new AnyEvent::FCGI(on_request => \&process_request, unix => '/home/vk/chat.socket');
    AnyEvent->loop;

    Извиняюсь за отсутствие подсветки кода — хабр никак не хочет добавлять в пост кучу тегов <font>, подсвечены только комментарии.

    Клиентская часть

    Клиентская часть довольно сильно похожа на серверную — существует такой же набор обработчиков действий, запросы на который приходят от сервера (добавить сообщение, установить список участников). Все запросы на сервер отправляются с помощью функции jQuery $.ajax. Выкладывать весь код в статье не буду, посмотреть его можно здесь.

    Что получилось


    Получился простой, но вполне юзабельный чат. В нем вижу только 2 недостатка:
    • Отсутствие уведомлений о доставке сообщений клиенту — из-за 40-секундного таймаута и разрыва соединения сервером шанс потери сообщения очень маленький, но он есть.
    • Отсутствие синхронизации исходящих запросов. Вполне может так получиться, что второе сообщение придет на сервер раньше первого.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 81

      +2
      интересно. сам сделал нечто подобное, но на другой технологической основе, хотя сервером стоит также nginx. Кстати, посмотрите в сторону специализированного сервера для такого рода проектов — APE Push Engine

      Первую проблему решить я пока не придумал как — если вызвать на клиенте в ответ на каждое сообщение какой-то каллбек, который берет идентификатор сообщения и говорит серверу, что все ОК, сообщение получил, то мы получим те же недостатки поллинга — множество запросов к серверу. Хотя можно просто накапливать в буфер это и раз в Х времени посылать массив, что все ОК или если есть недоставка тогда принудительно сбрасывать.

      Также у меня принудительно каждые 3 секунды шлется специальный пинг пакет, сообщающий, что сервер работает и клиент может ставить таймер — если в течении N*3 (для верности) не было ни сообщений ни пинга, принудительно перезапустить соединение.
        0
        вторая проблема решается на стороне сервера очередью сообщений и упорядочниванием их перед отсылкой.

        в IE не работает по причине, что используется дефолтный транспорт XMLHTTPRequest. На javascript.ru было несколько статей про различные методы стрима и кроссбраузерность. также можно посмотреть на исходники клиентской части того же APE
          0
          Если вы про второй недостаток (Отсутствие синхронизации исходящих запросов), то я про отсутствие синхронизации исходящих запросов на стороне клиента, проблема решается просто — небольшим воркэраундом над функцией посылки запроса.

          Думаю, в jquery функция $.ajax учитывает особенности IE. Причем из IE все сообщения на сервер уходят, только ответа не видно. Такое ощущение, что просто не срабатывает колбэк success
            0
            А можно ли добиться гарантированной доставки и без перемешивания? Чтобы сообщения приходили строго в том порядке как они отправляются?
              0
              Я бы решил вторую проблему простой нумерацией исходящих сообшений с ID клиента и сесии. Этот же способ поможет решить первую проблему. Для економии траффика цифры можно сжимать.
                0
                Гарантированная доставка возможно в случае отправки клиентом подтверждения о доставке нумеровннаых сообщений.

                Race condition может быть только в исходящих от клиента запросов. Сообщения с сервера забираются одним запросом, и следующий запрос не будет создан пока не будет получен ответ на предыдущий.
                  0
                  Как вариант лочить отправку новых сообщений, до получения инфы о получении предыдущего?

                  Не очень понял насчет забора сообщений с сервера. По идее comet рассылает сразу, все, что ему пришло?
                    0
                    1) да, складывать их в очередь ровно также как на сервере
                    2) да, сразу. после этого сразу посылается следующий запрос, только после окончания предыдущего. то есть в любой момент времени может быть не больше одного читающего с сервера данные запроса. А со стороны клиента в текущей реализации два подряд быстро отправленных сообщения уйдут на сервер в параллельных запросах, и не факт что первое придет раньше :)
                0
                Если я правильно понял проблему, то с подобным сталкивался. Если дело не в этом, заранее извиняюсь за коммент не в тему.

                Нужно сделать вот так:

                jQuery.ajax({
                // тут параметры
                success: function(rawxml)
                {
                var xml;
                if(jQuery.browser.msie)
                {
                xml = new ActiveXObject(«Microsoft.XMLDOM»);
                xml.async = false;
                xml.loadXML(rawxml);
                }
                else xml = rawxml;
                // дальше работаем с XML
                  0
                  Хм, сорри, форматирование кода поплыло :(
            +2
            Вы бы видели что там творилось)) Опыт показал — Хабру немодерируемый или без кармы чат вреден!
              –5
              伟大的中华民族已经走过了5000年的文明历程。在古老的中华大地上,勤劳、勇敢、智慧的各族人民共同开拓了幅员辽阔的国土,共同缔造了统一的多民族国家,共同发展了悠久灿烂的中华文化。一部厚重的中国史,就是一部中国各民族诞生、发展、交融并共同缔造统一国家的历史。 从很早的古代起,我国各民族的祖先,就劳动、生息、繁衍在中华大地之上。我们统一的多民族国家,是5000年来特别是秦汉以来的 2000多年里,在各民族长期交往融合中逐步形成和发展的。秦汉时期,不仅基本奠定了以汉族为中心的中原王朝的疆域规模,而且开创了将中华大地上渔猎文明区、游牧文明区和农耕文明区“混而为一”的大一统先河。秦汉陆续在今广西、云南、贵州
                0
                по русски можно?
                  +2
                  Великая китайская нация прошла через 5000 лет цивилизации, конечно. В древней земле Китая, трудолюбивый, мужественный и мудрый народ все сотрудничестве национальности развитие огромных просторах земли и построить единую многоэтнической страны, и общее развитие великолепной китайской культуры. Тяжелой китайской истории, то есть рождение китайских этнических групп, развития, смешения и соучредителя единую нацию. С самого раннего древности, наших предков из различных этнических групп на труд, проценты, размножаются на земле Китая. Мы являемся единой многоэтнической страны, составляет 5000 лет, особенно после Цинь и Хань более чем 2000 лет, интеграции долгосрочный обмен термина в различных этнических группах постепенно формируется и развивается. Цинь и Хань, не только заложила основные центром в Центральной равнины в Хан династии, размеры территории, но и открыл для китайской цивилизации, земля рыболовства и охотничьих угодий, кочевой цивилизации и цивилизованных районов и сельскохозяйственных районов «путать» большой прецедент объединения. Цинь и Хань подряд в этом Гуанси, Юньнань, Гуйчжоу
                    +1
                    По-русски «по русски» будет «по-русски».
                    0
                    Так вот кто это был!
                      0
                      Кто?
                        0
                        Китаец.
                          –1
                          Откуда вы знаете, что именно китаец?
                            0
                            А что, не китаец?
                              –1
                              А вдруг не китаец?
                    0
                    Увидели светлые мысли хабражителей :)
                    0
                    сделайте прокрутку в окне пользователей, а то сейчас оно разрослось на два экрана и потянуло за собой строку ввода сообщения
                      0
                      поправил, плюс ещё сделал невозможность разуплотнять окно сообщений :)
                        0
                        в опере всё равно верстка едет (opera 10.10)
                      0
                      Так вот они какие эти хабралюди…
                        0
                        В чате какой то срач, даже не обсудить спокойно… Вроде должны бы адекватные люди прийти.
                          +3
                          это ведь не часть хабра, это тест приложения, созданного автором.

                          и да, если ограничивать людей кармой, они будут искать любой способ выплеснуть всё это так чтобы не пострадать)
                          0
                          сколько людей в онлайне? смотрю очень неплохо держит нагрузку.
                            +1
                            Сложилось несколько негативное впечатление после посещения данного чата… Тролли флудят длинными сообщениями, из-за которых плывет верстка; гости выпрашивают инвайты…

                            Интересно, на скольки пользователях в чате он загнется? =)
                              0
                              Думаю загнется он только тогда когда загнется канал… Будет тормозить — можно подрубить EV и установить бэкендом обработки событий kqueue
                                0
                                Чего ему загибаться-то? Нагрузка-то по сути никакая.

                                Было дело, писал аналогичный функционал на Java.
                                1000 коннектов держало спокойно, причем это даже в режиме «one thread per client».
                                  0
                                  Ну да… Тут я согласен с vkramskikh. Хотя канал загнуть — задача не очень выполнимая даже для 1000 флудящих юзеров, а сейчас там и 10 флудеров не наберется…
                                0
                                Зашел и испугался движущейся стены текста.
                                  +1
                                  Хехе, а хабраэфект то держит =)
                                    0
                                    А строчка # защита от флуда, ограничение на 1 запрос в секунду
                                    limit_req zone=one burst=5 nodelay;
                                    у вас не работает :P
                                      0
                                      Она работала, но пришлось повысить лимит до 4 запросов в секунду, ибо при плотном потоке сообщений даже long-polling запросы посылаются чаще — этого не предусмотрел… Защиту от флуда надо делать внутри серверной части, nginx тут не спасет.
                                        0
                                        убрал nodelay и вернул ограничение на 1 запрос в секунду — полегче стало, только сообщения теперь приходят пачками с заметной задержкой. Но это лучше чем стена флуда :)
                                          0
                                          Эх! А мы думали, что он загибаться начал! :)
                                          0
                                          нашел более менее сносное решение проблемы флуда, напишу после того как все флудеры уйдут, ибо при знании конфига nginx оно легко обходится :) сообщения теперь можно слать не чаще 1 раза в 6 секунд, а на long-polling соединения это не влияет
                                            0
                                            Да, стало намного лучше! Только все равно флудить получается. Можно еще сделать блокирование повторных сообщений (достаточно длинных, чтобы «да/нет/угу» не блокировать) :)
                                          +4
                                          Зашел, понаблюдал и понял: ваш сервис — именно то, что было нужно хабру (:
                                            +1
                                            ужас что творится там. я думал хабра это более менее вменяемые люди.
                                              0
                                              добавьте фильтр флуда. делается всего-то парой условий.
                                                0
                                                Добавил, чат перезапустил.
                                              0
                                              было бы здорово поставить autocomplete=«off»
                                                0
                                                Поставил.
                                                  0
                                                  спасибо
                                                0
                                                лагает чат при большом кол-ве сообщений
                                                  0
                                                  А я вот хочу прочитать, что же там экраном выше написано, но после каждого нового сообщения в чате — кидает обратно вниз. В данном конкретном чате — не критично, но если пользовать по серьёзному — будет мешать.
                                                    0
                                                    А еще заметил, что чат проц грузит довольно сильно: при неактивной вкладке с чатом — 10-30%, при активной — 50-90%…

                                                    PS: Celeron 2.66 ггц, 1 гб озу…
                                                      0
                                                      Думаю это из-за того, что для добавления сообщений в чат используется сразу несколько обращений к DOM-дереву. В реальном чате так, конечно, делать не надо, здесь это сделано для того чтобы не преобразовывать спецсимволы на серверной стороне
                                                        0
                                                        image

                                                        Проблем не возникает вообще. Как будто и нет чата.
                                                          0
                                                          Всего 300 лишних мегагерц — а какая разница! -))
                                                        +2
                                                        Дикари!
                                                          0
                                                          Надо добавить вывод ip отправителя сообщения.
                                                            –6
                                                            всем чмоки-чмоки! ;)
                                                              0
                                                              кто ж такое на перле пишет… erlang батенька надо…
                                                                0
                                                                Можно, в принципе, на чем угодно. Это вообще как бы пруф-оф-концепт и на полноценный чат не претендует :) Профит использования эрланга по сравнению с перлом вижу разве что в возможности использования многоядерных процессоров (так как текущая реализация на перле выполняется в один поток)
                                                                  0
                                                                  Можно :) Просто что вы будете делать когда у вас будут тысячи подключений? Профит не только в использовании SMP. Профит в скорости обработки, количестве одновременных подключений и в возможности подключать дополнительные ноды без каких либо проблем
                                                                    0
                                                                    Что буду делать когда будут тысячи подключений? Включу kqueue бэкендом (сейчас там простой select). Серверная часть является FSM, как nginx, и может спокойно выдерживать тысячи подключений. Возможность масштабирования из коробки, вещь, конечно хорошая, но тут особо не нужная. Этот чат висит на дохлой вдске с 256 метрами памяти и 480 мгц проца, плюс на той же вдске хостится сайт с80к хитов в сутки — и ничего, успешно работает :)
                                                                      0
                                                                      я не спорю что nginx примет тысячи подключений. Проблема в другом, сможет ли ваш перловый сервер быстро обработать эти все подключения? или вы столкнетесь с очередью, которая будет отдаваться nginx постепенно. Если так будет, то у вас будут большие таймоуты.
                                                                        0
                                                                        Вы, видимо, меня не поняли :) Мой перловый сервер имеет ровно ту же архитектуру, что и nginx — конечный автомат на асинхронных сокетах, и так же как и nginx, может обрабатывать тысячи подключений (от nginx'а). Причины, по которым я вставил посередине nginx, я описал в статье. В принципе, можно его легко убрать и просто заменить AnyEvent::FCGI на AnyEvent::HTTPD и работать напрямую с клиентом — ничего не изменится. Для вас, наверное, впечатление о перле сложилось в те времена, когда на нем ничего кроме CGI-приложений под веб не делали. Рекомендую почитать мою предыдущую статью, ссылка в этом посте :)
                                                                          0
                                                                          Я вообще стараюсь Perl под веб не юзать :) Скрипты, сбор статистики, генерация репортов — то чем перл выигрывает у всех — а именно работа с текстом.
                                                                          Просто считаю что для каждой задачи есть свой язык :)
                                                                  0
                                                                  Erlang — это хорошо, но реализация comet есть и на других языках.
                                                                  Вы пробовали сравнивать разные варианты, например, тот же Orbited на Python?
                                                                    0
                                                                    мне после работы с Zope питон вообще видеть не хочется :)
                                                                      0
                                                                      Оооо… Зопа это феерическая вещь, оправдывающая свое название на 300%!
                                                                      Я тоже с ней работал, это незабываемо. После этого появляются афоризмы навроде: «Геморрой — это боль в зопе».
                                                                      Я ее с большой радостью забросил, а питон оставил. Как язык он шикарный, это, можно сказать, «лучик света в большой темной зопе».

                                                                      На питоне писать очень легко и приятно, он, честно, не виноват, что его использовали для написания такого монстра :)
                                                                      +1
                                                                      Нет, не пробовал, ибо:
                                                                      1) питон я не знаю
                                                                      2) мне хотелось написать велосипедсвою реализацию, чтобы понять технологию и получить новый опыт

                                                                      Из готовых решений на перле можно посмотреть Stardust — «the simplest COMET server»
                                                                        0
                                                                        хотел написать про велосипед то что вы хотели написать свою реализацию :)
                                                                        Хотя это полезно, после этого лучше понимаешь все все это устроено и работает
                                                                    0
                                                                    в чате жесть.

                                                                    а приватные сообщения там есть?
                                                                    я осматривал данные технологии, но сходу не придумал как раздавать клиентам идентификаторы или как пихать с сервера сообщения в пул, чтобы некоторые данные слалисль всем юзерам, а некототрые данные слались только определнным юзерам (т.е. весь список мессаг всем, а приватные тоьлко нужным) причем нельзя посылать приватные в общем стеке а потом формировать приватные, иначе умники просто прочитают джсон
                                                                      0
                                                                      В этой реализации нету, но можно их приделать очень просто. Рассылка сообщений всем делается вот так:
                                                                        0
                                                                        Случайно запостил, так вот:
                                                                        foreach my $user (keys %users) {
                                                                        push_actions(
                                                                        $user,
                                                                        # действия для рассылки
                                                                        );
                                                                        }

                                                                        Для приватного сообщения можно вызвать push_actions не для всех, а только для того, для кого надо :)
                                                                          0
                                                                          а ну да =)

                                                                          просто у меня задача намного сложнее была… и тогда не придумал как сделать. думал про несколько идентификаторов на одном пользователе
                                                                          но не суть, вроде пришла в голову светлая мысль =)
                                                                      +2
                                                                      не смотрели в сторону pushmodule.slact.net/?
                                                                        0
                                                                        Видел, можно будет попробовать воспользоваться
                                                                        0
                                                                        Там в чате такой бодряк теперь :)))
                                                                          +1
                                                                          На заметку: причина, по которой ваш чат не работает в IE — неправильная (по мнению IE) кодировка, указанная в заголовке. У вас сейчас возвращается «utf8», а IE поймёт только «utf-8». Немного подробностей: Системная ошибка: -1072896658.
                                                                            0
                                                                            Спасибо! Теперь и в IE работает :)
                                                                              0
                                                                              Тогда сто́ит подправить исходники в посте и замечание о работе в IE.
                                                                            0
                                                                            У нас с друзьями есть реализация комет-чата на Silverlight+WCF.
                                                                            Если интересно, можем поделится, я думаю.
                                                                              0
                                                                              А вот тут описано как реализовать то же только на ASP.NET
                                                                              По-моему это стоит инвайта, жаль что я не могу дать.

                                                                              Only users with full accounts can post comments. Log in, please.