Różnice między wybraną wersją a wersją aktualną.
| Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja | ||
|
sk2:qt [2020/11/17 13:25] jkonczak [Zadania] |
sk2:qt [2024/11/28 20:13] (aktualna) jkonczak [Zadania] |
||
|---|---|---|---|
| Linia 18: | Linia 18: | ||
| === O Qt === | === O Qt === | ||
| - | [[https://www.qt.io/product|[1]]] [[https://pl.wikipedia.org/wiki/Qt|[2]]] | + | Qt to framework (zestaw bibliotek i narzędzi) dla języka C+''''+. Duży nacisk w Qt jest położony na budowę aplikacji z graficznym interfejsem użytkownika i tworzenie wieloplatformowego kodu. \\ |
| + | [[https://www.qt.io/product|Strona produktu]] [[https://wiki.qt.io/About_Qt|Strona "O Qt" w wiki Qt]] [[https://pl.wikipedia.org/wiki/Qt|Polska Wikipedia o Qt]] | ||
| === Narzędzia === | === Narzędzia === | ||
| - | Qt dostarcza własny system budowania - program ''[[http://doc.qt.io/qt-5/qmake-manual.html|qmake]]'', co oczywiście nie przeszkadza korzystać z innych systemów budowania (np. CMake). | + | <small>Qt do wersji 5 jako podstawowego systemu budowania używa dostarczonego z Qt programu ''[[https://doc.qt.io/qt-5/qmake-manual.html|qmake]]''. Dodatkowo Qt 5 wspiera oficjalnie CMake (nieoficjalnie Qt jest wspierane też przez inne systemy budowania).</small>\\ |
| - | \\ | + | Qt od wersji 6 jako podstawowego systemu budowania używa CMake ([[https://doc.qt.io/qt-6/cmake-manual.html|dokumentacja dla Qt]]) nie zaprzestając rozwoju qmake. |
| - | Do graficznego projektowania GUI dostarczany jest program ''[[http://doc.qt.io/qt-5/qtdesigner-manual.html|designer]]'', generujący pliki w formacie XML. Następnie programem ''[[http://doc.qt.io/qt-5/uic.html|uic]]'' są one konwertowane na kod w C+''''+. \\ | + | |
| - | <html><small></html>Ponadto ''assistant'' pozwala przeglądać dokumentację, ''linguist'' w łatwy sposób umożliwia tłumaczenie programu, ''qtchooser'' pozwala wybierać między zainstalowanymi wersjami Qt. Częścią SDK do Qt jest też IDE ''qtcreator''.<html></small></html> | + | Do graficznego projektowania GUI opartego o [[https://pl.wikipedia.org/wiki/Wid%C5%BCet|widżety]] dostarczany jest program ''[[https://doc.qt.io/qt-6/qtdesigner-manual.html|designer]]'', generujący pliki w formacie XML. Następnie programem ''[[https://doc.qt.io/qt-6/uic.html|uic]]'' są one automatycznie konwertowane na kod w C+''''+. \\ |
| + | <html><small></html>Qt dostarcza też silnik [[https://doc.qt.io/qt-6/qmlapplications.html|języka QML]] i biblioteki [[https://doc.qt.io/qt-6/qtquickcontrols-index.html|Qt Quick]] do budowy GUI w QML. \\ Ponadto ''assistant'' pozwala przeglądać dokumentację, ''linguist'' w łatwy sposób umożliwia tłumaczenie programu. \\ Częścią SDK do Qt jest też IDE ''qtcreator''.<html></small></html> | ||
| === Pętla zdarzeń, sygnały i sloty === | === Pętla zdarzeń, sygnały i sloty === | ||
| - | Typowe aplikacje w Qt wykorzystują [[http://doc.qt.io/qt-5/qapplication.html#exec|pętlę zdarzeń]]. Obiekty mogą zażądać obsługi zdarzenia (zdarzenie w Qt nazywa się ''signal''), a programista za pomocą funkcji ''connect'' określa jaka funkcja (nazywana w Qt ''slot'') ma być wywoływana gdy wystąpi podane zdarzenie ([[http://doc.qt.io/qt-5/signalsandslots.html|signals and slots]]). | + | Typowe aplikacje w Qt wykorzystują [[https://doc.qt.io/qt-6/qapplication.html#exec|pętlę zdarzeń]]. Obiekty mogą zażądać obsługi zdarzenia (zdarzenie w Qt nazywa się ''signal''), a programista za pomocą funkcji ''connect'' określa jaka funkcja (nazywana w Qt ''slot'') ma być wywoływana gdy wystąpi podane zdarzenie ([[https://doc.qt.io/qt-6/signalsandslots.html|signals and slots]]). |
| \\ | \\ | ||
| - | Z historycznych przyczyn Qt korzysta z dodatkowego preprocesora ''[[http://doc.qt.io/qt-5/why-moc.html|moc]]''. Od Qt5 użycie ''moc'' można pominąć, jeśli korzysta się z nowej składni funkcji ''connect''. | + | Qt korzysta z dodatkowego preprocesora ''[[https://doc.qt.io/qt-6/why-moc.html|moc]]''. Od Qt5 użycie ''moc'' można pominąć, jeśli korzysta się z nowej składni funkcji ''connect'' i nie definiuje własnych sygnałów. |
| \\ | \\ | ||
| - | <html><small></html>Zarówno ''uic'' jak i ''moc'' są wywoływane automatycznie przez system budowania.<html></small></html> | + | <small>Zarówno ''uic'' jak i ''moc'' są wywoływane automatycznie przez system budowania qmake. |
| + | W CMake dla Qt6 należy <html><a class="urlextern" href="https://doc.qt.io/qt-6/cmake-get-started.html#building-a-c-gui-application"></html>dodać ''qt_standard_project_setup()'' i zamiast ''add_executable'' użyć ''qt_add_executable''<html></a></html> (w Qt5 zamiast tego trzeba włączyć opcje [[https://cmake.org/cmake/help/latest/prop_tgt/AUTOUIC.html|AUTOUIC]] i [[https://cmake.org/cmake/help/latest/prop_tgt/AUTOMOC.html|AUTOMOC]]).<html></small></html> | ||
| ===== Sieć w Qt ===== | ===== Sieć w Qt ===== | ||
| - | Opis z podręcznika Qt: [[https://doc.qt.io/qt-5/qtnetwork-programming.html|Network Programming with Qt]] | + | Opis z podręcznika Qt: [[https://doc.qt.io/qt-6/qtnetwork-programming.html|Network Programming with Qt]] |
| W Qt funkcje sieciowe są **nieblokujące** (asynchroniczne).\\ | W Qt funkcje sieciowe są **nieblokujące** (asynchroniczne).\\ | ||
| Linia 41: | Linia 44: | ||
| Ważniejsze klasy do obsługi socketów i podstawowe metody i sygnały: | Ważniejsze klasy do obsługi socketów i podstawowe metody i sygnały: | ||
| - | * ''[[http://doc.qt.io/qt-5/qtcpserver.html|QTcpServer]]'' - klasa tworząca gniazdo nasłuchujące protokołu TCP | + | * ''[[https://doc.qt.io/qt-6/qtcpserver.html|QTcpServer]]'' - klasa tworząca gniazdo nasłuchujące protokołu TCP |
| - | * klasa udostępnia sygnał (zdarzenie) ''[[http://doc.qt.io/qt-5/qtcpserver.html#newConnection|newConnection]]'' | + | * klasa udostępnia sygnał (zdarzenie) ''[[https://doc.qt.io/qt-6/qtcpserver.html#newConnection|newConnection]]'' |
| - | * metoda ''[[http://doc.qt.io/qt-5/qtcpserver.html#listen|listen]]'' wywołuje ''bind'', ustawia ''SO_REUSEADDR''((w uproszczeniu; szczegóły tutaj: https://doc.qt.io/qt-5/qabstractsocket.html#BindFlag-enum)) i wywołuje ''listen'' | + | * metoda ''[[https://doc.qt.io/qt-6/qtcpserver.html#listen|listen]]'' wywołuje ''bind'', ustawia ''SO_REUSEADDR''((w uproszczeniu; szczegóły tutaj: https://doc.qt.io/qt-6/qabstractsocket.html#BindFlag-enum)) i wywołuje ''listen'' |
| - | * metoda ''[[http://doc.qt.io/qt-5/qtcpserver.html#nextPendingConnection|nextPendingConnection]]'' wykonuje ''accept'' | + | * metoda ''[[https://doc.qt.io/qt-6/qtcpserver.html#nextPendingConnection|nextPendingConnection]]'' wykonuje ''accept'' |
| - | * ''[[http://doc.qt.io/qt-5/qtcpsocket.html|QTcpSocket]]'' - klasa gniada TCP klienckiego (łączącego się pod podany adres) | + | * ''[[https://doc.qt.io/qt-6/qtcpsocket.html|QTcpSocket]]'' - klasa gniada TCP klienckiego (łączącego się pod podany adres) |
| - | * sygnały ''[[http://doc.qt.io/qt-5/qabstractsocket.html#signals|connected]]'' i ''[[http://doc.qt.io/qt-5/qabstractsocket.html#signals|disconnected]]'' | + | * sygnały ''[[https://doc.qt.io/qt-6/qabstractsocket.html#signals|connected]]'' i ''[[https://doc.qt.io/qt-6/qabstractsocket.html#signals|disconnected]]''((Rozłączenie gniazda poza sygnałem ''disconnected'' wygeneruje też ''errorOccured'' z argumentem ''RemoteHostClosedError''.)) |
| - | * sygnał ''[[http://doc.qt.io/qt-5/qabstractsocket.html#signals|errorOccured]]'' (Qt w wersji ≥5.15) \\ <html><small></html> sygnał ''[[http://doc.qt.io/qt-5.14/qabstractsocket.html#signals|error]]'' (Qt w wersji przed 5.15)(( Uwaga, w nowej składni ''connect'' wymagany jest wskaźnik na funkcję, a klasa QTcpSocket ma dwie funkcje ''error''. W efekcie potrzebne jest wybranie funkcji przez rzutowanie na właściwy typ [[https://wiki.qt.io/New_Signal_Slot_Syntax#Asynchronous_made_easier|[1]]] ))<html></small></html> | + | * sygnał ''[[https://doc.qt.io/qt-6/qabstractsocket.html#signals|errorOccured]]'' (Qt w wersji ≥5.15) \\ <html><small></html> sygnał ''[[https://doc.qt.io/qt-5.14/qabstractsocket.html#signals|error]]'' (Qt w wersji przed 5.15)(( Uwaga, w nowej składni ''connect'' wymagany jest wskaźnik na funkcję, a klasa QTcpSocket ma dwie funkcje ''error''. W efekcie potrzebne jest wybranie funkcji przez rzutowanie na właściwy typ [[https://wiki.qt.io/New_Signal_Slot_Syntax#Asynchronous_made_easier|[1]]] ))<html></small></html> |
| - | * sygnał ''[[http://doc.qt.io/qt-5/qiodevice.html#signals|readyRead]]'', określający że przyszły nowe dane do odbioru | + | * sygnał ''[[https://doc.qt.io/qt-6/qiodevice.html#signals|readyRead]]'', określający że przyszły nowe dane do odbioru |
| - | * metoda ''[[http://doc.qt.io/qt-5/qabstractsocket.html#connectToHost|connectToHost]]'' żąda połączenia pod podany adres | + | * metoda ''[[https://doc.qt.io/qt-6/qabstractsocket.html#connectToHost|connectToHost]]'' żąda połączenia pod podany adres |
| - | * metoda ''[[http://doc.qt.io/qt-5/qiodevice.html#write|write]]'' pisze dane, natomiast ''[[http://doc.qt.io/qt-5/qiodevice.html#read|read]]'' i ''[[http://doc.qt.io/qt-5/qiodevice.html#readAll|readAll]]'' odbierają dane | + | * metoda ''[[https://doc.qt.io/qt-6/qiodevice.html#write|write]]'' pisze dane, natomiast ''[[https://doc.qt.io/qt-6/qiodevice.html#read|read]]'' i ''[[https://doc.qt.io/qt-6/qiodevice.html#readAll|readAll]]'' odbierają dane |
| - | * ''[[http://doc.qt.io/qt-5/qudpsocket.html|QUdpSocket]]'' - klasa obsługująca gniazda UDP | + | * ''[[https://doc.qt.io/qt-6/qudpsocket.html|QUdpSocket]]'' - klasa obsługująca gniazda UDP |
| * sygnały identyczne jak dla ''QTcpSocket'' | * sygnały identyczne jak dla ''QTcpSocket'' | ||
| - | * odbiór wiadomości metodą ''[[http://doc.qt.io/qt-5/qudpsocket.html#readDatagram|readDatagram]]'' | + | * odbiór wiadomości metodą ''[[https://doc.qt.io/qt-6/qudpsocket.html#receiveDatagram|receiveDatagram]]'' |
| - | * wysłanie wiadomości metodą ''[[http://doc.qt.io/qt-5/qudpsocket.html#writeDatagram|writeDatagram]]'' | + | * wysłanie wiadomości metodą ''[[https://doc.qt.io/qt-6/qudpsocket.html#writeDatagram|writeDatagram]]'' |
| Qt jest modularny. Do włączenia modułu odpowiedzialnego za sieć należy: | Qt jest modularny. Do włączenia modułu odpowiedzialnego za sieć należy: | ||
| * dla qmake dodać linię: \\ ''QT += network'' | * dla qmake dodać linię: \\ ''QT += network'' | ||
| - | * dla cmake dodać do linii szukające Qt komponent Network, a następnie dodać go do bibliotek łączonych: \\ ''find_package(Qt5 COMPONENTS … Network …) \\ target_link_libraries(target … Qt5::Network …)'' | + | * dla cmake dodać do linii szukające Qt komponent Network, a następnie dodać go do bibliotek łączonych: \\ ''find_package(Qt6 COMPONENTS … Network …) \\ target_link_libraries(target … Qt6::Network …)'' \\ <html><small></html>Dla Qt 5 trzeba zmienić oczywiście Qt6 na Qt5 <html></small></html> |
| + | |||
| + | <html><small></html> | ||
| | | ||
| + | ++++ Przykład konsolowego klienta TCP w Qt | | ||
| + | **Uwaga**: ten przykład pokazuje kilka niewygodnych / nieprzenośnych / rzadko używanych rzeczy: | ||
| + | * nie ma przenośnego sposobu czekania na zdarzenia na standardowym wejściu – stąd osobny kod dla Windowsa | ||
| + | * raczej rzadko spotyka się w wysokopoziomowym, obiektowym języku funkcje i zmienne globalne - stąd potrzeba ''QObject::connect'' zamiast ''connect'', ''&socket'' zamiast ''socket'' i podawanie do obsługi zdarzenia globalnej funkcji zamiast obiektu i wskaźnika na metodę z klasy. | ||
| + | |||
| + | <code cmake CMakeLists.txt> | ||
| + | cmake_minimum_required(VERSION 3.12) | ||
| + | project(tcp_client LANGUAGES CXX) | ||
| + | set(CMAKE_CXX_STANDARD 20) | ||
| + | find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Network REQUIRED) | ||
| + | find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Network REQUIRED) | ||
| + | add_executable(tcp_client main.cpp) | ||
| + | target_link_libraries(tcp_client Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network) | ||
| + | </code> | ||
| + | <code cpp main.cpp> | ||
| + | #include <QtCore> | ||
| + | #include <QtNetwork> | ||
| + | #include <unistd.h> | ||
| + | #ifdef _WIN32 | ||
| + | #include "Windows.h" | ||
| + | #endif | ||
| + | |||
| + | void readStdin(); | ||
| + | void onConnect(); | ||
| + | void readSocket(); | ||
| + | void onError(QTcpSocket::SocketError err); | ||
| + | |||
| + | QTcpSocket socket; | ||
| + | |||
| + | int main(int argc, char *argv[]) | ||
| + | { | ||
| + | QCoreApplication a(argc, argv); | ||
| + | |||
| + | // obsługa standardowego wejścia jako zdarzenie | ||
| + | #ifdef _WIN32 | ||
| + | QWinEventNotifier inputNotifier(GetStdHandle(STD_INPUT_HANDLE)); | ||
| + | QObject::connect(&inputNotifier, &QWinEventNotifier::activated, readStdin); | ||
| + | #else | ||
| + | QSocketNotifier inputNotifier(0, QSocketNotifier::Read); | ||
| + | QObject::connect(&inputNotifier, &QSocketNotifier::activated, readStdin); | ||
| + | #endif | ||
| + | |||
| + | QObject::connect(&socket, &QTcpSocket::connected, onConnect); | ||
| + | QObject::connect(&socket, &QTcpSocket::readyRead, readSocket); | ||
| + | QObject::connect(&socket, &QTcpSocket::errorOccurred, onError); | ||
| + | |||
| + | socket.connectToHost(a.arguments().at(1), a.arguments().at(2).toUShort()); | ||
| + | |||
| + | return a.exec(); | ||
| + | } | ||
| + | |||
| + | void readStdin(){ | ||
| + | QByteArray ba(1024, '\0'); | ||
| + | int count = read(STDIN_FILENO, ba.data(), ba.size()); | ||
| + | if(count<=0){ | ||
| + | socket.disconnectFromHost(); | ||
| + | QByteArray ba = "\033[1mError or EOF on standard input\033[0m\n"; | ||
| + | write(STDOUT_FILENO, ba.data(), ba.length()); | ||
| + | QCoreApplication::exit(0); | ||
| + | } | ||
| + | ba.resize(count); | ||
| + | socket.write(ba); | ||
| + | } | ||
| + | |||
| + | void onConnect(){ | ||
| + | QByteArray ba = ("\033[1mConnected successfully to : " + | ||
| + | socket.peerAddress().toString() + ":" + | ||
| + | QString::number(socket.peerPort()) + | ||
| + | "\033[0m\n").toLocal8Bit(); | ||
| + | write(STDOUT_FILENO, ba.data(), ba.length()); | ||
| + | } | ||
| + | |||
| + | void readSocket(){ | ||
| + | QByteArray ba = socket.readAll(); | ||
| + | write(STDOUT_FILENO, ba.data(), ba.length()); | ||
| + | } | ||
| + | void onError(QTcpSocket::SocketError err){ | ||
| + | QByteArray ba = ("\033[1mError or disconnect on socket: "+socket.errorString()+"\033[0m\n").toLocal8Bit(); | ||
| + | write(STDOUT_FILENO, ba.data(), ba.length()); | ||
| + | QCoreApplication::exit(0); | ||
| + | } | ||
| + | </code> | ||
| + | ++++ | ||
| + | |||
| + | <html></small></html> | ||
| ===== Zadania ===== | ===== Zadania ===== | ||
| | | ||
| Linia 68: | Linia 158: | ||
| === IP multicast - przypomnienie === | === IP multicast - przypomnienie === | ||
| [[http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml|Pula adresów]] (224.0.0.0/4), w tym: lokalne dla łącza adresy (224.0.0.0/24), [[https://tools.ietf.org/html/rfc2365|Organization-Local Scope]] (239.0.0.0/24)\\ | [[http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml|Pula adresów]] (224.0.0.0/4), w tym: lokalne dla łącza adresy (224.0.0.0/24), [[https://tools.ietf.org/html/rfc2365|Organization-Local Scope]] (239.0.0.0/24)\\ | ||
| - | Komputer chcąc odbierać wiadomości od grupy multicastowej musi wysłać odpowiednią wiadomość IGMP. \\ | + | Komputer chcąc odbierać wiadomości od grupy multicastowej musi wysłać odpowiednią wiadomość IGMP. \\ |
| + | BSD Socket API pozwala dołączyć do grupy multicastowej na konkretnym sockecie, adresie i interfejsie, co powoduje wysłanie wspomnianej wiadomości. \\ | ||
| IP multicast można używać w połączeniu z protokołem UDP, natomiast nie jest wspierany przez protokół TCP. | IP multicast można używać w połączeniu z protokołem UDP, natomiast nie jest wspierany przez protokół TCP. | ||
| \\ | \\ | ||
| Linia 74: | Linia 165: | ||
| Użycie IP multicast w BSD socket API: [[sk2:multicast_example]]\\ | Użycie IP multicast w BSD socket API: [[sk2:multicast_example]]\\ | ||
| Lista grup mcastowych do których jest zapisany komputer: ''ip maddr'' lub ''netstat -ng'' lub ''cat /proc/net/igmp'' | Lista grup mcastowych do których jest zapisany komputer: ''ip maddr'' lub ''netstat -ng'' lub ''cat /proc/net/igmp'' | ||
| - | ++++ Użycie programu socat do testowania multicastu: | <code bash> | + | ++++ Użycie programu socat do testowania multicastu: | |
| + | Zajęcia w laboratorium: | ||
| + | <code bash> | ||
| + | socat UDP-DATAGRAM:239.0.0.1:2000,reuseaddr,bind=0.0.0.0:2000,ip-add-membership=239.0.0.1:enp0s3 STDIO | ||
| + | |||
| + | # UDP-DATAGRAM:239.0.0.1:2000 → wysyłaj datagramy do podanego adresu (239.0.0.1:2000) i odbieraj co przyjdzie | ||
| + | # reuseaddr → włącze SO_REUSEADDR; pozwala wielu programom na tym samym komputerze używać tego samego portu do mcastu | ||
| + | # ip-add-membership=239.0.0.1:br0 → dołącza do grupy mcastowej 239.0.0.1 na urządzeniu br0 | ||
| + | # bind=0.0.0.0:2000 → wykonaj bind na podany adres; pod tym adresem będą odbierane datagramy | ||
| + | </code> | ||
| + | |||
| + | Zajęcia zdalne: | ||
| + | <code bash> | ||
| socat UDP-DATAGRAM:239.0.0.1:2000,reuseaddr,bind=0.0.0.0:2000,ip-add-membership=239.0.0.1:tap0,ip-multicast-if=10.0.9.X STDIO | socat UDP-DATAGRAM:239.0.0.1:2000,reuseaddr,bind=0.0.0.0:2000,ip-add-membership=239.0.0.1:tap0,ip-multicast-if=10.0.9.X STDIO | ||
| + | |||
| # UDP-DATAGRAM:239.0.0.1:2000 → wysyłaj datagramy do podanego adresu (239.0.0.1:2000) i odbieraj co przyjdzie | # UDP-DATAGRAM:239.0.0.1:2000 → wysyłaj datagramy do podanego adresu (239.0.0.1:2000) i odbieraj co przyjdzie | ||
| # reuseaddr → włącze SO_REUSEADDR; pozwala wielu programom na tym samym komputerze używać tego samego portu do mcastu | # reuseaddr → włącze SO_REUSEADDR; pozwala wielu programom na tym samym komputerze używać tego samego portu do mcastu | ||
| Linia 82: | Linia 186: | ||
| # ip-multicast-if=10.0.9.X → nakazuje wysyłać wiadomości do grupy multicastowej z podanego adresu | # ip-multicast-if=10.0.9.X → nakazuje wysyłać wiadomości do grupy multicastowej z podanego adresu | ||
| </code> | </code> | ||
| + | |||
| + | |||
| ++++ | ++++ | ||
| <html></small></html> | <html></small></html> | ||