Простой сервер на Qt/C++

    В последнее время очень часто приходится слушать определенный порт, получать данные от клиента и отправлять соответствующий ответ. Решил поделиться с новичками, как же создать такой сервер и решить некоторые поставленные вопросы.
    В этой статье мы рассмотрим:
    — Создание tcp сервера.
    — Подключение нескольких клиентов к серверу параллельно.
    — Отключение клиентов (отключение сокетов).
    — Получение и отправку данных.

    Исходники: https://github.com/valualit/QTcpServer01

    image


    QTcpServer или слушаем нужный порт

    В переменной server_status — храню статус QTcpServer, чтоб не происходило эксцессов при работе сервера (если 0 — то сервер не слушает порт, 1 — слушает).
    Сигналы в данном случае решают лишний раз проблему с прослушиванием порта, т.е. слот newuser() в данный момент вызывается только тогда, когда появляется новое подключение к серверу.
        tcpServer = new QTcpServer(this);
        connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newuser()));
        if (!tcpServer->listen(QHostAddress::Any, 33333) && server_status==0) {
            qDebug() <<  QObject::tr("Unable to start the server: %1.").arg(tcpServer->errorString());
        } else {
            server_status=1;
            qDebug() << QString::fromUtf8("Сервер запущен!");
        }
    


    Подключение нескольких клиентов к серверу параллельно

    Код ниже демонстрирует, как создать новый сокет и заставить его слушать сигналы. Так же в нем получаем дескриптор сокета, который используем в качестве ключа для хранения объекта сокета, который пригодится нам при дальнейшей работе.
        if(server_status==1){
            qDebug() << QString::fromUtf8("У нас новое соединение!");
            QTcpSocket* clientSocket=tcpServer->nextPendingConnection();
            int idusersocs=clientSocket->socketDescriptor();
            SClients[idusersocs]=clientSocket;
            connect(SClients[idusersocs],SIGNAL(readyRead()),this, SLOT(slotReadClient()));
        }
    


    QMap<int,QTcpSocket *> SClients; Данная карта хранит объекты созданных сокетов. Ее использую например если принудительно останавливаю сервер и мне необходимо закрыть открытые сокеты. Если их не закрыть, то клиент будет еще долго ждать ответ от нашего сервера и не закрывать соединение. Ниже выложен вариант принудительного закрытия всех сокетов.
        if(server_status==1){
            foreach(int i,SClients.keys()){
                QTextStream os(SClients[i]);
                os.setAutoDetectUnicode(true);
                os << QDateTime::currentDateTime().toString() << "\n";
                SClients[i]->close();
                SClients.remove(i);
            }
            tcpServer->close();
            qDebug() << QString::fromUtf8("Сервер остановлен!");
            server_status=0;
        }
    


    При создании нового сокета Вы уже наверно заметили сигнал readyRead(), он выполняется когда клиент передает какие-то данные на наш сервер, в этот момент мы и будем давать ответ нашему клиенту, предварительно получив данные.

        // Получаем объект сокета, который вызвал данный слот
        QTcpSocket* clientSocket = (QTcpSocket*)sender();
        // Получаем дескриптор, для того, чтоб в случае закрытия сокета удалить его из карты
        int idusersocs=clientSocket->socketDescriptor();
        // Пример отправки ответа клиенту
        QTextStream os(clientSocket);
        os.setAutoDetectUnicode(true);
        os << "HTTP/1.0 200 Ok\r\n"
              "Content-Type: text/html; charset=\"utf-8\"\r\n"
              "\r\n"
              "<h1>Nothing to see here</h1>\n"
              << QDateTime::currentDateTime().toString() << "\n";
        // Полученные данные от клиента выведем в qDebug, 
        // можно разобрать данные например от GET запроса и по условию выдавать необходимый ответ. 
        qDebug() << clientSocket->readAll()+"\n\r");
        // Если нужно закрыть сокет
        clientSocket->close();
        // Удалим объект сокета из карты
        SClients.remove(idusersocs);
    


    Таким образом мы получаем сервер (например HTTP), который слушает порт 33333, сможет обрабатывать сразу несколько запросов одновременно и отдавать нужный результат.

    image

    P.S. В будущем напишу о передачи большого объема данных с помощью сокетов.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

      +10
      У нас новое соединение!
      Вспомнилось, извините, вспомнилось:
      Когда писал свой первый сайт, сделал себе уведомлялку по СМС (post запрос на сайт оператора), когда на сайт приходил новый пользователь и радовался как ребенок каждому новому посетителю. Потом их становилось больше, пришлось отключить )
        –8
        Наверное сути не поняли!?
        Данная статья для того, чтоб помочь новичкам разобраться как написать свой слушающий сервер и т.д. В будущем эти знания можно наложить на любые задачи от написания простых ответов для http, для чатов, для других программ которые будут автономно работать и слушать порт.
          +5
          Да понял я суть, за статью спасибо, как раз потихоньку интересуюсь Qt.
          Вспомнилось просто, на правах оффтопа.
          • НЛО прилетело и опубликовало эту надпись здесь
              +1
              А если нужен сервер кроссплатформенного чата. Я не против nginx — просто всему свое использование. Статья написана в образовательных целях.
              P.S. Я допустил ряд ошибок в статье, в ближайшее время исправлю их.
              • НЛО прилетело и опубликовало эту надпись здесь
                0
                для продакшена не лучше…
                лучше писать scgi сервер, так как сможешь уронить nginx
                модули пишутся только в случае очень простой логики и очень быстрой отдачи ответа.
            0
            А стоит ли забивать память мапом SClients? (ну или не map, в общем переменной :) )
            Ведь нужно получить запрос, распарсить и отправить ответ. В случае с web-сервером. Можно просто создать новый поток и ждать клиентов дальше. Имхо — лишние затраты.
              +1
              Задача стояла не на web сервер, для чего я делал так, я пояснил — для того, чтоб сбрасывать потоки, которые нам не нужны или при приостановке сервера, чтоб сокеты не висли.
                0
                Но это же не веб-сервер у Вас, просто заглушка на http-запрос.
                Никакой даже минимальной функциональности не поддерживается.
                Как правильно пишут здесь же в комментариях — это просто переложение стандартного примера из множества средств разработки. И почти во всех, есть специальных компонент (или виджет) который может сделать то же, что и у Вас.

                Добавьте хотя-бы разбор GET запроса, а лучше POST-urlencoded или даже multipart.
                Это гораздо трудней для новичков реализовать. И, как раз, требующее пояснений.

                2ALL: У меня вот есть веб-сервер на Delphi 6 (самописный), нужно ли мне написать статью о нем и дать исходники?
              +3
              Пара замечаний.
              server_status тут не нужен — почему бы в конструкторе окна просто не присвоить tcpServer = NULL, а потом не проверять if (tcpServer) {...}?
              Утечка памяти, кстати, тоже присутствует — tcpServer не удаляется.
              И ни слова о параллельном подключении нескольких клиентов.

              В примерах, поставляемых с Qt, есть пример реализации многопоточного сервера — Threaded Fortune Server, мне кажется, начинать надо как раз с такой архитектуры.
                0
                Да, ещё функцию readAll() у сокета вызывать лучше до отправки в него своих данных.
                  0
                  tcpServer = new QTcpServer(this);

                  где здесь утечка? Parent же задется, нет?
                    0
                    valgrind в помощь
                      0
                      чем он сможет здесь помочь, если предок удаляет всех своих детей?
                        0
                        Это проще пареной репы:
                        отлаживаем кусок треда, как отдельный процесс и уже отлаженный код без утечек кусок вставляем в поект.
                          0
                          вариант 2 — находим точку падения,
                          делаем искусственное завершение процесса без его падения (грубо ставим ретурн + освобождение всех ресурсов), отлавливаем утечки на этом куске… идем дальше.
                        +2
                        Parent — это MainWindow, соответственно экземпляры QTcpServer удалятся только тогда, когда удалится MainWindow (при выходе из программы то бишь). То есть формально как бы утечки вроде и нет, а фактически, все созданные QTcpServer будут висеть в памяти до самого конца работы программы, хотя могли бы быть удалены намного раньше, что фактически является утечкой.
                          0
                          Вот про это я и говорил в своём комменте.
                      +2
                      Спасибо. Недавно искал нечто подобное, жалко раньше не появилась эта статья.
                        +6
                        Этот какое-то делфи-мышление, приделывать сервер к окошку.
                          +7
                          О дааа! когда то дейкстра говорил, что человек, знающий бейсик, потерян как программист. Дельфи это такая современная реинкарнация того васика. Настоящий дельфи программист напишет дельфи программу на любом языке программирования.
                            0
                            через 5 лет я Вам скажу, что пхп — это реинкарнация дельфи))
                            все кому не лень изучают, потом пишут го*но код, потом надо это всё поддерживать)
                          +1
                          В слоте newUser новые соединения следует обрабатывать в цикле:
                          while (tcpServer->nasPendingConnection()) {

                          }

                          Иначе вполне можете пропустить клиента при большой интенсивности запросов.
                            0
                            В догонку:
                            >QMap SClients; Данная карта хранит объекты созданных сокетов. Ее использую
                            > например если принудительно останавливаю сервер и мне необходимо закрыть открытые сокеты.
                            > Если их не закрыть, то клиент будет еще долго ждать ответ от нашего сервера и не закрывать соединение.

                            The socket is created as a child of the server, which means that it is automatically deleted when the QTcpServer object is destroyed. It is still a good idea to delete the object explicitly when you are done with it, to avoid wasting memory.

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

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

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

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