====== Klient TCP (przypomnienie) ====== Do pracy programu w roli klienta TCP trzeba w kolejności użyć funkcji:
- ''socket'' do stworzenia gniazda, podając typ ''SOCK_STREAM'' i protokół ''IPPROTO_TCP'' lub ''0'' - ''connect'' żeby połączyć utworzone gniazdo pod adres wskazany w argumentach - po połączeniu można wysyłać i odbierać dane, np. funkcjami ''write'', ''send'' i ''read'', ''recv'' - ''shutdown'' żeby zakończyć połączenie na tym gnieździe - ''close'' żeby zwolnić zasoby systemowe związane z gniazdem Zwróć uwagę że ''socket'' tworzy gniazdo protokołu TCP, a ''connect'' wybiera żeby to gniazdo pełniło rolę klienta. ====== Serwer TCP ====== ===== Wstęp ===== Aby oczekiwać na przychodzące połączenia TCP konieczne jest stworzenie gniazda do odbierania nowych połączeń. \\ Takie gniazdo, tzw. **gniazdo nasłuchujące** (listening), nie umożliwia odbierania ani wysyłania danych – **pozwala tylko na odbieranie przychodzących połączeń** i tworzy **dla każdego nowego połączenia kolejne gniazdo** reprezentujące to odebrane połączenie. Do przygotowania nasłuchującego gniazda należy najpierw stworzyć gniazdo funkcją ''socket'', a następnie wywołać na tym gnieździe funkcję ''listen'', ale: jeśli wcześniej nie ustawi się adresu gniazda, system operacyjny wylosuje efemeryczny port i rozpocznie nasłuch na dowolnym adresie IP na tym porcie. Dlatego przed wywołaniem funkcji ''listen(…)'' **należy funkcją ''bind'' ustawić lokalny adres gniazda**. Funkcja ''bind'' przyjmuje jako argument wskaźnik na strukturę ''sockaddr'', który musi wskazywać na strukturę opisująca adres z rodziny obsługiwanej przez gniazdo. **Lokalny adres IP nasłuchującego gniazda zwykle ustawia się na dowolny**, czyli dla IPv4 adres 0.0.0.0,** reprezentowany przez stałą ''INADDR_ANY''**. \\ Inny lokalny adres IP ustawia się, jeśli gniazdo ma odbierać połączenia kierowane tylko na ten właśnie adres – np. 127.0.0.1 (''INADDR_LOOPBACK'') jeśli połączenia mają być ograniczone do połączeń z tego komputera. **Wywołanie funkcji ''listen'' nakazuje systemowi operacyjnemu czekać na połączenia** (wybiera że gniazdo ma pełnić rolę serwera). Wykonanie funkcji listen jest natychmiastowe – ta funkcja nie czeka na połączenie, tylko informuje system operacyjny że nowe połączenia przychodzące na ustawiony adres mają być kierowane na to gniazdo. \\ Argumentem funkcji ''listen'' jest ilość nowych połączeń które czekają w kolejce na odebranie (tj. połączeń dla których nie wykonano jeszcze funkcji ''accept'')((Argument "backlog" funkcji ''listen'' powinien się zawierać w zakresie ''1''÷''SOMAXCONN'' (w tej chwili o wartości [[https://elixir.bootlin.com/linux/v6.17.1/source/include/linux/socket.h#L298|4096 w Linuksie]]) i jest traktowany jako podpowiedź, tzn. system operacyjny może używać innego limitu niż podany w argumencie ''listen''.)). Do odebrania nowych połączeń używa się funkcji ''accept(…)''. **Funkcja ''accept'' zwraca nowe gniazdo** reprezentujące nawiązane połączenie. Tworzenie kolejnych deskryptorów plików oraz wyniki ''lsof'', ''strace'' i ''ss'' są zaprezentowane [[sk2:sockets_full:tcp_srv_img|tutaj]] ===== Zadania ===== ~~Zadanie.#~~ Napisz program, który: - stworzy gniazdo TCP funkcją ''socket'', - stworzy zmienną typu ''sockaddr_in'' i wypełni ją wpisując: * ''AF_INET'' w pole określające rodzinę adresów * stałą ''INADDR_ANY'' w pole określające adres IPv4 * wybrany numer portu na którym serwer ma czekać na połączenia w pole określające port \\ (pamiętaj o ''htons'' do zmiany kolejności bajtów) - ustali lokalny adres gniazda funkcją ''bind'' \\ //uwaga//: koniecznie sprawdzaj czy funkcja ''bind'' się powiodła \\ jeśli zobaczysz błąd //address already in use//, przeczytaj tekst o stanie TIME_WAIT pod zadaniem - rozpocznie oczekiwanie na połączenia wywołując funkcję ''listen'' \\ drugi argument ''listen'' określa ile nieodebranych połączeń może naraz czekać w systemie; tutaj wystarczy ''1'' - zaakceptuje połączenie funkcją ''accept'' \\ na razie drugi i trzeci argument funkcji ''accept'' ustaw na ''0'' lub ''nullptr'' - wynik funkcji ''accept'' to deskryptor nowego gniazda, połączonego z klientem \\ (w tym momencie możesz już zamknąć gniazdo nasłuchujące na połączenia) - wyśle do gniazda połączonego z klientem stały ciąg znaków (np. ''hello'') - zakończy połączenie z klientem funkcją ''shutdown'' i zamknie deskryptor pliku funkcją ''close''
#include
#include
#include
#include
#include
int main(int argc, char **argv) {
return 0;
}
const int one = 1;
setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
Oczywiście trzeba zmienić to zachowanie przed wywołaniem funkcji ''bind''
której działania ta opcja dotyczy.
===== Zadania =====
~~Zadanie.#~~ Zmodyfikuj program z poprzedniego zadania tak, by ustawiał opcję
''SO_REUSEADDR''.
\\
Sprawdź czy teraz możesz uruchomić serwer natychmiast po zakończeniu jego
poprzedniego uruchomienia.
~~Zadanie.#~~ Zmodyfikuj program z poprzedniego zadania tak, by w pętli
obsługiwał kolejne połączenia.
===== Odczytywanie informacji skąd przyszło połączenie =====
Funkcja ''accept'' może przekazać informację o adresie z którego nawiązano
połączenie. W tym celu należy jej podać:
* jako drugi argument informację gdzie ma zapisać ten adres – tj. podać adres struktury którą ta funkcja ma wypełnić \\ ''accept'' oczekuje typu ''sockaddr*'', ale wskazuje mu się zmienną typu spodziewanego adresu (np. ''sockaddr_in'')
* trzeci argument to ilość bajtów struktury z adresem, która jest **czytana i nadpisywana** przez ''accept'' \\ czyli: trzeba podać adres zmiennej, która w momencie wywołania ''accept'' ma wpisany rozmiar przekazanej struktury, \\ a po wyjściu z funkcji ''accept'' będzie mieć informację ile bajtów zostało tam wpisane przez ''accept''
sockaddr_in cliAddr;
socklen_t cliAddrSize = sizeof(cliAddr);
client = accept(ssock, (sockaddr*)&cliAddr, &cliAddrSize);
#include
#include
#include
#include
#include
#include
#define CHECK(code, txt) if(code==-1) {perror(txt); return 1;}
enum ttt:char{e='.',X='x',O='o'} map[3][4]{{e,e,e},{e,e,e},{e,e,e}};
#define MAP_TO_BUF sprintf(buf, "\n 012\n0%s\n1%s\n2%s\n\n",\
(char*)map[0],(char*)map[1],(char*)map[2]);
int main(){
int s = socket(AF_INET, SOCK_STREAM, 0), one = 1; CHECK(s, "socket");
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
sockaddr_in saddr{AF_INET, htons(3003), {htonl(INADDR_ANY)}};
int rv = bind(s, (sockaddr*)&saddr, sizeof(saddr)); CHECK(rv, "bind");
rv = listen(s, 1); CHECK(rv, "listen");
int c1 = accept(s, nullptr, nullptr); CHECK(c1, "accept c1");
write(c1, "Poczekaj na drugiego gracza\n", 28);
int c2 = accept(s, nullptr, nullptr); CHECK(c2, "accept c2");
close(s);
for(;;std::swap(c1,c2)){
write(c2, "Ruch przeciwnika.\n", 18);
write(c1, "Podaj x i y oddzielone spacją (zakres 0-2)\n", 44);
char buf[256]{}; unsigned x=3, y=3;
read(c1, buf, 255);
sscanf(buf, "%u%u", &x, &y);
if (x<3&&y<3 && map[y][x]==e) map[y][x]=c1
++++