Narzędzia użytkownika

Narzędzia witryny


Pasek boczny

sk2:qt

To jest stara wersja strony!


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

  * 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

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)
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);
 
    #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 Użycie programu socat do testowania multicastu:

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.

sk2/qt.1669646481.txt.gz · ostatnio zmienione: 2022/11/28 15:41 przez jkonczak