Narzędzia użytkownika

Narzędzia witryny


Pasek boczny

sk2:sockets_intro

Wstęp – przypomnienia


C++

Większość przykładów w materiałach i programów pisanych na laboratoriach jest w C++.
Stąd przypominam jak obsługiwać kompilator GCC / clang w Linuksie.

Przykładowe polecenie do kompilacji przykładów ze strony:

g++ --std=c++20 -Wall -O0 -g -pthread -o example example.cpp

c++domyślny kompilator C++. Zwykle link do g++ lub clang++
c++ zrodlo.cpp -o progkompiluje plik zrodlo.cpp do programu prog
c++ -Wall z.cpp -o pwłącza wszystkie1) ostrzeżenia kompilatora (-W = warn, all = wszystkie)
c++ -O0 -g z.cpp -o pwyłącza optymalizacje i dodaje do programu dane umożliwiające debugowanie
c++ --std=c++20 z.cpp -o pwłącza używanie standardu ISO C++ z 2020 roku
c++ -pthread z.cpp -o pwłącza obsługę wątków standardu POSIX
wymagane do wersji glibc ≤ 2.33 [1] [2]

Przykładowy plik cmake:

CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(projname)
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Debug")
endif()
set(CMAKE_CXX_STANDARD 20)
find_package(Threads)
link_libraries(Threads::Threads)
add_executable(example1 example1.cpp)
add_executable(example2 example2.cpp)
# target_link_libraries(example2 Threads::Threads)

Obsługa plików w POSIX

Interface obsługi gniazd BSD jest bardzo zbliżony do API specyfikowanego przez POSIX do obsługi plików (który znacie z PSiW).
BSD socket API został przyjęty jako standard przez większość systemów operacyjnych, m. inn. został bez większych zmian wpisany w standard POSIX.

Zadanie 1. Napisz program który otworzy plik o nazwie 'date' i wypisze jego zawartość. Poniżej "pusty" plik źródłowy. Pomiń obsługę błędów. (Jeśli nie wiesz skąd wziąć plik date, wpisz w konsolę: date>date .)
Do obsługi pliku i standardowego wejścia / wyjścia użyj funkcji open, read, write i close.
Opis funkcji znajdziesz na stronach podręcznika systemowego (np. man open).

z1.cpp
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main(int argc, char **argv) {
 
	return 0;
}

Zadanie 2. Co trzeba dodać do obsłużenia błędów?

Adresacja połączeń na warstwie transportu

Zadanie 3. Jakie adresy (numery) są potrzebne by móc przesłać wiadomość do konkretnego programu na wybranym komputerze?

Przypomnienie programów netcat / socat oraz programów netstat / ss (link do materiałów z SK1)

Zadanie 4. Prześlij między sobą dowolny tekst używając programu netcat lub socat

Zadanie 5. Nawiąż połączenie na port 13 (daytime) twojego komputera.
Jeżeli używasz własnego komputera i port 13 nie odpowiada, uruchom i pozostaw działającą w osobnym terminalu komendę
socat tcp-listen:1313,fork,reuseaddr exec:date   i do tego oraz dalszych ćwiczeń używaj port 1313.

Więcej informacji o "useful debugging and measurement tools": echo, discard, chargen, qotd, daytime

Sockets

BSD sockets

Gniazdo (socket) – interfejs między systemem operacyjnym a programem użytkownika używany do dwukierunkowej komunikacji poza program.
Porównaj z: FIFO (named pipe, nazwany potok; było na SOP i PSiW)

API stworzone dla systemu BSD zostało przyjęte przez praktycznie wszystkie systemy operacyjne (POSIX socket API, WinSock).

Pełen opis: http://www.cs.put.poznan.pl/ddwornikowski/sieci/sieci2/bsdsockets.html oraz https://en.wikipedia.org/wiki/Berkeley_sockets

man 7 socket tcp udp
Ekstra: man 7 pipe – przypomnienie potoków; man 7 unix – unix sockets – wykorzystanie gniazd do komunikacji międzyprocesowej

[ekstra] Kolejność bajtów (i bitów)

W jakiej kolejności są wysyłane bajty danych i dlaczego to jest ważne.

Przykład wysłania liczby 0x010F, czyli 271, czyli 0b0000000100001111

byte order\bit orderMSB firstLSB first
little endian00001111000000011111000010000000 x86/x86_64
ARM, IA-64 (Itanium), SPARC≥v9, RISC-V
big endian00000001000011111000000011110000Motorola 68k, SPARC<v9, z/Arch
I²C, SDH Ethernet, RS232, USB

MSB/LSB first – to głównie problem producentów sprzętu. Little/big endian - to problem programistów.
https://en.wikipedia.org/wiki/Most_significant_bit https://pl.wikipedia.org/wiki/Kolejność_bajtów

Sieciowa kolejność bajtów (networking byte order) to BE – odwrotnie niż na x86 https://en.wikipedia.org/wiki/Endianness#Networking

uint16_t hostEndianessPort = 13;
uint16_t networkEndianessPort = htons(hostEndianessPort);

Opis funkcji pomocniczych – patrz man byteorder lub strona Darka Dwornikowskiego

Zapis adresu gniazda

Protokół trzeba podać na etapie tworzenia gniazda. Adres IP oraz numer portu (lub ścieżkę dla gniazd unix) trzeba podać w odpowiedniej strukturze.

Do przekazywania adresu gniazda funkcje wymagają wskaźnika na strukturę sockaddr (w C – struct sockaddr). Ta struktura nie pozwala na bezpośrednie wykorzystanie. Zamiast tego dla IPv4 należy używać sockaddr_in (w C – struct sockaddr_in)

Opis struktury – patrz man 7 ip i man netinet_in.h lub strona Darka

C nie pozwala na dziedziczenie, więc zamiast tego (https://en.wikipedia.org/wiki/Type_punning#Sockets_example) struktura sockaddr ma kilka "specjalizacji" dla konkretnej rodziny adresów:

  • sockaddr_in (INET, czyli IPv4)
  • sockaddr_in6 (INET6, czyli IPv6)
  • sockaddr_un (dla unix socket).

"Hello world"

Zadanie 6. Zmień program do odczytu pliku tak, by zamiast funkcji open(…):

  • tworzył funkcją socket(…) gniazdo:
    • domeny komunikacyjnej protokołu IPv4 – stała PF_INET (zamiennie AF_INET)
    • typu strumieniowego – stała SOCK_STREAM (czyli TCP)
    • protokołu TCP – stała IPPROTO_TCP (można też podać 0, oznaczające domyślny protokół)
  • wypełniał strukturę sockaddr_in (zdefiniowaną w pliku nagłówkowym netinet/in.h):
    • rodzina adresów IPv4 – stała AF_INET
    • port – 13 (port usługi daytime; pamiętaj o porządku bajtów - htons)
    • adres IP – 127.0.0.1 (localhost, do konwersji użyj inet_addr lub inet_aton);
      można też ustawić adres na stałą htonl(INADDR_LOOPBACK).
      Uwaga: .sin_addr to struktura typu in_addr z jedną składową .s_addr typu uint32_t
      inet_aton przyjmuje wskaźnik na strukturę, natomiast inet_addr zwraca liczbę którą trzeba przypisać składowej .s_addr:
      sockaddr_in nazwa_zmiennej;
      wersja 1: nazwa_zmiennej.sin_addr.s_addr = inet_addr("8.8.8.8");
      wersja 2: inet_aton("8.8.8.8", &nazwa_zmiennej.sin_addr);

  • łączył się z użyciem powyższej struktury – funkcja connect(…)
    • funkcja connect oczekuje sockaddr* zamiast sockaddr_in* – wymagane rzutownaie
    • trzeci argument funkcji connect to rozmiar struktury opisującej adres – sizeof() zmiennej lub typu
  • przed wywołaniem funkcji close() zamykał połączenie funkcją shutdown();
    w funkcji shutdown() można osobno zakończyć nadawanie i odbiór, drugi argument określa co zakończyć – SHUT_RD / SHUT_WR / SHUT_RDWR

Opis funkcji – strona Darka lub man 3 … / man 3p …
Potrzebne pliki nagłówkowe to:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

Zadanie 7. Przekształć poprzedni program tak, by czytał z adresu IP i portu podanego w argumentach programu.

Zadanie 8. Dodaj do programu obsługę błędów zwracanych przez funkcje connect i read.

Zadanie 9. Zmień IP na losowe (tak, by nie odpowiadało na próbę połączenia). Programem netstat -tnp / ss -tnp wyświetl utworzone połączenie.

Zadanie 10. Zmień program tak, by zamiast read(…) używał funkcji recv(…)

Zadanie 11. Przed wywołaniem connect ustal lokalny adres funkcją bind. Powtórz zadanie 8.

1) "Wszystkie" oznacza wybrany zbiór ostrzeżeń o nazwie wszystkie, poza -Wall warto też dodać -Wextra i rozważyć dodanie -pedantic. Szczegóły w dokumentacji kompilatora gcc/clang
sk2/sockets_intro.txt · ostatnio zmienione: 2023/10/10 16:54 przez jkonczak