Служба мгновенных собщений своими руками

    Все мы привыкли пользоваться аськой, многие этот функционал реализуют в своих проектах, кто-то использует БД, или сервер очередей, например memcacheq. Есть готовые решения, типа eJabber.

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

    Суть «Службы коротких сообщений — это в принципе и есть сервер очередей, работающий по протоколу HTTP, который легко должен интегрироваться с любым JS фреймворком. Вса результаты выдаются в JSON. Обмен с сервером осуществляется из браузера посредством AJAX.

    по методу POST записываем данные по ключу, которым является url (или его часть). По методу GET извлекаем из очереди (по ключу, которым является url) то, что было записано. Итак имеем: одну Хештаблицу, ключом в которой являются uri, а данными — очереди. Очередь представляет строку собщения. При желании можно добавить и время. Хотя есть много идей по развитию.

    итак ближе к теме:
    1. #include <sys/types.h>
    2. #include <sys/time.h>
    3. #include <sys/queue.h>
    4. #include <stdlib.h>
    5. #include <err.h>
    6. #include <event.h>
    7. #include <evhttp.h>
    8.  
    9. #include <map>
    10. #include <string>
    11. #include <queue>
    12.  
    13. using namespace std;
    14.  
    15. std::map<string,queue<string> > ht;
    16.  
    17. void generic_handler(struct evhttp_request *req, void *arg)
    18. {
    19.     struct evbuffer *buf;
    20.     buf = evbuffer_new();
    21.     if (buf == NULL)
    22.         err(1"failed to create response buffer");
    23.     string key = evhttp_request_uri(req);
    24.     string out;
    25.  
    26.         if (req->type == EVHTTP_REQ_POST) {
    27.  
    28.             const char * str_len = evhttp_find_header(req->input_headers, "Content-Length");
    29.             int len = atoi(str_len);
    30.             out.assign((const char *)EVBUFFER_DATA(req->input_buffer),len);
    31.             if ( ht.find(key) == ht.end() ) { 
    32.                 queue<string> q;
    33.                 q.push(out);
    34.                 ht.insert( pair<string,queue<string> >(key,q));
    35.             } else 
    36.                 ht[key].push(out);
    37.                 evbuffer_add_printf(buf, "{\"result\"\"Ok\"}\r\n");
    38.             } else {
    39.                 if ( ht.find(key) == ht.end() ) {
    40.                 evbuffer_add_printf(buf, "{\"result\": null}\r\n" );
    41.                  } else {
    42.                     queue<string> q = ht[key];
    43.                     if ( q.size() ) {
    44.                         out = q.front();
    45.                        q.pop();
    46.                        ht[key]=q;
    47.                        evbuffer_add_printf(buf, "{\"result\"\"%s\"}" , out.c_str() );
    48.                 } else {
    49.                 evbuffer_add_printf(buf, "{\"result\": null }" )
    50.                 }
    51.             }
    52.         }
    53.         evhttp_send_reply(req, HTTP_OK, "OK", buf);
    54. }
    55.  
    56. int main(int argc, char **argv)
    57. {
    58.     struct evhttp *httpd;
    59.     event_init();
    60.     httpd = evhttp_start("0.0.0.0"8080);
    61.     evhttp_set_gencb(httpd, generic_handler, NULL);
    62.     event_dispatch();
    63.     evhttp_free(httpd);
    64.     return 0;
    65. }

    немного пояснений по коду:
    строки 58-63 инициализация WEB сервера. За основу взять WEB сервер основанный на libevent. У него отличная производительность. На моем ноуте 2.3ГГц он дает производительность 2к qps. Осуществляется обработка любого урла.
    стр 20-22 — инициализирем буфер
    стр 23 получаем REQUEST_IRI и используем для ключа. Есть много предложений по оптимизации.
    стр 26 проверяем на POST. Несомненно можно еще проверить на HEAD (другие методы evhttp не поддерживает). Пока не будем усложнять жизнь.
    стр 28-30 формируем переменную, в которой хранятся данные. Так как в буфере накапливается мусор, то мы записывает столько байт, сколько указано в заголовке Content-Length
    стр 30-35 Если ключ такой не существует, то заводим новую очередь и вставляем в очередь элемент данных
    стр 36 иначе просто вставляем в очередь элемент данных
    стр 38 — отрабатывает метод GET
    стр 39 проверяет существует ли ключ
    стр 40 — нет — выводим сообщение о пустом результате
    стр 42- получаем данные по ключу
    стр 43 — проверяем пустая ли очередь,
    стр 44-47 — нет, выбираем сообщение из очереди и выводим его, очередь уменьшается на одно сообщение
    Можно немного пофлеймить, что надо сделать эскейпинг. Да, обязательно это добавлю.
    стр 49 да, очередь пуста, сообщаем об этом.
    стр 53 финализируем запрос, отправляем код ответа 200 OK

    Необходимо отметить, что модель однопоточная, по этому ни каких блокировок на запись делать не надо. Хотя этот вопрос еще мной будет прорабатываться.

    Результаты ab
    Concurrency Level: 3
    Time taken for tests: 0.415 seconds
    Complete requests: 1000
    Failed requests: 0
    Write errors: 0
    Total transferred: 83000 bytes
    HTML transferred: 19000 bytes
    Requests per second: 2409.31 [#/sec] (mean)
    Time per request: 1.245 [ms] (mean)
    Time per request: 0.415 [ms] (mean, across all concurrent requests)
    Transfer rate: 195.29 [Kbytes/sec] received

    объем потребляемой память на 10 000 сообщений — чуть более 600К

    Вообще-то планируется поставить за nginx, пусть он отвечает за безопасность (ngx_http_accesskey_module)
    кусок конфига:
    location /test {
    proxy_pass 127.0.0.1:8080;
    ## и еще директивы ngx_http_accesskey_module
    }

    но через nginx производительность доходит до 800 rps

    Есть много идей — как и куда двигаться дальше. Например, отображение статусов активности, антиспам.
    Любые иные идеи приветствуются. Пока сижу без работы, по этому нет проекта, на котором могу это опробовать. По моим расчетам одновременно около 10 тыс клиентов спокойно потянет, если делать запросы с частотой 1 -1.5 мин ( 130-200 rps).

    Средняя зарплата в IT

    113 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 5 444 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 17

      +6
      >wellcom под каст

      куда-куда?
        +10
        > Пока сижу без работы, по этому нет проекта, на котором могу это опробовать.

        >делать запросы с _честотой _

        русский язык можно попробовать осилить, или спеллчекер хотя бы
          –1
          С серверной частью я думаю проблем не возникнет, а вот с клиентской… :-)
            0
            поправлю,
            спасибо
              0
              а вот с русским у меня проблемы еще со школьной скамьи
            +2
            ИМХО делать подобные штуки на основе веба шибко накладно, это как минимум
            1. — HTTP заголовки отправляемые серверу и получаемые от него будут занимать я думаю в среднем 90%
            2. — во время простоя происходит совсем бесполезная возня с проверками
            3. — 1 — 1.5 минут — это уже совсем не «Служба мгновенных сообщений»

            ну и расчетные 10к пользователей — это в идеальном случае, когда все будут обновляться равномерно...10% из них при одновременном подключении вас и положат )
              0
              1 — согласен, но в подобных системах, больше половины трафика — служебные сообщения.
              по моим расчетам не положат
              2. Comet предлагаешь?
              3. вот как раз это то время, когда человек набирает сообщение, можно и 10 сек сделать, тогда больше вероятность, что сервер ляжет. Время выбрано так, что если равномерно 10 к пользователей полезут общаться, то будет 350-400 rps а сервер выдерживает на ноуте около 2000.
              0
              Если уж приспичило на основе HTTP, то чем ваша реализация лучше Comet-архитектуры?
              Статья как пример разработки веб-сервера на сях — очень даже ничего, только вот комментарии лучше в коде имхо писать, а не после, хоть особых проблем и не вызывает.
                0
                это очень спорный вопрос, что лучше. Пока я не готов на него ответить. Комет лучше тем, что сжирает меньше трафика, более оперативный ответ.
                –1
                это на ruby с использованием eventmachine пишется за 10 минут (включая установку нужный гемов).
                  0
                  testchatapp.appspot.com — чат написанный с использованием vaadin за 10 минут и app engine.

                  Если не считать обязательных конструкций то написано было около 50 строчек кода.

                  Когда нет работы — всегда хочется сделать очередной велосипед.
                    +1
                    ну так и тут автор использовал аналог EM. смысл в том, что кол-во конектов они выдержат равное (хотя я уверен, что руби сможет больше чем то, что сделал автор).
                      0
                      это не первый мой велосипед
                        0
                        давай проэксперементируем?
                        руби имеет производительность в 5 раз меньше чем си.

                        можно это запустить на 2 и более потоков (по ядру на поток)
                        тогда производительность повысится раза в полтора.
                        правда кода раза в два увеличится и головная боль появится в в виде блокировок.
                          –1
                          школьник? вся твоя производительность в си упрется в скорость написания кода и хард. В реальных ситуациях си не намного быстрее ruby. (и не надо опять делать топик где будет считать факториалы на разных языках)

                          >правда кода раза в два увеличится и головная боль появится в в виде блокировок.

                          создали вэб сервера thin с вами не согласны :D вы наверное не знаете, что в руби потоки не настоящие?
                        +2
                        каждый сделанный тобой новый велосипед — это твои новые знания,
                        твои ошибки и твой бесценный опыт.
                    • НЛО прилетело и опубликовало эту надпись здесь

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое