======= 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 dla Qt6 należy dodać ''qt_standard_project_setup()'' i zamiast ''add_executable'' użyć ''qt_add_executable'' (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]]).
===== 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]]''((Rozłączenie gniazda poza sygnałem ''disconnected'' wygeneruje też ''errorOccured'' z argumentem ''RemoteHostClosedError''.))
* 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: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
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.