przejście do zawartości
Jan Kończak
Narzędzia użytkownika
Zaloguj
Narzędzia witryny
Narzędzia
Pokaż stronę
Poprzednie wersje
Odnośniki
Ostatnie zmiany
Menadżer multimediów
Indeks
Zaloguj
Ostatnie zmiany
Menadżer multimediów
Indeks
Jesteś tutaj:
start
»
sk2
»
sfml
sk2:sfml
Ta strona jest tylko do odczytu. Możesz wyświetlić źródła tej strony ale nie możesz ich zmienić.
====== Dokumentacja SFML ====== [[https://www.sfml-dev.org/tutorials/2.6/#network-module|Zbiór samouczków SFML]] [[https://www.sfml-dev.org/documentation/2.6.1/group__network.php|API klas sieciowych]] ====== Ogólne informacje ====== ===== SFML ===== [[https://sfml-dev.org|SFML]] jest prostym zestawem bibliotek do rysowania okienek, opakowywania okna dla OpenGL, obsługi dźwięku i obsługi sieci. \\ "Prostym" należy rozumieć zarówno jako "łatwym do nauki" jak i "mającym funkcjonalność ograniczoną do wybranych, podstawowych rzeczy". ===== Wątki ===== SFML oferuje własne wątki, zamki, …, ale wg. oficjalnej dokumentacji te klasy ustępują bibliotece standardowej C+%%%%+: <html><div style="display:block;margin-left:10px;padding-left:10px;border-left:1px gray solid;font-style: italic;margin-top:-1.2em"> If you work with compilers that support the new standard and its <thread>header, forget about the SFML thread classes and use it instead -- it will be much better. But if you work with a pre-2011 compiler, or plan to distribute your code and want it to be fully portable, the SFML threading classes are a good solution. </div></html> <small><html><div style="text-align:right"></html> (źródło: [[https://www.sfml-dev.org/tutorials/2.6/system-thread.php|SFML tutorial on threads]]) <html></div></html> </small> ===== Pętla zdarzeń ===== W SFML do obsługi GUI programista musi ręcznie zbudować pętlę zdarzeń. <small> Zarówno oficjalne materiały jak i literatura polecana ze strony biblioteki promują użycie aktywnego czekania, tzn. aplikacja będzie zużywać procesor nawet jeśli nic się nie dzieje. Aby uniknąć aktywnego czekania, można dodawać co jakiś czas sleepy; w SFML jest to "oficjalnie" opakowne w funkcję ''[[https://www.sfml-dev.org/documentation/2.6.1/classsf_1_1Window.php#af4322d315baf93405bf0d5087ad5e784|setFramerateLimit]]'' </small> Mimo posiadania modułu do obsługi połączeń sieciowych, funkcja ''[[https://www.sfml-dev.org/documentation/2.6.1/classsf_1_1WindowBase.php#a6a143de089c8716bd42c38c781268f7f|pollEvent]]'' nie wspiera zdarzeń z gniazd. Obsługę gniazd w pętli zdarzeń należy zbudować ręcznie. ===== Budowanie kodu i CMake ===== Aby zbudować kod używający SFML, należy dodać do opcji kompilatora łączenie z odpowiednimi bibliotekami: ''sfml-system'', ''sfml-graphics'', ''sfml-network'', ''sfml-audio'', ''sfml-window''. Ręcznie, z linii poleceń można podać (np. dla programu potrzebującego modułów graphics i network): <html><div style="margin:-1.2em 0 -1.2em 0"/></html> <code bash>c++ prog.cpp -o prog -lsfml-graphics -lsfml-network</code> Dla systemu automatyzacji budowania CMake należy do pliku ''CMakeLists.txt'' dodać: <html><div style="margin:-1.2em 0 -1.2em 0"/></html> <code cmake> find_package(SFML 2.6 COMPONENTS system network REQUIRED) target_link_libraries(prog … sfml-system sfml-network … ) </code> <html><div style="margin:-1.2em 0 -1.2em 0"/></html> SFML __nie__ ustawia zmiennych zawierających listę modułów do linkowania, trzeba je podać ręcznie. <small> Przykład kompletnego pliku ''CMakeLists.txt'': <html><div style="margin:-1.2em 0 0 0"/></html> <code cmake> cmake_minimum_required(VERSION 3.12) project(myProg) set(CMAKE_CXX_STANDARD 20) find_package(SFML 2.6 COMPONENTS graphics system network REQUIRED) add_executable(myProg main.cpp window.h window.cpp) target_link_libraries(myProg sfml-graphics sfml-network) </code> </small> ====== Moduł obsługi sieci ====== Klasy do obsługi sieci są dostępne po dodaniu do kodu dyrektywy: <html><div style="margin:-1.2em 0 0 0"/></html> <code c++>#include <SFML/Network.hpp></code> ===== Adresy IP ===== **Uwaga: SFML obsługuje tylko IPv4** Adres IP jest reprezentowany przez klasę ''[[https://www.sfml-dev.org/documentation/2.6.1/classsf_1_1IpAddress.php#details|sf::IpAddress]]'' \\ Ta klasa ma konstruktor przyjmujący ''std::string'' / ''char*'' akceptujący zapisany jako tekst adres IP **lub nazwę domenową**. \\ <small>Wersja rozwojowa SFML zamiast konstruktora używa metody statycznej ''[[https://github.com/SFML/SFML/blob/master/include/SFML/Network/IpAddress.hpp#L270|sf::IpAddress::resolve]]''</small> ===== Klasy gniazd ===== [[https://www.sfml-dev.org/documentation/2.6.1/classsf_1_1TcpListener.php|TcpListener]] - serwer TCP: * ''listen'' - rozpoczyna nasłuchiwanie; przyjmuje przynajmniej numer portu * ''accept'' - przyjmuje następne połączenie * ''close'' - zamyka gniazdo [[https://www.sfml-dev.org/documentation/2.6.1/classsf_1_1TcpSocket.php|TcpSocket]] - klient TCP: * ''connect'' - nawiązuje połączenie; przyjmuje przynajmniej adres IP i numer portu * ''send'' - wysyła dane * ''receive'' - odbiera dane * ''disconnect'' - rozłącza i zamyka gniazdo [[https://www.sfml-dev.org/documentation/2.6.1/classsf_1_1UdpSocket.php|UdpSocket]] - gniazdo UDP: * ''bind'' - ustala lokalny adres; przyjmuje przynajmniej numer portu * ''send'' - wysyła dane * ''receive'' - odbiera dane ==== Tryb nieblokujący ==== Wszystkie klasy udostępniają metodę ''setBlocking'' pozwalającą przestawić gniazdo w tryb nieblokujący. ==== Konwencje ==== Operacje sieciowe zwracają wartość z wyliczenia ''[[https://www.sfml-dev.org/documentation/2.6.1/classsf_1_1Socket.php#a51bf0fd51057b98a10fbb866246176dc|sf::Socket::Status]]'': * ''Done'' jeżeli wszystko się powiodło, * ''NotReady'' lub ''Partial'' dla odpowiednich wyników działania gniazd w trybie nieblokującym((Choć czasami mimo odebrania części danych funkcje odbierające zwracają ''NotReady'')), * ''Disconnected'' jeśli gniazdo nie jest bądź przestało być połączone((Szczegóły w [[https://github.com/SFML/SFML/blob/2.6.x/src/SFML/Network/Unix/SocketImpl.cpp#L89|implementacji]] biblioteki)), * ''Error'' jeśli wystąpił błąd (inny niż raportowany jako ''Disconnected''). **Uwaga:** operacje takie jak ''accept'', ''send'', ''receive'' też zwracają ''sf::Socket::Status''. \\ Odpowiednio nowe połączenie, ilość wysłanych / odebranych bajtów jest wpisywana do dodatkowego argumentu przekazywanego przez referencję. ==== Zadania ==== ~~Zadanie.#~~ Napisz (wielowątkowo) prostego konsolowego klienta TCP, który jako argumenty przyjmuje docelową nazwę hosta i numer portu. Klient ma jednocześnie odbierać dane z sieci i czytać dane ze standardowego wejścia. ~~Zadanie.#~~ Napisz wielowątkowy serwer czatu. ===== sf::Packet - serializacja wiadomości ===== SFML oferuje klasę ''[[https://www.sfml-dev.org/documentation/2.6.1/classsf_1_1Packet.php|sf::Packet]]'' która reprezentuje logiczną wiadomość, pozwala strumieniowo dodawać dane do wiadomości i współpracuje z metodami ''send'' i ''receive'' gniazd. \\ <small> Dla gniazd TCP wysłanie pakietu używając metody ''send'' wysyła przed danymi ich długość w postaci 4-ro bajtowej liczby w notacji big endian ([[https://github.com/SFML/SFML/blob/2.6.x/src/SFML/Network/TcpSocket.cpp#L323|kod]]). \\ Uwaga: to jest szczegół implementacji bieżącej wersji SFML; API bibliotek nie określa w jaki sposób wiadomości są przesyłane. </small> Przykładowo, do wysłania i odbioru danych można: <html><div style="margin:-1.2em 0 -1.2em 0"/></html> <html><div style="line-height:1.1em"></html> <code C++> enum MessageType : sf::Uint8 { Foo = 1, Bar, Baz }; struct Message { Message(MessageType type) : type(type){}; virtual ~Message() {} const MessageType type; }; struct FooMessage : public Message { FooMessage() : Message(MessageType::Foo){}; sf::String text; sf::Uint32 number; }; (…) // wysłanie wiadomości FooMessage m; sf::Packet packet; packet << m.type << m.text << m.number; socket.send(packet); (…) // odebranie wiadomości sf::Packet packet; socket.receive(packet); // (pominięto obsługę błędów) sf::Uint8 type; packet >> type; switch (type) { case MessageType::Foo: { FooMessage m; packet >> m.text >> m.number; (…) </code> <html></div></html> Dla poprawienia modularności i czytelności kodu można dodać własne operatory: <html><div style="margin:-1.2em 0 0 0"/></html> <html><div style="line-height:1.1em"></html> <code C++> sf::Packet &operator<<(sf::Packet &packet, const FooMessage &msg) { packet << msg.type << msg.text << msg.number; return packet; } sf::Packet &operator>>(sf::Packet &packet, FooMessage &msg) { packet >> msg.text >> msg.number; return packet; } (…) // wysłanie wiadomości FooMessage m; sf::Packet packet; packet << m; socket.send(packet); (…) // odebranie wiadomości sf::Packet packet; socket.receive(packet); // (pominięto obsługę błędów) sf::Uint8 type; packet >> type; switch (type) { case MessageType::Foo: { FooMessage m; packet >> m; (…) </code> <html></div></html> ==== Zadania ==== ~~Zadanie.#~~ Wyjaśnij dlaczego ''operator<<'' zapisuje typ wiadomości, ale ''operator>>'' go nie odczytuje. ~~Zadanie.#~~ Stwórz strukturę którą mógłby używać serwer czatu żeby przesyłać do klientów wiadomość z nazwą użytkownika i treścią wiadomości. Napisz odpowiednie operatory pozwalające wpisać tą strukturę w ''sf::Packet''. <small> ~~Zadanie.#~~ Użyj tej struktury w kodzie klienta i serwera z zadania 1 i 2 </small> ==== sf::Packet i tryb nieblokujący ==== Żeby odebrać ''sf::Packet'' w trybie nieblokującym, należy powtarzać wykonanie funkcji ''receive'' do czasu otrzymania w wyniku statusu ''Done''. \\ Klasa ''sf::TcpSocket'' w prywatnym polu przechowuje częściowe dane przychodzącego pakietu ([[https://github.com/SFML/SFML/blob/2.6.x/include/SFML/Network/TcpSocket.hpp#L219|kod]]). \\ Pakiet podany jako argument jest wypełniany danymi tylko w ostatnim wywołaniu funkcji ''receive''. Podobnie żeby wysłać ''sf::Packet'' w trybie nieblokującym należy podawać jako argument metody ''send'' ten sam pakiet do czasu otrzymania statusu ''Done''. \\ **Uwaga:** to, ile bajtów udało się wysłać zapisywane jest w przekazanym obiekcie z klasy ''sf::Packet'' ([[https://github.com/SFML/SFML/blob/2.6.x/src/SFML/Network/TcpSocket.cpp#L349|kod]]). \\ **Nie wolno tego samego obiektu pakietu wysyłać nieblokująco do wielu gniazd!** ===== Oczekiwanie na zdarzenie na gniazdach ===== SFML wspiera oczekiwanie na przyjście danych na podanej liście gniazd przez obiektowe opakowanie funkcji ''select'' przez klasę ''[[https://www.sfml-dev.org/documentation/2.6.1/classsf_1_1SocketSelector.php|sf::SocketSelector]]''. Uwaga: jeżeli ''SocketSelector'' zwrócił gotowość odczytu na gnieździe, to na tym gnieździe wywołana w trybie blokującym metoda ''receive(void*, std::size_t, std::size_t&)'' się nie zablokuje, ale **metoda ''receive(Packet&)'' dalej może się zablokować**(( ''receive(Packet&)'' wywołuje wewnętrznie ''receive(void*, std::size_t, std::size_t&)'' do czasu odebrania całego pakietu)). SFML nie wspiera czekania na możliwość wysłania kolejnej porcji danych ([[https://github.com/SFML/SFML/blob/2.6.x/src/SFML/Network/SocketSelector.cpp#L168|kod]]). API klasy ''sf::SocketSelector'' definiuje metody: * ''add'' / ''remove'' – dodaje/usuwa gniazdo do/ze zbioru monitorowanych, * ''wait'' – wywołuje systemową funkcję ''select'' dla wcześniej dodanych gniazd; opcjonalnie przyjmuje limit czasu oczekiwania, * ''isReady'' – sprawdza czy podane jako argument gniazdo było gotowe w momencie ostatniego wykonania ''wait''. **Uwaga:** ani zamknięcie ani usunięcie gniazda nie usuwa go z ''SocketSelector''a, trzeba to zrobić ręcznie - inaczej implementacja ''SocketSelector''a nie zauważy że występuje błąd i po prostu nie będzie działać. Przykład użycia klasy ''sf::SocketSelector'' do stworzenia serwera czatu: <html><div style="margin:-1.2em 0 0 0"/></html> <html><div style="line-height:1.1em"></html> <code c++> #include <SFML/Network.hpp> #include <list> int main(int, char **argv) { sf::TcpListener srvSock; std::list<sf::TcpSocket> clients; srvSock.listen(atoi(argv[1])); sf::SocketSelector selector; selector.add(srvSock); while (true) { selector.wait(); if (selector.isReady(srvSock)) { clients.emplace_back(); srvSock.accept(clients.back()); selector.add(clients.back()); } decltype(clients)::iterator curr, next = clients.begin(); while ((curr = next++) != clients.end()) { if (!selector.isReady(*curr)) continue; char data[1024]; size_t recvCnt; if (curr->receive(data, 1024, recvCnt) != sf::Socket::Done) { selector.remove(*curr); clients.erase(curr); continue; } for (auto &c : clients) if (&c != &(*curr)) c.send(data, recvCnt); } } } </code> <html></div></html> ===== HTTP, FTP ===== SFML oferuje też klasy do wysłania żądań HTTP i obsługi FTP, ale nie obsługuje powszechnego dziś dla protokołu HTTP szyfrowania. ===== Przykład z GUI ===== Kod uproszczonego graficznego klienta TCP znajduje się tutaj: {{sfml-tcp-client.tar.xz}}. \\ Na zajęciach nie ma zadań z graficznymi programami w SFML ze względu na dużą objętość kodu który trzeba napisać żeby cokolwiek osiągnąć w tej bibliotece. Powyższy przykład ma 277 linii kodu. Dla porównania daleko bardziej funkcjonalny klient TCP w Qt (z poprzednich zajęć) ma 115 linii kodu i to już okazało się dla części osób zaporową liczbą.
sk2/sfml.1701297444.txt.gz
· ostatnio zmienione: 2023/11/29 23:37 przez
jkonczak
Narzędzia strony
Pokaż stronę
Poprzednie wersje
Odnośniki
Złóż / rozłóż wszystko
Do góry