Технология tcp/ip сервера

Технология tcp/ip сервера

08.05.2014 12:19:12 Просмотров 22 Источник

У меня есть сервер написанный на C++ для *UNX, основанный на "неблокирующих сокетах с использованием select" - Пример данный реализации есть в интернете, я использовал эту конструкцию для своего сервера и хочу удостоверится оптимальная ли логика работы сервера для моей задачи.

Задача: Что должен делать мой сервер:

1) Принимать клиентов СТРОГО по авторизации и держать с ними соединение.

2) Выполнять действия по команде клиента (Запрос в бд, чтение запись в файл, и т.д)

3) Передавать файлы клиенту по его запросу.

Число клиентов от 15 000 тысяч.

Пример конструкции на которой основан моей сервер.

int main()
{
    int listener;
    struct sockaddr_in addr;
    char buf[1024];
    int bytes_read;

    listener = socket(AF_INET, SOCK_STREAM, 0);
    if(listener < 0)
    {
        perror("socket");
        exit(1);
    }

    fcntl(listener, F_SETFL, O_NONBLOCK);

    addr.sin_family = AF_INET;
    addr.sin_port = htons(3425);
    addr.sin_addr.s_addr = INADDR_ANY;
    if(bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind");
        exit(2);
    }

    listen(listener, 2);

    set<int> clients;
    clients.clear();

    while(1)
    {
        // Заполняем множество сокетов
        fd_set readset;
        FD_ZERO(&readset);
        FD_SET(listener, &readset);

        for(set<int>::iterator it = clients.begin(); it != clients.end(); it++)
            FD_SET(*it, &readset);

        // Задаём таймаут
        timeval timeout;
        timeout.tv_sec = 15;
        timeout.tv_usec = 0;

        // Ждём события в одном из сокетов
        int mx = max(listener, *max_element(clients.begin(), clients.end()));
        if(select(mx+1, &readset, NULL, NULL, &timeout) <= 0)
        {
            perror("select");
            exit(3);
        }

        // Определяем тип события и выполняем соответствующие действия
        if(FD_ISSET(listener, &readset))
        {
            // Поступил новый запрос на соединение, используем accept
            int sock = accept(listener, NULL, NULL);
            if(sock < 0)
            {
                perror("accept");
                exit(3);
            }

            fcntl(sock, F_SETFL, O_NONBLOCK);

            clients.insert(sock);
        }

        for(set<int>::iterator it = clients.begin(); it != clients.end(); it++)
        {
            if(FD_ISSET(*it, &readset))
            {
                // Поступили данные от клиента, читаем их
                bytes_read = recv(*it, buf, 1024, 0);

                if(bytes_read <= 0)
                {
                    // Соединение разорвано, удаляем сокет из множества
                    close(*it);
                    clients.erase(*it);
                    continue;
                }

                // Отправляем данные обратно клиенту
                send(*it, buf, bytes_read, 0);
            }
        }
    }

    return 0;
}

Авторизация и приём команд происходит в блоке

for(set<int>::iterator it = clients.begin(); it != clients.end(); it++)
    {
        if(FD_ISSET(*it, &readset))
        {
            // Поступили данные от клиента, читаем их
            bytes_read = recv(*it, buf, 1024, 0);

            if(bytes_read <= 0)
            {
                // Соединение разорвано, удаляем сокет из множества
                close(*it);
                clients.erase(*it);
                continue;
            }
           /* Проверяем что за команда и дальнейшие действия с ней */
        }

Если от клиента пришла команда загрузки файла, то сервер создаёт новый поток для отправки файла. Команды и файлы отправляются через один порт что не очень хорошо.

Вопросы:

1) Подойдёт ли данная конструкция сервера для реализации моей задачи?

2) Какие ещё есть варианты сервера для реализации моей задачи? (Можно и с потоками там форк или аналоги)

Приоритеты: Обычно тут идёт выбор между производительностью и ресурсами, я предпочитаю выбор производительности и надёжности сервера.

Готов к критики новым идеям и технологиям в рамках моей темы, но не личности (Шутка).

У вопроса есть решение - Посмотреть?

Ответы - Технология tcp/ip сервера / Технология tcp/ip сервера

KoVadim

08.05.2014 12:43:35

если Вы планируете работать с 15000 пользователей одновременно, то этот код работать не будет. В целом, написание серверов, которые держут 10к пользователей одновременно - уже не такая тривиальная задача.

Но Ваш код будет держать одновременно не более 1024 пользователей (на самом деле где то 900-950, нужно глубже код копать). Вся суть в select. Он просто не умеет больше (там просто массив на 1024 элемента). Но главное, что нужно понимать, что это значит не то, select умеет управлять 1024 дескрипторами, а дескрипторами, номера которых меньше 1024. Если нужно много клиентов, то первое, что нужно сделать, это заменить select на poll (замена на самом деле не очень сложная, но работать будет лучше, хотя на больших наборах медленнее - poll тратит 8 байт на сокет). Если нужно ещё быстрее - есть epoll и подобные вещи.

Следующая ошибка - Вы читаете с помощью recv, но похоже надеетесь, что все данные придут сразу. Это очень распространенная ошибка. Если с одной стороны отправить send на 100 байт, то с другой стороны может прочитаться как 100 байт одновременно, а может и 20, а вторым пакетом 80. То есть, данные могут разрываться как угодно, гарантируется только порядок байт и то, что байты не будут "утеряны произвольно".

Поэтому, прочитав данные, нужно проанализировать буфер. Если там данных недостаточно, то ждать следующего чтения, которым дописать данные в конец буфера и снова анализировать. Вполне возможно, что после этого в буфере будет вся первая команда и начало второй. Или даже первая, вторая и начало третей.

Точно также с командой send. Она не гарантирует, что все данные будут отправлены. Она возвращает кол-во байт, которые были отправлены. Поэтому нужно организовывать досылку данных.

https://ru.stackoverflow.com/questions/318560/%d0%a2%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d1%8f-tcp-ip-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80%d0%b0/318591#comment318606_318591
KoVadim, Благодарю за ответ. epoll - Понравился он же и будет основным компонентом моего сервера. По поводу разрыва байт, я не уверен в себе, но надеюсь мне хватит тай [литературы][beej.us/guide/bgnet/output/html/singlepage/bgne‌​t.html#sendall] которою мне оставили в предыдущем моём вопросе. Но у меня трудности с пониманием англиканского языка, если же на просторах рунета есть описание этого механизма, я с удовольствием пополню запас своих знаний.
https://ru.stackoverflow.com/questions/318560/%d0%a2%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d1%8f-tcp-ip-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80%d0%b0/318591#comment318608_318591
@updaite, что Вы имеете в виду под "разрывом байт"??? Наверное, что send/recv могут отправить/принять меньше чем запрошено? В принципе тут особых проблем с программированием быть не должно. С каждым сокетом (клиентом) связываете свой блок контекста (кстати, его адрес можно использовать в epoll), который создаете после возврата из accept и действуете в соответствии с ним. Не забудьте о таймаутах (тоже в контекстах), в epoll лучше сделать общий для всех, скажем 1 сек. и маске сигналов (удобней их отлавливать в epoll_pwait).
https://ru.stackoverflow.com/questions/318560/%d0%a2%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d1%8f-tcp-ip-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80%d0%b0/318591#comment318706_318591
Нашёл на просторах рунета скелет с использованием epoll, чуток переделал под себя и прокомментировал логику. Знающие люди не могли бы вы подсказать не будет ли у этого скелета сколиоза от моих требований к нему?
https://ru.stackoverflow.com/questions/318560/%d0%a2%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d1%8f-tcp-ip-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80%d0%b0/318591#comment318711_318591
Если это вся логика, то сколиоз наверняка будет. 1) что Вы будете делать, если в recv пришло меньше данных (скажем, 1 байт)? 2) если пришло больше 256, то из-за EPOLLET этот сокет навсегда уснет внутри epoll_pwait (Вы про Level-Triggered and Edge-Triggered в man epoll внимательно читали?). 3) сокеты неблокируемые, а попыток как-то обработать отсылку не всех len байт в send я не вижу. Я правильно понимаю, что обработка сигналов просто пока не рассматривается? Не очень понятно, как Вы включите сюда обработку длинных запросов. -- Пока вроде все. Ждем еще архитектурных решений.
https://ru.stackoverflow.com/questions/318560/%d0%a2%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d1%8f-tcp-ip-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80%d0%b0/318591#comment318716_318591
Забыл добавить про recv, send - в данном скелете они не в счёт для них будет отдельная реализация, да, вы правильно поняли обработка сигналов тоже не в счёт. Архитектурные решения к сожжению будут только завтра ближе к вечеру... Спасибо, за помощь и критику.
https://ru.stackoverflow.com/questions/318560/%d0%a2%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d1%8f-tcp-ip-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80%d0%b0/318591#comment318717_318591
По поводу обработки сигналов. IMHO в Вашу схему с epoll отлично вписывается синхронная обработка (см. man signalfd) с маскированием всех обрабатываемых сигналов. Просто передаете этот дескриптор в epoll вместе с сокетом. Тогда, вместо epoll_pwait можно вызывать epoll_wait.
https://ru.stackoverflow.com/questions/318560/%d0%a2%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d1%8f-tcp-ip-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80%d0%b0/318591#comment318818_318591
Утро доброе и с праздником, с сигналами poll разобрался, но не совсем понимаю что нужно делать с сигналами man signalfd? Какие именно сигналы отлавливать и какие действия выполнять при определенном сигнале signalfd. Чтобы не загружать очередь обработки сигналов от poll на ум пришла следующая идея: Send и Recv оставить как есть, потому что клиент отправляя команду знает что получит (0 или 1) если не 0 и 1 то повторить команду... Таким образом я думаю можно снизить нагрузку на сервере.Так же подчеркну что я плохо знаю "устройство сигналов на linux" дабы планктон консольных hello wordow для win.
https://ru.stackoverflow.com/questions/318560/%d0%a2%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d1%8f-tcp-ip-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80%d0%b0/318591#comment318857_318591
@updaIte, спасибо, также примите мои поздравления. Если правильно понял, то Вы собрались обслуживать тысячи соединений одновременно (т.е. общее количество пользователей -- сотни тысяч). Неплохо было бы, чтобы такой сервер не молча падал, получив сигнал, а хоть запись в логе оставлял. Также сигнал SIGHUP принято использовать для обновления конфига сервера без его перезапуска. Вообще же, чтобы говорить о серьезных вещах на одном языке, IMHO полезно будет прочесть какую-нибудь серьезную книжку, например, эту.
https://ru.stackoverflow.com/questions/318560/%d0%a2%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d1%8f-tcp-ip-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80%d0%b0/318591#comment1507549_318591
Коментарии - нечто временное, подшейте пожайлуста их в ответ, если не сложно, нет не все, хотя бы раздел - полезные ссылки.
Закрыть X