четверг, 31 октября 2013 г.

Qt 5.0 Работа с сетью. и Лайф-хак по запуску Qt программ без зависимостей.

Понадобилось как-то мне передавать достаточно специфичные данные по TCP, а на другом конце другая программа их принимает и возвращает некоторый ответ. В Linux  есть замечательная улитка nc для тестирования подобных велосипедов. Хоть она и не идеально подходит для моих нужд( мне нужны 16-ричные данные ), но я бы ей обошелся. Под виндой я нашел только Гипертерминал, который работает из рук вот плохо и умеет только подключатся к портам, а сам открывать не умеет. После недолгого и безуспешного гугления, было принято решение самостоятельно написать свой велосипед для этих нужд с преферансом и сеньоритами да ещё и кросс платформенный.  



Ближе к делу

Всё это лирика, я хочу показать некоторые основные моменты работы с сетью в TCP. Я не открою Америку, есть целая куча хороших турториалов от Qt  , и несколько статей на хабре.

QTcpSocket\QTcpServer.


Всегда есть сервер и клиент, и это важно. Одна сторона открывает соединение, другая, к нему подключается. Но не будем же мы писать 2 программы, мы хотим написать класс работы с сетью, которы реализует как сервер так и клиент, в зависимости от ситуации. Логика проста: постараться быть клиентом, если подключаться не к кому, то открывать серверное соединение.


Дла начала рассмотрим клиент:

Создать и подключить его очень просто. Он плюётся сигналами, когда происходят некоторые события. Так что создайте в вашем родительском классе слоты, в которые будут приходить события об успешном подключении, ошибке, готовности передачи данных и т.д.
QTcpSocket* mTcpSocket = new QTcpSocket(this);
mTcpSocket->connectToHost(adress,port);
connect( mTcpSocket, SIGNAL(connected()),
             this,             SLOT(slotClientConnected()));
connect(mTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
            this,             SLOT(slotClientError(QAbstractSocket::SocketError))
            );
На этом подключение клиента закончилось.

Теперь, как создать сервер:
 QTcpServer* mTcpServer = new QTcpServer();
 mTcpServer->listen(this->adress,this->port);
 connect(mTcpServer,SIGNAL(newConnection ()),this,SLOT(slotServerConnected()));
 connect(mTcpServer,SIGNAL(acceptError(QAbstractSocket::SocketError)),this,SLOT(slotServerError(QAbstractSocket::SocketError)));
По сути, всё одно и тоже, разница только в том, что сервер слушает а клиент старается подключиться. Зная это уже можно написать програмулину, которая ещё не умеет передавать данные но умеет подключаться. Но когда к серверу произошло подклчение, сервер плюнется сигналом newConnection(), а после этого из него можно достать клиентский сокет для передачи данный с помощью метода mTcpServer->nextPendingConnection(); И ещё, если вы хотите держать только одно соединение в один момент времени, не забудьте закрыть серверный сокет!! mTcpServer->close();

Следущий этап - передача данных:
После осуществления подключения, на каждой стороне вы имеете по Клиентскому сокету. Каждый их них имеет метод write, а в качестве параметра может принять ByteArray. Вот так и пишите. Всё, что вам угодно преобразуйте в ByteArray и пишите получившийся массив в сокет. Чтение данных:
Когда в стеке TCP есть что прочитать, клиентский сокет будет сигнализировать вам об этом сигналом readyRead(). После этого из сокета можно прочесть данные с помощью метода TcpSocket->readAll(). Ещё есть очень полезный метод bytesAvailable(), который возврящает количество доступных байт в очереди. Разные программисты по разному организовывают цикл чтения из сокета.Я для себя сделал такой велосипед:
 while(this->mTcpSocket->bytesAvailable()>0)
    {
        inputData.append(this->mTcpSocket->readAll());
    }
Вот и всё, по сути то. Тестировать это очень удобно под Линуксом с помощью улиты nc. Вот небольшой мануал по ней.

 Закрытие соединения!
Очень важно правильно и вовремя закрыть соединение, особенно под виндой! Так-что, когда вы закончили работу в сети или произошел какой-то сбой, да и просто на завершении работы приложения нужно обязательно закрыть и приравнять к 0 серверный и клиентский сокеты. Я для себя сделал функцию clear
void ClientServer::clear()
{
    killSocket();
    killServer();
    this->setState(DISCONNECTED);
}

void ClientServer::killSocket(bool abort)
{
    if(mTcpSocket)
    {
        if(mTcpSocket->isOpen())
            mTcpSocket->close();
        mTcpSocket->deleteLater();
        delete mTcpSocket;
        this->mTcpSocket = 0;
    }

}

void ClientServer::killServer()
{
    if(mTcpServer)
    {
        if(mTcpServer->isListening())
             mTcpServer->close();
        mTcpServer->deleteLater();
        delete mTcpServer;
        this->mTcpServer = 0;
    }

}

Запуск Qt программы на Wondows компьютере на котором нет Qt:

Рано или поздно программист Qt сталкивается с проблемой - он не может дать свои программы другим людям, не смотря на то, что они кроссплатформенны. Да, конечно, можно сделать статическую сборку Qt, для этого скачать его, установить Перл и Питон , самостоятельно собрать Qt с определённым флагом , и подождать несколько часов. Но я не стал идти этим путём. У меня Винда на виртуалке , так что оперативная память, жесткий диск,и процессорное время там на вес золота. Немного покопавшись, почему Qt программа не запускается на других машинах, было выяснено, что её не хватает библиотек. Ну так можно их положить рядом программой! 
Для запуска программы, которая работает с сетью рядом с программой нужно положить такие файлы.

-rwxr-xr-x  1 alex root 22378434 Apr 22  2013 icudt51.dll
-rwxr-xr-x  1 alex root  3369922 Apr 22  2013 icuin51.dll
-rwxr-xr-x  1 alex root  1978690 Apr 22  2013 icuuc51.dll
-rwxr-xr-x  1 alex root   544817 Apr 17  2013 libgcc_s_dw2-1.dll
-rwxr-xr-x  1 alex root   989805 Apr 17  2013 libstdc++-6.dll
-rwxr-xr-x  1 alex root    73901 Apr 17  2013 libwinpthread-1.dll
-rwxr-xr-x  1 alex root  4392960 Oct 17 09:49 Qt5Core.dll
-rwxr-xr-x  1 alex root  4453376 Aug 25 21:58 Qt5Gui.dll
-rwxr-xr-x  1 alex root  1393664 Aug 25 21:56 Qt5Network.dll
-rwxr-xr-x  1 alex root  6149632 Aug 25 22:02 Qt5Widgets.dll

И папочку plugins. 
drwxr-xr-x 16 alex root     4096 Oct 30 12:34 plugin

Это всё можно найти там. куда вы устанавливали Qt, обычно это C:\Qt\bin
После этого нужно создать файл qt.conf, такого содержания:
alex@alex-Lenovo-B570 /media/alex/Flash/TestQt $ cat qt.conf

[Paths]

Libraries=./platforms

Компилировал я на машине с Windows XP, а тестировал на Windows XP и Windows 7. Работает.

Но всё равно, вам нужно передать программу, с которой идёт куча dll, и ещё файлик настройки... обычный человек испугается. Для этого я придумал ещё один лайвхак - само распаковывающийся архив. Можно сделать средствами самой винды, программка эта лежит в C:\Windows\system31\iexpress. А можно с помощью WinRar. В любом случае это запуститься на любой машине, есть там винрар или нет.
Вкусность заключается в том, что можно при создании архива указать, файл, который надо запускать после распаковки.
Более того, файлы которые распаковались, после завершения работы программы удалятся.
А ещё можно даже указать программу, которая запустится после окончания работы основной программы и убрать за собой мусор самостоятельно.
Это всё даёт возможность запаковать в архив все библиотеки и служебные файлы вместе с программой, указать, чтоб запуске архива выполнялось наше приложение, и .... оно... ВЫПОЛНИТСЯ!!! А после его окончание удалится весь мусор.
И передавать вы будете один единственный .exe файл - само распаковывающийся архив.

На этом, пожалуй, всё. Очень надеюсь, что эта информация кому-то оказалась полезной.
Ну и конечно, как я могу оставить статью, в которой описываю работу с сетью без исходника? Вот он: https://github.com/alexkutsan/TcpHexTester

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

  1. Большое спасибо за проделанный труд! Я потихоньку осваиваю Qt на работе. Сейчас нужно как раз написать подобную прогу для приёма\отправки данных прибора. Прямо то что нужно! Спасибо большое!

    ОтветитьУдалить
  2. Спасибо за объяснение и за обсуждение переносимости, очень интересно!

    ОтветитьУдалить