Sockety TCP/IP - podstawy. Sieci Komputerowe II Wyk ład 2

Podobne dokumenty
Gniazda BSD. komunikacja bezpołączeniowa

Iteracyjny serwer TCP i aplikacja UDP

Literatura uzupełniająca: W. Richard Stevens, Programowanie zastosowań sieciowych w systemie Unix WNT 1998

Krótkie wprowadzenie do korzystania z OpenSSL

3. Identyfikacja. SKŁADNIA #include <sys/socket.h> int getpeername(int socket, struct sockaddr *addr, int *addrlen);

ZESZYTY ETI ZESPOŁU SZKÓŁ W TARNOBRZEGU Nr 1 Seria: Teleinformatyka 2013

Aplikacja Sieciowa. Najpierw tworzymy nowy projekt, tym razem pracować będziemy w konsoli, a zatem: File->New- >Project

Gniazda BSD implementacja w C#

2. Interfejs gniazd Gniazdo

Programowanie przy użyciu gniazdek

TCP - receive buffer (queue), send buffer (queue)

Aplikacja Sieciowa wątki po stronie klienta

Podstawowe typy serwerów

Oprogramowanie komunikacyjne dla Internetu rzeczy Laboratorium nr 4 komunikacja unicastowa IPv6

SUMA KONTROLNA (icmp_cksum) NUMER KOLEJNY (icmp_seq)

Architektura typu klient serwer: przesyłanie pliku tekstowo oraz logowania do serwera za pomocą szyfrowanego hasła

Programowanie Sieciowe 1

Pobieranie argumentów wiersza polecenia

Gniazda BSD. Procesy w środowisku sieciowym. Gniazda podstawowe funkcje dla serwera. Gniazda podstawowe funkcje dla klienta

Architektura typu klient serwer: uproszczony klient POP3

Tworzenie aplikacji rozproszonej w Sun RPC

Programowanie sieciowe

Gniazda. S. Samolej: Gniazda 1

Instytut Teleinformatyki

Gniazda UDP. Bartłomiej Świercz. Łódź, 3 kwietnia Katedra Mikroelektroniki i Technik Informatycznych. Bartłomiej Świercz Gniazda UDP

Komunikacja międzyprocesowa. Krzysztof Banaś Systemy rozproszone 1

PROGRAMOWANIE SYSTEMÓW CZASU RZECZYWISTEGO

Pliki w C/C++ Przykłady na podstawie materiałów dr T. Jeleniewskiego

Klient-Serwer Komunikacja przy pomocy gniazd

1.1 Przykład znajdowanie liczb pierwszych leżących w zadanym zakresie, tryb bezpołączeniowy

Komunikacja sieciowa - interfejs gniazd

Biblioteka standardowa - operacje wejścia/wyjścia

PROGRAMOWANIE SYSTEMÓW CZASU RZECZYWISTEGO

1. Model klient-serwer

RPC. Zdalne wywoływanie procedur (ang. Remote Procedure Calls )

Programowanie aplikacji równoległych i rozproszonych. Wykład 4

Sieci komputerowe. Wykład 7: Transport: protokół TCP. Marcin Bieńkowski. Instytut Informatyki Uniwersytet Wrocławski

Winsock. Sieci Komputerowe II Wyk ład 3

Obsługa plików. Systemy Operacyjne 2 laboratorium. Mateusz Hołenko. 25 września 2011

Gniazda - Wstęp. Oprogramowanie systemów równoległych i rozproszonych. Sposób komunikacji. Domena adresowa. olas@icis.pcz.pl

Część 4 życie programu

dynamiczny przydział pamięci calloc() memset() memcpy( ) (wskaźniki!! )

Laboratorium Systemów Operacyjnych. Ćwiczenie 4. Operacje na plikach

Architektury systemów rozproszonych LABORATORIUM. Ćwiczenie 1

Model OSI/ISO. Komputer B. Warstwy w modelu OSI aplikacji. aplikacji. prezentacji Komputer A. prezentacji. sesji. sesji. komunikacja wirtualna

Gniazda surowe. Bartłomiej Świercz. Łódź,9maja2006. Katedra Mikroelektroniki i Technik Informatycznych. Bartłomiej Świercz Gniazda surowe

Transport. część 2: protokół TCP. Sieci komputerowe. Wykład 6. Marcin Bieńkowski

Instrukcja do laboratorium Systemów Operacyjnych. (semestr drugi)

Wstęp do Programowania, laboratorium 02

iseries Programowanie z użyciem gniazd

Programowanie Proceduralne

Funkcje zawarte w bibliotece < io.h >

Ćwiczenie nr 6. Poprawne deklaracje takich zmiennych tekstowych mogą wyglądać tak:

Komputery i Systemy Równoległe Jędrzej Ułasiewicz 1

Uwagi dotyczące notacji kodu! Moduły. Struktura modułu. Procedury. Opcje modułu (niektóre)

Pliki. Informacje ogólne. Obsługa plików w języku C

Przykład aplikacji UDP

W różnych systemach definicje mogą się różnić od powyższej. W linuxie sprobuj man 7 ip do wyswietlenia opisu.

Funkcje zawarte w bibliotece < io.h >

Transport. część 2: protokół TCP. Sieci komputerowe. Wykład 6. Marcin Bieńkowski

Procesy. Systemy Operacyjne 2 laboratorium. Mateusz Hołenko. 9 października 2011

socket(int domain, int type, int protocol)

JĘZYK PYTHON - NARZĘDZIE DLA KAŻDEGO NAUKOWCA. Marcin Lewandowski [ mlew@ippt.gov.pl ]

Zadanie 2: transakcyjny protokół SKJ (2015)

Programowanie proceduralne INP001210WL rok akademicki 2018/19 semestr letni. Wykład 6. Karol Tarnowski A-1 p.

utworz tworzącą w pamięci dynamicznej tablicę dwuwymiarową liczb rzeczywistych, a następnie zerującą jej wszystkie elementy,

Katedra Elektrotechniki Teoretycznej i Informatyki. wykład 9 - sem.iii. Dr inż. M. Czyżak

Języki i metodyka programowania. Wprowadzenie do języka C

Katedra Elektrotechniki Teoretycznej i Informatyki. wykład 7- sem.iii. M. Czyżak

ISO/ANSI C - funkcje. Funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje

I - Microsoft Visual Studio C++

Łącza nienazwane(potoki) Łącza nienazwane mogą być używane tylko pomiędzy procesami ze sobą powiązanymi.

Wstęp do programowania INP001213Wcl rok akademicki 2017/18 semestr zimowy. Wykład 12. Karol Tarnowski A-1 p.

Informacje które należy zebrać przed rozpoczęciem instalacji RelayFax.

Ćwiczenie 4. Obsługa plików. Laboratorium Podstaw Informatyki. Kierunek Elektrotechnika. Laboratorium Podstaw Informatyki Strona 1.

Politechnika Łódzka. Instytut Systemów Inżynierii Elektrycznej

Sieciowa komunikacja procesów - XDR i RPC

Ćwiczenia 2 IBM DB2 Data Studio

Ghost in the machine

Komunikacja międzyprocesowa. Krzysztof Banaś Systemy rozproszone 1

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

PROGRAMOWANIE SYSTEMÓW CZASU RZECZYWISTEGO

1. Utwórz blok pamięci współdzielonej korzystając z poniższego kodu:

Programowanie proceduralne INP001210WL rok akademicki 2015/16 semestr letni. Wykład 6. Karol Tarnowski A-1 p.

Wskaźniki. Przemysław Gawroński D-10, p marca Wykład 2. (Wykład 2) Wskaźniki 8 marca / 17

Tunelowanie, kapsułkowanie, XDR. 1. Transmisja tunelowa i kapsułkowanie serwery proxy. 2. Zewnętrzna reprezentacja danych XDR.

Tomasz Greszata - Koszalin

Laboratorium 6: Ciągi znaków. mgr inż. Leszek Ciopiński dr inż. Arkadiusz Chrobot dr inż. Grzegorz Łukawski

Struktury. Przykład W8_1

Bezpieczeństwo systemów informatycznych

Laboratorium 6: Dynamiczny przydział pamięci. dr inż. Arkadiusz Chrobot dr inż. Grzegorz Łukawski

Tryb bezpołączeniowy (datagramowy)

Obsługa plików Procesy

Języki i metodyka programowania. Typy, operatory, wyrażenia. Wejście i wyjście.

Tworzenie maszyny wirtualnej

Programowanie sieciowe

Uzupełnienie dot. przekazywania argumentów

Struktura programu. Projekty złożone składają się zwykłe z różnych plików. Zawartość każdego pliku programista wyznacza zgodnie z jego przeznaczeniem.

// Potrzebne do memset oraz memcpy, czyli kopiowania bloków

Transkrypt:

Sockety TCP/IP - podstawy Sieci Komputerowe II Wyk ład 2

Plan Klient IPv4 Serwer IPv4

Pierwszy program Aplikacja w architekturze klient-serwer Realizacja protokołu echo Zasada działania: klient łączy się z serwerem i wysyła na serwer pewne dane w odpowiedzi serwer wysyła z powrotem do klienta to co od niego otrzymał i się rozłącza Dane wysyłane do serwera są argumentem (łańcuchem znaków) wywołania klienta podawanym w linii komend Zastosowania cele testowe i diagnostyczne

Klient TCP IPv4 Zadanie zainicjowanie komunikacji z biernie oczekującym serwerem Typowy schemat komunikacji klienta: utworzenie socketu TCP socket() nawiązanie połączenia z serwerem connect() komunikacja z serwerem send(), recv() zamknięcie połączenia close()

Implementacja: Komunikaty o błędach #include <stdio.h> #include <stdlib.h> void DieWithUserMessage(const char *msg, const char *detail) { fputs(msg, stderr); fputs(": ", stderr); fputs(detail, stderr); fputc('\n', stderr); exit(1); } void DieWithSystemMessage(const char *msg) { perror(msg); exit(1); } Dwa polecenia obsługi błędów obydwa wysyłają na strumień stderr komunikat użytkownika msg, po którym następuje szczegółowy opis błędu na koniec wykonywana jest funkcja exit() powodująca zakończenie programu różnica między tymi poleceniami polega na źródle opisu szczegółowego, w pierwszym przypadku pochodzi on od twórcy programu, w drugim z systemu w oparciu o wartość zmiennej systemowej errno

Implementacja: echo klient (1) #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include "Practical.h" // plik nagłówkowy dla komunikatów o błędach Ustawienie plików include: należy sprawdzić w dokumentacji jakie pliki include należy uwzględnić dla funkcji socketów oraz struktur danych zamiast definiować osobny plik z komunikatami o błędach można te dwie funkcje dołączyć do programu

Implementacja: echo klient (2) int main(int argc, char *argv[]) { if (argc < 3 argc > 4) // Test for correct number of arguments DieWithUserMessage("Parameter(s)", "<Server Address> <Echo Word> [<Server Port>]"); char *servip = argv[1]; // First arg: server IP address (dotted quad) char *echostring = argv[2]; // Second arg: string to echo // Third arg (optional): server port (numeric). 7 is well-known echo port in_port_t servport = (argc == 4)? atoi(argv[3]) : 7; Parsing i sprawdzenie logiczności parametrów: pierwszy parametr adres IPv4 serwera drugi parametr łańcuch znakowy wysyłany na echo trzeci parametr (opcjonalny) numer portu serwera (domyślnie 7 standardowy port dla usługi echo)

Implementacja: echo klient (3) // Create a reliable, stream socket using TCP int sock = socket(af_inet, SOCK_STREAM, IPPROTO_TCP); if (sock < 0) DieWithSystemMessage("socket() failed"); Tworzenie socketu TCP: przy użyciu funkcji socket() parametry: adres IPv4 (AF_INET), protokół strumieniowy (SOCK_STREAM), TCP (IPPROTO_TCP) funkcja socket() zwraca deskryptor numeryczny, jeśli tworzenie zakończyło się sukcesem, lub wartość -1 w przeciwnym przypadku i wówczas pojawia się komunikat o błędzie użytkownika i następuje wyjście z programu

Implementacja: echo klient (4) // Construct the server address structure struct sockaddr_in servaddr; // Server address memset(&servaddr, 0, sizeof(servaddr)); // Zero out structure Przygotowanie struktury sockaddr_in przechowującej adres i port serwera Funkcja memset() używana w celu zapewnienia, że wszystkie niepodane wprost wartości składowych struktury są równe 0

Implementacja: echo klient (5) servaddr.sin_family = AF_INET; // IPv4 address family // Convert address int rtnval = inet_pton(af_inet, servip, &servaddr.sin_addr.s_addr); if (rtnval == 0) DieWithUserMessage("inet_pton() failed", "invalid address string"); else if (rtnval < 0) DieWithSystemMessage("inet_pton() failed"); servaddr.sin_port = htons(servport); // Server port Wypełnienie struktury sockaddr_in: należy ustawić następujące wartości: rodzina adresów (AF_INET), adres IP, numer portu funkcja inet_pton() zamienia adres IP z postaci kropkowej na postać liczby 32 bitowej funkcja htons() host to network short zapewnia, że binarna wartość reprezentująca numer portu jest sformatowana w postaci wymaganej przez API

Implementacja: echo klient (6) // Establish the connection to the echo server if (connect(sock, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) DieWithSystemMessage("connect() failed"); Łączenie: funkcja connect() ustanawia połączenie między tworzonym socketem, a tym określonym przez adres i numer portu przekazanym w strukturze sockaddr_in konieczne jest rzutowanie struktury sockaddr_in (dla adresu IPv4) na typ sockaddr (ogólny), dla którego należy podać aktualny rozmiar struktury danych

Implementacja: echo klient (7) size_t echostringlen = strlen(echostring); // Determine input length // Send the string to the server ssize_t numbytes = send(sock, echostring, echostringlen, 0); if (numbytes < 0) DieWithSystemMessage("send() failed"); else if (numbytes!= echostringlen) DieWithUserMessage("send()", "sent unexpected number of bytes"); Wysyłanie łańcucha znakowego do usługi echo serwera: na początku zapamiętywana jest długość wysyłanego łańcucha znaków do funkcji send() przekazywany jest wskaźnik do wysyłanego łańcucha znaków oraz jego długość funkcja send() zwraca liczbę wysłanych bajtów przy poprawnym wykonaniu i liczbę -1 w przeciwnym przypadku na koniec następuje obsługa błędów

Implementacja: echo klient (8) Odbiór odpowiedzi z usługi echo serwera: protokół TCP jest protokołem bajtowo-strumieniowym bajty wysłane jednym wywołaniem funkcji send() po jednej stronie połączenia niekoniecznie muszą być zwrócone podczas jednego wywołania funkcji recv() po drugiej stronie w konsekwencji dane muszą być odbierane w pętli najprawdopodobniej wszystkie wysłane bajty zostaną zwrócone naraz, ale nie ma na to gwarancji Podstawowa zasada pisania programów używających socketów brzmi: Nigdy nie możesz zakładać czegokolwiek o tym co sieć i program po drugiej stronie mają zamiar zrobić

Implementacja: echo klient (9) // Receive the same string back from the server unsigned int totalbytesrcvd = 0; // Count of total bytes received fputs("received: ", stdout); // Setup to print the echoed string while (totalbytesrcvd < echostringlen) { char buffer[bufsize]; // I/O buffer /* Receive up to the buffer size (minus 1 to leave space for a null terminator) bytes from the sender */ numbytes = recv(sock, buffer, BUFSIZE - 1, 0); if (numbytes < 0) DieWithSystemMessage("recv() failed"); else if (numbytes == 0) DieWithUserMessage("recv()", "connection closed prematurely"); totalbytesrcvd += numbytes; // Keep tally of total bytes buffer[numbytes] = '\0'; // Terminate the string! fputs(buffer, stdout); // Print the echo buffer } wyzerowanie licznika odebranych bajtów i przygotowanie standardowego wyjścia do wypisania odebranych bajtów odbieranie bajtów funkcją recv() trwa tak długo jak dostępne są dane, funkcja ta zwraca liczbę bajtów skopiowanych do bufora lub -1 w przypadku błędu, a 0 oznacza, że aplikacja po drugiej stronie zamknęła połączenie TCP (parametr oznaczający rozmiar rezerwuje jeden bajt na znak końca łańcucha wypisywanie zawartości bufora: po każdej partii odebranych bajtów na końcu łańcucha dodawany jest znak null, dzięki czemu można je wypisać na ekranie funkcją fputs(), zawartość odebranych bajtów nie jest sprawdzana pod względem zgodności z wysłanymi bajtami.

Implementacja: echo klient (10) fputc('\n', stdout); // Print a final linefeed } close(sock); exit(0); Zakończenie połączenia i programu: przejście do nowej linii funkcja close() informuje zdalny socket o zakończeniu komunikacji, a następnie powoduje dezalokację lokalnych zasobów socketu.

Program klienta uruchomienie % TCPEchoClient4 169.1.1.1 "Echo this!" Received: Echo this! Uwaga program do poprawnego działania wymaga drugiego programu, czyli serwera.

Serwer TCP IPv4 Zadania serwera: ustanowienie punktu końcowego dla komunikacji bierne oczekiwanie na połączenie klienta komunikacja serwera z klientem: odbieranie danych wysłanych przez klienta i odesłanie ich z powrotem do klienta czynności te są powtarzane aż do zamknięcia połączenia przez klienta, co oznacza koniec komunikacji

Podstawowe kroki dla serwera TCP 1. Utworzenie socketu TCP socket() 2. Przydzielenie socketowi numeru portu bind() 3. Zakomunikowanie systemowi, że ma umożliwić połączenia do tego portu listen() 4. Powtarzanie w pętli następujących czynności: tworzenie nowego socketu dla każdego połączenia z klientem accept() komunikacja z klientem send() i recv() zamykanie połączenia z klientem close() Tworzenie socketu, wysyłanie i odbieranie danych oraz zamykanie połączenia wygląda tak samo jak w przypadku klienta

Implementacja: echo serwer (1) #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include "Practical.h" static const int MAXPENDING = 5; // Maximum outstanding connection requests Ustawienie plików include oraz stałej oznaczającej maksymalną liczbę obsługiwanych połączeń

Implementacja: echo serwer (2) int main(int argc, char *argv[]) { if (argc!= 2) // Test for correct number of arguments DieWithUserMessage("Parameter(s)", "<Server Port>"); in_port_t servport = atoi(argv[1]); // First arg: local port Parsing parametrów wywołania programu: atoi() konwertuje łańcuch znaków na liczbę całkowitą (numer portu) jeśli argument nie jest liczbą to atoi() zwróci wartość 0, która spowoduje błąd wykonania funkcji bind() w dalszej części programu

Implementacja: echo serwer (3) // Create socket for incoming connections int servsock; // Socket descriptor for server if ((servsock = socket(af_inet, SOCK_STREAM, IPPROTO_TCP)) < 0) DieWithSystemMessage("socket() failed"); Tworzenie socketu TCP: odbywa się tak samo jak w przypadku klienta

Implementacja: echo serwer (4) // Construct local address structure struct sockaddr_in servaddr; // Local address memset(&servaddr, 0, sizeof(servaddr)); // Zero out structure servaddr.sin_family = AF_INET; // IPv4 address family servaddr.sin_addr.s_addr = htonl(inaddr_any); // Any incoming interface servaddr.sin_port = htons(servport); // Local port Wypełnienie pól struktury z informacjami o adresie serwera: struktura sockaddr_in (IPv4) ponieważ adres IP nie ma większego znaczenia to można posłużyć się dowolnym przypisanym maszynie, na której działa serwer INADDR_ANY funkcje htonl() oraz htons() używane do konwersji adresu i numeru portu na właściwą postać

Implementacja: echo serwer (5) // Bind to the local address if (bind(servsock, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0) DieWithSystemMessage("bind() failed"); Powiązanie socketu z numerem portu i adresem IP: wiązanie socketu z lokalnym adresem i numerem portu bind() klient podaje adres serwera (connect), a serwer podaje swój własny adres (bind) wykonanie funkcji bind() może zakończyć się niepowodzeniem z różnych powodów np.: inny socket korzysta już z tego samego portu wymagane są specjalne uprawnienia do powiązania z portem (szczególnie dla portów o numerach mniejszych od 1024

Implementacja: echo serwer (6) // Mark the socket so it will listen for incoming connections if (listen(servsock, MAXPENDING) < 0) DieWithSystemMessage("listen() failed"); Przełączenie socketu w tryb nasłuchiwania: listen() przed wykonaniem listen() wszystkie przychodzące połączenia są odrzucane connect() po stronie klienta kończy się błędem

Implementacja: echo serwer (7) for (;;) { // Run forever struct sockaddr_in clntaddr; // Client address HandleTCPClient(clntSock); } Pętla, w której następuje iteracyjna obsługa przychodzących połączeń

Implementacja: echo serwer (8) struct sockaddr_in clntaddr; // Client address // Set length of client address structure (in-out parameter) socklen_t clntaddrlen = sizeof(clntaddr); // Wait for a client to connect int clntsock = accept(servsock, (struct sockaddr *) &clntaddr, &clntaddrlen); if (clntsock < 0) DieWithSystemMessage("accept() failed"); Akceptowanie przychodzących połączeń: zamiast wysyłania i odbierania danych, w taki sposób, jak w aplikacji klienta, stosowana jest funkcja accept(), która buforuje dane przez cały czas połączenia z nasłuchującycm portem accept() zwraca deskryptor nowego socketu, który został użyty do połączenia ze zdalnym socketem, drugi argument jest wskaźnikiem do struktury sockaddr_in, a trzeci wskazuje długość adresu po pozytywnym wykonaniu funkcji accept() struktura sockaddr_in zawiera adres IP oraz numer portu socketu, który nawiązał połączenie

Implementacja: echo serwer (9) // clntsock is connected to a client! char clntname[inet_addrstrlen]; // String to contain client address if (inet_ntop(af_inet, &clntaddr.sin_addr.s_addr, clntname, sizeof(clntname))!= NULL) printf("handling client %s/%d\n", clntname, ntohs(clntaddr.sin_port)); else puts("unable to get client address"); Wypisanie informacji o połączonym kliencie: cintaddr zawiera adres i numer portu połączonego klienta binarną formę adresu klienta trzeba zamienić na postać w zapisie kropkowym inet_ntop() przed wypisaniem numeru portu należy go przekonwertować na właściwą postać ntohs()

Implementacja: echo serwer (10) HandleTCPClient(clntSock); Obsługa właściwej usługi (protokołu) aplikacji w tym przypadku echo Zaleca się oddzielić tę część programu i umieścić jego implementację w osobnym pliku Zalecana zasada: rozdzielić implementację funkcji serwera od implementacji obsługiwanych przez niego usług i protokołów, dzięki temu kod serwera można ponownie wykorzystać bez jakichkolwiek zmian

Implementacja: HandleTCPClient (1) Zasada działania: odbieranie danych z podanego socketu i odsyłanie ich z powrotem na ten sam socket iteracyjne odbieranie danych trwa tak długo jak recv() zwraca wartość dodatnią recv() działa tak długo jak cokolwiek przychodzi od klienta lub do momentu, gdy klient zamknie połączenie (recv() zwraca wtedy 0)

Implementacja: HandleTCPClient (2) void HandleTCPClient(int clntsocket) { char buffer[bufsize]; // Buffer for echo string // Receive message from client ssize_t numbytesrcvd = recv(clntsocket, buffer, BUFSIZE, 0); if (numbytesrcvd < 0) DieWithSystemMessage("recv() failed"); // Send received string and receive again until end of stream while (numbytesrcvd > 0) { // 0 indicates end of stream // Echo message back to client ssize_t numbytessent = send(clntsocket, buffer, numbytesrcvd, 0); if (numbytessent < 0) DieWithSystemMessage("send() failed"); else if (numbytessent!= numbytesrcvd) DieWithUserMessage("send()", "sent unexpected number of bytes"); } // See if there is more data to receive numbytesrcvd = recv(clntsocket, buffer, BUFSIZE, 0); if (numbytesrcvd < 0) DieWithSystemMessage("recv() failed"); } close(clntsocket); // Close client socket

Programy serwera i klienta uruchomienie % TCPEchoServer4 5000 Handling client 169.1.1.2 % TCPEchoClient4 169.1.1.1 "Echo this!" 5000 Received: Echo this! Serwer działa na hoście 169.1.1.1 Klient działa na hoście 169.1.1.2 Numer portu 5000