понедельник, 28 апреля 2014 г.

Сетевой обмен в Qt (QTcpServer и QTcpClient) (часть 1)

Практически все стоящие приложения так или иначе связаны с сетевым обменом данных. Этот пост посвящен обмену по tcp-протоколу между клиентом и сервером для Qt 5.2.1

Основные классы, которые нам потребуются - QTcpSocket и QTcpServer. Мы напишем 2 приложения, одно из которых будет сервером, а второе клиентом.
Для начала нам необходимо написать сервер, который будет принимать подключения от клиентов. Все данные, которые мы будем получать от первого клиента,  будем пересылать остальным подключенным клиентам. Т.е. мы сделаем некоторую простую версию системы вещания.

Создадим простое консольное приложение Qt. Что именно необходимо выбрать показано на картинке ниже.
Что из себя представляет сервер? В нашем случае это класс, который по определенному айпи-адресу сервера (можно и по всем) занимает под себя некоторый порт и слушает его на предмет запроса о подключении.
Как только происходит подключение от какого-либо клиента, он выдает сигнал newConnection().
А теперь к практике:
Для начала добавим модуль работы с сетью в .pro file нашего проекта:

QT       += core network

В данном случае к уже существующему модулю core я добавил модуль network.
Теперь нам необходимо добавить класс, в котором мы будем производить все наши манепуляции с QTcpServer. Назовем его MyServer и унаследуем от QObject, дабы мы молги работать со сигналами и слотами. В .h файле у меня получается следующий код:

#ifndef MYSERVER_H
#define MYSERVER_H

#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include <QByteArray>
#include <QDataStream>

class MyServer : public QObject
{
    Q_OBJECT
public:
    explicit MyServer(QObject *parent = 0); // конструктор

signals:

public slots:
    void incommingConnection(); // обработчик входящего подключения
    void readyRead(); // обработчик входящих данных
    void stateChanged(QAbstractSocket::SocketState stat); // обработчик изменения состояния вещающего сокета (он нам важен, дабы у нас всегда был кто-то, кто будет вещать

private:
    QTcpServer *server; // указатель на сервер
    QList<QTcpSocket *> sockets; // получатели данных
    QTcpSocket *firstSocket; // вещатель
};

#endif // MYSERVER_H
Извиняюсь, если код оказался туповат, я его выдрал из одного проекта. Итак, логика нашего приложения: У нас есть первый подключившийся клиент, он становится "вещающим", остальные, кто подключаются, становятся получателями вещания. Если "вещающий" отключается, то следующий подключивший после его отключения становится вещающим.

#include "myserver.h"

MyServer::MyServer(QObject *parent) :
    QObject(parent),
    firstSocket(NULL)
{
    server = new QTcpServer(this);
    qDebug() << "server listen = " << server->listen(QHostAddress::Any, 6666);
    connect(server, SIGNAL(newConnection()), this, SLOT(incommingConnection())); // подключаем сигнал "новое подключение" к нашему обработчику подключений
}


void MyServer::incommingConnection() // обработчик подключений
{
    QTcpSocket * socket = server->nextPendingConnection(); // получаем сокет нового входящего подключения
    connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(stateChanged(QAbstractSocket::SocketState))); // делаем обработчик изменения статуса сокета
    if (!firstSocket) { // если у нас нет "вещающего", то данное подключение становится вещающим
        connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead())); // подключаем входящие сообщения от вещающего на наш обработчик
        socket->write("server"); // говорим ему что он "вещает"
        firstSocket = socket; // сохраняем себе"
        qDebug() << "this one is server";
    }
    else { // иначе говорим подключенному что он "получатель"
        socket->write("client");
        sockets << socket;
    }
}


void MyServer::readyRead() // обработчик входящих сообщений от "вещающего"
{
    QObject * object = QObject::sender(); // далее и ниже до цикла идет преобразования "отправителя сигнала" в сокет, дабы извлечь данные
    if (!object)
        return;
    qDebug() << "[1]";
    QTcpSocket * socket = static_cast<QTcpSocket *>(object);
    QByteArray arr =  socket->readAll();

    // на самом деле весь верхний код можно было заменить на firstSocket, но я выдирал код из другого проекта, и переписывать мне лень :)
    foreach(QTcpSocket *socket, sockets) { // пишем входящие данные от "вещающего" получателям
        if (socket->state() == QTcpSocket::ConnectedState)
            socket->write(arr);
    }
}

void MyServer::stateChanged(QAbstractSocket::SocketState state) // обработчик статуса, нужен для контроля за "вещающим"
{
    QObject * object = QObject::sender();
    if (!object)
        return;
    QTcpSocket * socket = static_cast<QTcpSocket *>(object);
    qDebug() << state;
    if (socket == firstSocket && state == QAbstractSocket::UnconnectedState)
        firstSocket = NULL;

}

Надеюсь по комментариям большая часть кода понятна, но, если будут вопросы, обязательно пишите. В следующем посте я приведу пример клиента.
Про многопоточную реализацию можно почитать здесь: http://pro-prof.com/archives/1390

3 комментария: