Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja | ||
sk2:qt [2019/11/18 21:44] jkonczak |
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|error]]'' (( 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]]] )) | + | * 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 podobne 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>socat UDP-DATAGRAM:239.0.0.1:2000,reuseaddr,bind=0.0.0.0:2000,ip-add-membership=239.0.0.1:eth0 STDIO | + | ++++ 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 | # 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 | ||
+ | # 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 | # bind=0.0.0.0:2000 → wykonaj bind na podany adres; pod tym adresem będą odbierane datagramy | ||
- | # ip-add-membership=239.0.0.1:eth0 → dołącza do grupy mcastowej 239.0.0.1 na urządzeniu eth0</code> | + | </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 | ||
+ | |||
+ | # 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:tap0 → dołącza do grupy mcastowej 239.0.0.1 na urządzeniu tap0 | ||
+ | # bind=0.0.0.0:2000 → wykonaj bind na podany adres; pod tym adresem będą odbierane datagramy | ||
+ | # ip-multicast-if=10.0.9.X → nakazuje wysyłać wiadomości do grupy multicastowej z podanego adresu | ||
+ | </code> | ||
+ | |||
++++ | ++++ | ||
<html></small></html> | <html></small></html> |