Komunikacja z wykorzystaniem TCP/IP TCP jest protokołem

Transkrypt

Komunikacja z wykorzystaniem TCP/IP TCP jest protokołem
Komunikacja z wykorzystaniem TCP/IP
TCP jest protokołem połączeniowym, czyli przed przesłaniem danych przez użytkownika konieczne
jest zestawienie połączenia, potem następuje faza przesyłania danych, a następnie połączenie jest
zamykane rys.1.
Serwer
socket()
bind()
Klient
listen()
socket()
accept()
connect()
recv()
send()
send()
recv()
close()
close()
Rys 1. Schemat blokowy przykładowego programu klient-serwer
Aby stworzyć takie połączenie - gniazdo internetowe, należy wywołać funkcję socket(),w tym
celu potrzebne są dwa pliki nagłówkowe:
#include <sys/types.h>
#include <sys/socket.h>
int socket ( int family, int type, int protocol );
Jest to funkcja systemowa, która zwraca deskryptor gniazda, bądź wartość -1 w przypadku błędu
utworzenia. Parametry funkcji to:
 family – jest przestrzenią nazw dla rodziny protokołów, który można wybrać zamiennie jako:
AF_INET lub PF_INET, gdy mamy doczynienia z protokołami TCP/IP
 type – określa typ gniazda, SOCK_STREAM gniazdo strumieniowe, pakiety będą odbierane
sekwencyjnie w kolejności w jakiej zostały wysłane.
 protocol – typ protokołu, jeśli parametr będzie miał wartość 0 funkcja socket() wybierze
właściwy protokół bazując na parametrze type.
mainSock = socket ( AF_INET, SOCK_STREAM, 0 );
Gniazdo należy powiązać z adresem na konkretnej maszynie i do tego służy funkcja systemowa
bind().
int bind ( int mainSock, struct sockaddr *myAddr, int addrLen );
Dowiązuje ona do gniazda adres i port lokalny. Pierwszy argument jest deskryptorem gniazda
zwróconym przez funkcję socket(). Drugi argument jest wskaźnikiem do struktury zawierającej
adres IP oraz port serwera, który ma być powiązany z gniazdem. Deklaracja i wypełnienie struktury:
#include <netdb.h>
struct sockaddr_in mainAddr;
mainAddr.sin_family = AF_INET;
mainAddr.sin_port = htons(5150);
mainAddr.sin_addr.s_addr = htonl(INADDR_ANY);
//rodzina adresów
//numer portu
//adres IP
memset(&(mainAddr.sin_zero), 0, 8);
//dla zachowania odpowiedniego rozmiaru struktury
Struktura ta ułatwia dostęp do elementów adresu gniazda. Parametr struktury sin_zero ustawia się na 0
poprzez funkcję memset(). Trzeci argument funkcji bind() określa rozmiar struktury zawierającej
adres. W przypadku błędu funkcja bind() zwraca -1.
bind ( mainSock, ( struct sockaddr *) &mainAddr, sizeof(mainAddr));
Aby połączyć się z innym hostem serwer musi oczekiwać (nasłuchiwać) na przychodzące połączenia.
Do nasłuchiwania przeznaczona jest funkcja listen().
int listen ( int mainSock, int backlog );
Pierwszy argument jest deskryptorem gniazda zwróconym przez funkcję socket(). Drugi
argument jest ilością dozwolonych żądań nawiązania połączenia do obsługi. Serwer jest wstanie
nawiązywać w danej chwili jedno połączenie. W tym czasie mogą inne hosty mogą żądać obsługi
swoich połączeń, które są wówczas ustawiane w kolejce. Funkcja listen() zwraca -1
w przypadku błędu.
listen ( mainSock, 10 );
Funkcja connect() tworzy połączenie między dwoma gniazdami (klient-serwer).
int connect( int mainSock, struct sockaddr *servAddr, int addrLen );
Pierwszy argument jest deskryptorem gniazda zwróconym przez funkcję socket(). Drugi
argument jest wskaźnikiem do struktury zawierającej adres IP oraz port serwera, z którym ma zostać
nawiązane połączenie. Trzeci argument określa rozmiar struktury zawierającej adres. Funkcja
connect() zwraca -1 w przypadku błędu.
connect ( mainSock, (struct sockaddr *) &servAddr, sizeof ( struct
sockaddr ) );
Deklaracja i wypełnienie struktury sockaddr_in w kliencie ma postać:
struct sockaddr_in servAddr;
servAddr.sin_family = AF_INET;
//rodzina adresów
servAddr.sin_port = htons(5150);
//numer portu
servAddr.sin_addr.s_addr = inet_addr(argv[1]);
//adres IP przekazywany przez parametr argv[1] funkcji main()
memset(&(servAddr.sin_zero), 0, 8);
//dla zachowania odpowiedniego rozmiaru struktury
Aby zaakceptować przychodzące połączenie serwer musi wywołać funkcję accept(), która
blokuje wykonywanie dalszych instrukcji programu dopóki nie pojawi się żądanie nawiązania
połączenia.
int accept(int mainSock, struct sockaddr *clientAddr, int addrLen );
Pierwszy argument funkcji jest deskryptorem gniazda głównego zwróconym przez funkcję
socket().Drugi jest wskaźnikiem do struktury, w której zostanie zapisany adres obsługiwanego
klienta. Trzeci argument określa rozmiar struktury przechowującej adres połączenia. Funkcja
accept() tworzy nowe gniazdo do obsługi połączenia z danym klientem i zwraca jego deskryptor.
Obsługa połączenia powinna odbywać się w osobnym procesie (fork()).
int addrLen = sizeof ( clientAddr );
clientSock = accept ( mainSock,
&addrLen );
(struct sockaddr *) &clientAddr,
Wysyłanie danych jest realizowane przez funkcje send().
int send ( int clientSock, void *msg, int len, int flags );
Parametry tej funkcji to:
 clientSock – jest deskryptorem gniazda, do którego są wysyłane dane zwróconym przez
funkcję accept(),
 *msg – jest wskaźnikiem na wiadomość, która ma być przesłana,
 len – odpowiada długości przesyłanej wiadomości,
 flags – flaga, gdy jest przypisana jej wartość 0 to powoduje zablokowanie funkcji send() do
momentu aż wszystkie dane zostaną wysłane
Funkcja send() zwraca ilość wysłanych bajtów, bądź -1 w przypadku wystąpienia błędu
send ( clientSock, bufor, 100, 0 );
Odbieranie danych jest realizowane przez funkcje recv().
int recv ( int clientSock, void *buf, int len, int flags );
Parametry tej funkcji to:
 clientSock – jest deskryptorem gniazda, z którego są odbierane dane zwróconym przez
funkcję accept(),
 *buf – jest buforem, do którego zostaną zapisane odebrane dane,
 len – odpowiada wielkości bufora,
 flags – flaga, gdy jest przypisana jej wartość 0 to powoduje zablokowanie funkcji send() do
momentu aż wszystkie dane zostaną wysłane
Funkcja recv() zwraca ilość otrzymanych bajtów, bądź -1 w przypadku wystąpienia błędu, bądź 0
jeśli zostało zamknięte połączenie.
recv ( clientSock, buf, sizeof(buf), 0 );
Funkcja systemowa służąca do zamykania gniazda ma postać:
int close ( int mainSock );
Parametrem funkcji jest deskryptor zamykanego gniazda.
close ( mainSock );
W celu ustalenia przyczyny błędu można posłużyć się globalną zmienną errno ustawianą przez każdą
z opisanych funkcji. Przykład obsługi błędu dla funkcji socket():
#include <errno.h>
if( (mainSock = socket ( AF_INET, SOCK_STREAM, 0 ) ) == -1 )
{
perror(“socket”);
return 0;
}
Zadania do wykonania:
1. Wzorując się na rysunku 1. oraz fragmentach kodu zawartego w instrukcji napisać aplikację
klient-serwer. Serwer ma za zadanie odsyłać wszystko co odbierze od klienta, natomiast klient
ma wysyłać dane o długości 10, 100, 1000 oraz 10000 bajtów. Należy zmierzyć czas
transmisji przesyłanych danych, przy założeniu że czas pomiaru powinien przekraczać 10
sekund.
Aby sprawdzić z kim serwer nawiązał połączenie można posłużyć się poniższą instrukcją,
wpisując ją po wywołaniu przez serwer funkcji accept().
printf(“Server
otrzymal
polaczenie
inet_ntoa(clientAddr.sin_addr));
od
%s\n”,
2. Wzorując się na rysunku 1. oraz fragmentach kodu zawartego w instrukcji napisać aplikację
klient-serwer. Proces klienta ma za zadanie wysyłać do serwera znaki pobrane z klawiatury,
natomiast serwer wyświetla wszystko co otrzymał od klienta i odsyła mu z powrotem.
Wysyłanie i odbieranie danych przez klienta powinno się odbywać w odrębnych procesach,
aby nie blokować działania programu.
Aby pobrać znaki z klawiatury należy użyć funkcji:
char tablica_znakow[rozmiar];
scanf(„%s”,
tablica_znakow);
W procesie kompilacji programów konieczne jest dodanie biblioteki „gniazd” (ang. socket)
umożliwiających wykorzystanie protokołu TCP/IP do komunikacji.
cc
–o
nazwa
nazwa.c
/lib/libsocket.so