Dydaktyka:
FeedbackTo jest stara wersja strony!
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:
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 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]] Strona "O Qt" w wiki Qt Polska Wikipedia o Qt
=== Narzędzia ===
Qt do wersji 5 jako podstawowego systemu budowania używa dostarczonego z Qt programu 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 (dokumentacja dla Qt) nie zaprzestając rozwoju qmake.
Do graficznego projektowania GUI opartego o widżety dostarczany jest program designer
, generujący pliki w formacie XML. Następnie programem uic
są one automatycznie konwertowane na kod w C++.
Qt dostarcza też silnik języka QML i biblioteki 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ą 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 (signals and slots).
Qt korzysta z dodatkowego preprocesora 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 AUTOUIC i AUTOMOC.
===== Sieć w Qt =====
Opis z podręcznika Qt: 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:
* 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]]'' * 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 ''[[http://doc.qt.io/qt-5/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) * sygnały ''[[http://doc.qt.io/qt-5/qabstractsocket.html#signals|connected]]'' i ''[[http://doc.qt.io/qt-5/qabstractsocket.html#signals|disconnected]]'' * 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ł ''[[http://doc.qt.io/qt-5/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 ''[[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 * ''[[http://doc.qt.io/qt-5/qudpsocket.html|QUdpSocket]]'' - klasa obsługująca gniazda UDP * sygnały identyczne jak dla ''QTcpSocket'' * odbiór wiadomości metodą ''[[http://doc.qt.io/qt-5/qudpsocket.html#receiveDatagram|receiveDatagram]]'' * wysłanie wiadomości metodą ''[[http://doc.qt.io/qt-5/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(Qt5 COMPONENTS … Network …)
target_link_libraries(target … Qt5::Network …)
Dla Qt 6 trzeba zmienić oczywiście Qt5 na Qt6
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 <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); #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); }
<html></small></html>
===== 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 ===
Pula adresów (224.0.0.0/4), w tym: lokalne dla łącza adresy (224.0.0.0/24), 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: 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 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.