======= Obsługa sieci w API / językach wysokiego poziomu ======= BSD socket API jest interfejsem niskiego poziomu do obsługi sieci. Pozwala on korzystać z wszystkich możliwości oferowanych przez system operacyjny. Co za tym idzie, nie jest on szczególnie wygodny, a typowe programy używające BSD socket API będą zawierać identyczne ciągi wywołań funkcji, w większości z identycznymi argumentami. \\ Stąd w bibliotekach / językach wysokiego poziomu są dostępne uproszczone interfejsy do obsługi sieci, które opakowują wywołania z BSD socket API. Typowo wysokopoziomowe interfejsy: * są obiektowe * posiadają osobne klasy do typowych zadań: * nawiązanie połączenia TCP do podanego adresu * stworzenie serwera TCP * użycie gniazda UDP * często są przygotowane do łatwego wykorzystania w pętli zdarzeń * często pojawiają się bardziej specjalizowane klasy, np. do komunikacji żądanie-odpowiedź (HTTP). Takie podejście pozwala przygotować wygodne i łatwe do nauczenia interfejsy programistyczne do obsługi sieci. Trzeba jednak pamiętać że takie interfejsy są tworzone pod typowe zastosowania, co czasami ogranicza możliwości ich wykorzystania. ======= Qt ======= ===== Wprowadzenie ===== === O Qt === 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 === 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).\\ 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 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+''''+. \\ 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''. === Pętla zdarzeń, sygnały i sloty === 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]]). \\ 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. \\ Zarówno ''uic'' jak i ''moc'' są wywoływane automatycznie przez system budowania qmake. W cmake 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]]. ===== Sieć w 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).\\ Bufory nadawcze i odbiorcze w Qt są nieograniczone. Ważniejsze klasy do obsługi socketów i podstawowe metody i sygnały: * ''[[https://doc.qt.io/qt-6/qtcpserver.html|QTcpServer]]'' - klasa tworząca gniazdo nasłuchujące protokołu TCP * klasa udostępnia sygnał (zdarzenie) ''[[https://doc.qt.io/qt-6/qtcpserver.html#newConnection|newConnection]]'' * 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 ''[[https://doc.qt.io/qt-6/qtcpserver.html#nextPendingConnection|nextPendingConnection]]'' wykonuje ''accept'' * ''[[https://doc.qt.io/qt-6/qtcpsocket.html|QTcpSocket]]'' - klasa gniada TCP klienckiego (łączącego się pod podany adres) * sygnały ''[[https://doc.qt.io/qt-6/qabstractsocket.html#signals|connected]]'' i ''[[https://doc.qt.io/qt-6/qabstractsocket.html#signals|disconnected]]'' * sygnał ''[[https://doc.qt.io/qt-6/qabstractsocket.html#signals|errorOccured]]'' (Qt w wersji ≥5.15) \\ 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]]] )) * sygnał ''[[https://doc.qt.io/qt-6/qiodevice.html#signals|readyRead]]'', określający że przyszły nowe dane do odbioru * metoda ''[[https://doc.qt.io/qt-6/qabstractsocket.html#connectToHost|connectToHost]]'' żąda połączenia pod podany adres * 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 * ''[[https://doc.qt.io/qt-6/qudpsocket.html|QUdpSocket]]'' - klasa obsługująca gniazda UDP * sygnały identyczne jak dla ''QTcpSocket'' * odbiór wiadomości metodą ''[[https://doc.qt.io/qt-6/qudpsocket.html#receiveDatagram|receiveDatagram]]'' * 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: * 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(Qt6 COMPONENTS … Network …) \\ target_link_libraries(target … Qt6::Network …)'' \\ Dla Qt 5 trzeba zmienić oczywiście Qt6 na Qt5 ++++ 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. 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) #include #include #include #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); } ++++ ===== Zadania ===== //Zadanie 1.// Korzystając z {{:sk2:l6_z1_template.tar.xz|}} stwórz program, który będzie działać jak graficzna wersja programu ''netcat''. Kod zawiera komentarze z wyszczególnionymi krokami do zrobienia (''TODO'''s). === 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)\\ 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. \\ 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'' ++++ Użycie programu socat do testowania multicastu: | Zajęcia w laboratorium: 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:enp0s3 → dołącza do grupy mcastowej 239.0.0.1 na urządzeniu enp0s3 # bind=0.0.0.0:2000 → wykonaj bind na podany adres; pod tym adresem będą odbierane datagramy Zajęcia zdalne: 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 ++++ //Zadanie 2.// Korzystając z {{:sk2:l6_z2_template.tar.xz|}} stwórz program, który będzie wysyłać i odbierać wiadomości z wybranej grupy multicastowej. Kod zawiera komentarze z wyszczególnionymi krokami do zrobienia (''TODO'''s). //Zadanie 3.// Napisz serwer czatu (serwer, który każdą otrzymaną wiadomość przekaże wszystkim połączonym klientom) wykorzystując Qt.