NOWA Treść wykładu

Download Report

Transcript NOWA Treść wykładu

Programowanie Usług Sieciowych

Tematyka wykładu

Wprowadzenie do Informatyki Podstawy Programowania Programowanie Obiektowe Inżynieria Oprogramowania I Inżynieria Oprogramowania II Podstawy Sieci Komputerowych

Programowanie Usług Sieciowych

Sieciowe Systemy Operacyjne

Sprawy organizacyjne (laboratorium) • Sposób zaliczenia laboratorium analogiczny do laboratoriów WDI i PSK – odpowiedź z programu • Wykorzystywane języki programowania: C++, C# • Wykorzystywane środowiska programistyczne: Dev-Cpp, Visual C# 2005 Express Edition, Visual Web Developer 2005 Express Edition,

Tematyka laboratorium

• Prog. 1 – klient i serwer wykorzystujące bibl. winsock i protokoły TCP/IP i UDP/IP • Prog. 2 – klient i serwer wykorzystujące obiekty C# obsługujące TCP/IP • Prog. 3 – klient i serwer wykorzystujące .NET Remoting w C# • Prog. 4 – klient i serwer wykorzystujące WebServices i SOAP w C# • Prog. 5 – klient wykorzystujący WebService dostarczany przez zewnętrzny serwer

Pojęcia podstawowe - zakres wykładu

Model OSI

aplikacji prezentacji sesji transportowa sieciowa łącza danych fizyczna

Model DoD (Department of Defense )

aplikacji transportowa sieciowa dostępu do sieci

pr. 2 - 5 pr. 1

Pojęcia podstawowe – warstwy

aplikacji transportowa sieciowa dostępu do sieci

Model DoD (Department of Defense )

TCP zastosowania IPv4, IPv6 dostępu do sieci UDP Warstwą transportową może być jeden z protokołów TCP lub UDP. Możliwe jest też ominięcie warstwy transportowej i korzystanie z bezpośrednio z oprogramowania IPv4 i IPv6 (tzw. Gniazdo surowe –

raw socket

) Użyteczne dla człowieka aplikacje: Serwer www, przeglądarka Rozdysponowuje informacje do odpowiednich aplikacji Ustala drogę do docelowego komputera w sieci Fizyczne połączenie między urządzeniami sieciowymi (np. modem, karta sieciowa)

Pojęcia podstawowe - Ogólny przegląd protokołów IPv4 IPv6 ping traceroute Prog 1 Prog 2 Prog 1 Prog 2 traceroute ping TCP UDP ICMP IPv4 IPv6 ICMPv6 Warstwa dostępu do sieci

Pojęcia podstawowe - Omówienie wybranych protokołów • IPv4 – wersja 4 protokołu IP (Internet Protocol). Używa się w nim 32 bitowych adresów. Obsługuje on dostarczanie pakietów danych dla TCP, UDP, ICMP i innych. Przykład

192.168.1.2 = 1100 0000.1010 1000.0000 0001.0000 0010= 11000000101010000000000100000010= 3232235778

Pojęcia podstawowe - Omówienie wybranych protokołów

• TCP – protokół sterowania transmisją (Transmission Control Protocol) jest protokołem obsługi połączeniowej procesu użytkownika, umożliwiającym niezawodne i w pełni dwukierunkowe (full-duplex) przesyłanie strumienia bajtów. Przykładem gniazd strumieniowych są gniazda gniazda TCP. Do zadań protokołu TCP należy należy potwierdzanie, uwzględnianie procesu oczekiwania, dokonywanie retransmisji itp. W większości internetowych programów użytkowych stosuje się protokół TCP. Protokół TCP może korzystać z IPv4 albo IPv6.

Pojęcia podstawowe - Omówienie wybranych protokołów • Protokół UDP – (User Datagram Protocol) jest protokołem obsługi bezpołączeniowej procesów użytkownika. Przykładem gniazd datagramowych są gniazda UDP. W odróżnieniu od protokołu TCP, który jest niezawodny, protokół UDP nie daje gwarancji, że datagramy UDP zawsze dotrą do wyznaczonego celu. Protokół TCP może korzystać z IPv4 albo IPv6

Pojęcia podstawowe - Omówienie wybranych protokołów

• Protokół ICMP – (Internet Control Message Protocol). Obsługuje komunikaty o błędach i infrormacje sterujące przesyłane między routerami a stacjami. Komunikaty te są zazwyczaj generowane i przetwarzane przez oprogramowanie sieciowe protokołów TCP/IP, nie zaś przez procesy użytkownika. Istnieje też oprogramowanie użytkowe, które używa protokołu ICMP (program ping). Protokół ICMP istnieje w dwóch wersjach ICMPv4 i ICMPv6

biblioteki

• Za komunikację sieciową w systemie Windows odpowiada biblioteka nagłówkowy winsock.h (i winsock2.h) jest położony w

WinSock c:\dev-cpp\include\

(w Dev-Cpp plik • W systemach unixowych jest to przeważnie biblioteka socket, jej plik nagłówkowy nosi nazwę

socket.h

• Przy programowaniu wyższego poziomu wykorzystywać będziemy odpowiednie obiekty C#

#include #include using namespace std; void PrintWSAData(LPWSADATA pWSAData);

Program 1

} { int main(int argc, char **argv) WORD wVersionRequested = MAKEWORD(1,1); WSADATA wsaData; int rc; cout << (int)LOBYTE(wVersionRequested) << "." << (int)HIBYTE(wVersionRequested) << endl; rc = WSAStartup(wVersionRequested, &wsaData); if (!rc) PrintWSAData(&wsaData); else cout << rc << endl; WSACleanup(); system("PAUSE"); return 0;

{ void PrintWSAData(LPWSADATA pWSAData) cout << endl; cout << "WSADATA" << endl; cout << "-----------------------" << endl; ;

Program 1 cd

cout << "Version .............: " << (int)LOBYTE(pWSAData->wVersion) << "." << (int)HIBYTE(pWSAData->wVersion) << endl ; cout << "HighVersion .........: " << (int)LOBYTE(pWSAData->wHighVersion) << "." << (int)HIBYTE(pWSAData->wHighVersion) << endl; } cout << "Description..........: " << pWSAData->szDescription << endl; cout << "System status........: " << pWSAData->szSystemStatus << endl; cout << "Max number of sockets: " << pWSAData->iMaxSockets << endl; cout << "MAX UDP datagram size: " << pWSAData->iMaxUdpDg << endl;

Omówienie programu 1

• • c:\dev-cpp\include\windef.h

typedef unsigned short WORD MAKEWORD() – makro tworzy wartość WORD z dwóch wartości BYTE MAKEWORD(1,1) -> 0000 0001 0000 0001 • MAKEWORD(1,2) -> 0000 0002 0000 0001 LOBYTE, HIBYTE – zwraca

Struktura WSAData

WSAData WORD WORD char char unsigned short wVersion wHighVersion szDescription[WSADESCRIPTION_LEN+1] szSystemStatus[WSADESCRIPTION_LEN+1] unsigned short char iMaxSockets iMaxUdpDg * lpVendorInfo Wersja interfejsu Winsock, której chce używać funkcja wywołująca Najwyższa wersja interfejsu Winsock, obsługiwana przez bibliotekę Opis tekstowy załadowanej biblioteki Informacje o stanie lub konfiguracji Maksymalna liczba gniazd Maksymalny rozmiar datagramu UDP Informacje o sprzedawcy

Struktura WSAData

typedef struct WSAData { WORD WORD char wVersion; wHighVersion; szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; unsigned short iMaxSockets; unsigned short char * iMaxUdpDg; lpVendorInfo; } WSADATA;

Omówienie programu 1 cd

int WSAStartup(WORD,LPWSADATA); typedef WSADATA *LPWSADATA; (winsock.h) void PrintWSAData(LPWSADATA pWSAData); to zdefiniowaliśmy sami WSACleanup();

#include #include using namespace std; void PrintWSAData(LPWSADATA pWSAData);

Program 1

} { int main(int argc, char **argv) WORD wVersionRequested = MAKEWORD(1,1); WSADATA wsaData; int rc; cout << (int)LOBYTE(wVersionRequested) << "." << (int)HIBYTE(wVersionRequested) << endl; rc = WSAStartup(wVersionRequested, &wsaData); if (!rc) PrintWSAData(&wsaData); else cout << rc << endl; WSACleanup(); system("PAUSE"); return 0;

{ void PrintWSAData(LPWSADATA pWSAData)

Program 1 cd

cout << endl; cout << "WSADATA" << endl; cout << "-----------------------" << endl; ; cout << "Version .............: " << (int)LOBYTE(pWSAData->wVersion) << "." << (int)HIBYTE(pWSAData->wVersion) << endl ; cout << "HighVersion .........: " << (int)LOBYTE(pWSAData->wHighVersion) << "." << (int)HIBYTE(pWSAData->wHighVersion) << endl; } cout << "Description..........: " << pWSAData->szDescription << endl; cout << "System status........: " << pWSAData->szSystemStatus << endl; cout << "Max number of sockets: " << pWSAData->iMaxSockets << endl; cout << "MAX UDP datagram size: " << pWSAData->iMaxUdpDg << endl;

Wynik działania programu

257 1.1

WSADATA ---------------------- Version .............: 1.1

HighVersion .........: 2.2

Description..........: WinSock 2.0

System status........: Running Max number of sockets: 32767 MAX UDP datagram size: 65467

Podsumowanie cz1

• Modele OSI, DoD • Protokoły TCP, UDP, ICMP, IP • Winsock • Struktura WSAData, • Funkcja WSAStartup

Część 2 - UDP

• Charakterystyka gniazd UDP • Przebieg komunikacji przy użyciu gniazd UDP • Funkcje wywoływane w celu przesłania danych i ich parametry • Struktury niezbędne w komunikacji UDP

Część 2 - UDP

• Protokół UDP jest bezpołączeniowy – nie wymaga żadnego długotrwałego związku między klientem i serwerem, klient nie ustanawia połączenia z serwerem, a serwer nie akceptuje połączenia z klientem. Czeka na odbiór danych z dowolnego źródła • Przez jedno gniazdo serwer może wysłać datagram do jednego serwera i w następnej chwili czasowej może wysłać przez to samo gniazdo drugi datagram do drugiego serwera.

• Jest wykorzystywany w DNS, NFS, SNMP • Opisany w RFC 768 • Komunikacja przy użyciu datagramów UDP • Każdy datagram można traktować jako rekord

Komunikacja UDP/IP

Aplikacja n Aplikacja 3 Aplikacja 2 Aplikacja 1 Gniazdo n Gniazdo 3 Gniazdo 2 Gniazdo 1 Host Adres ip: 192.168.1.2

Gniazdo n Gniazdo 3 Gniazdo 2 Gniazdo 1 Aplikacja n Aplikacja 3 Aplikacja 2 Aplikacja 1 Host Adres ip: 192.168.1.2

Komunikacja UDP

Klient

Datagram UDP Do kogo wysyłamy Do którego gniazda Kto wysyła Z którego gniazda

Klient

Datagram UDP Do kogo wysyłamy Do którego gniazda Kto wysyła Z którego gniazda IPv4/ IPv6 Datagram UDP Do kogo wysyłamy Kto wysyła IPv4/ IPv6 Datagram UDP Do kogo wysyłamy Kto wysyła

Wywołania funkcji gniazd podczas współpracy serwera i klienta UDP

klient udp serwer udp socket() socket() sednto() recvfrom() close() bind() recvfrom() Blokuje do czasu nadejścia datagramu od klienta Przetwarzanie żądania sendto()

Funkcja

socket

• SOCKET socket(int , int, int);

family

określa rodzinę protokołów (IPv4, IPv6, NetBios, IPX) •

type

- Rodzaj gniazda (strumieniowe, datagramowe, surowe) • Protocol – wskazanie dostawcy transportowego (TCP, UDP, ICMP) • SOCKET – deskryptor gniazda – do tej zmiennej zapisujemy jaki rodzaj gniazda został utworzony po wywołaniu funkcji socket.

Funkcja

bind

int bind(SOCKET,const struct sockaddr*,int); Funkcja przypisuje gniazdu lokalny adres protokołowy. Adres protokołowy składa się z 32 bitowego adresu IPv4 oraz 16-bitowego numeru portu (dla IPv6 adres ma 128bitów) • Deskryptor gniazda – uzyskany w wyniku wywołania funkcji socket() • Wskaźnik do gniazdowej struktury adresowej • Rozmiar gniazdowej struktury adresowej

Funkcja

recvfrom

int recvfrom(SOCKET,char*,int,int,struct sockaddr*,int*); Funkcja oczekuje na przyjęcie danych, jeżeli zakończyła się sukcesem zwraca liczbę otrzymanych bajtów. • Deskryptor gniazda – uzyskany w wyniku wywołania funkcji socket() • Wskaźnik do bufora do którego zostaną zapisane dane • Rozmiar bufora w bajtach • Flaga, wpisujemy 0 • Wskaźnik do struktury adresowej do której zostaną zapisane dane hosta z którego dane zostały odebrane • Wskaźnik do rozmiaru bufora do przechowywania gniazdowej struktury adresowej

Funkcja

sendto

int sendto(SOCKET,const char*,int,int,const struct sockaddr*,int); Funkcja oczekuje na przyjęcie danych • Deskryptor gniazda – uzyskany w wyniku wywołania funkcji socket() • Wskaźnik do bufora z którego zostaną pobrane dane • Rozmiar bufora w bajtach • Flaga, wpisujemy 0 • Wskaźnik do struktury adresowej zawierającej adres hosta do którego dane zostaną wysłane. Wpisujemy tutaj długość ogólnej gniazdowej struktury gniazdowej struct sockaddr { u_short sa_family; charsa_data[14]; }; jest to właściwie niepotrzebna struktura, która jest wykorzystywana do przekazywania parametrów funkcji, tam gdzie trzeba przekazać strukturę gniazdową. Struktura gniazdowa jest inna dla każdego protokołu i dlatego wymyślono strukturę ogólną dla wszystkich protokołów żeby na nią rzutować. W zasadzie problem byłby rozwiązany przez parametr void ale kiedy pisano funkcje gniazd w standardzie ansi C nie było jeszcze void

#include #include 1.

2.

using namespace std; void DatagramServer(short nPort);

Program 2 serwer

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

21.

22.

23. } #define PRINTERROR(s) \ cout << "Blad " << s << " " << WSAGetLastError() << endl; { int main() WORD wVersionRequested = MAKEWORD(1,1); WSADATA wsaData; int nRet; short nPort; cout << "Startujemy program serwera udp" << endl; cout << "Podaj port na ktorym mam nasluchiwac: "; cin >> nPort; nRet = WSAStartup(wVersionRequested, &wsaData); { if (wsaData.wVersion != wVersionRequested) cout << ”Zla wersja winsock” << endl; return -1; } DatagramServer(nPort); WSACleanup(); return 0;

2

void DatagramServer(short nPort);

Prototyp funkcji, która będzie serwerem UDP

3-4

#define PRINTERROR(s) \ cout << "Blad " << s << " " << WSAGetLastError() << endl;

Definicja makra wyświetlającego na ekranie błąd s – treść komunikatu Int WSAGetLastError(void) – funkcja zwraca wartość całkowitą odpowiadającą kodowi błędu. Wszystkie kody błędów zwracane przez funkcję mają predefiniowane stałe w pliko winsock.h

9

int nRet;

Zmienna do przechowywania wartości zwracanych przez funkcje

10

int nPort;

Zmienna do której zostanie zapisany numer portu na którym będzie nasłuchiwał serwer

13

cin >> nPort;

Wczytujemy z klawiatury nr portu na którym sewer będzie nasłuchiwał

13

if (wsaData.wVersion != wVersionRequested);

Sprawdzamy czy wersja

winsock

z tą o którą „prosiliśmy” załadowana do pamięci pokrywa się

20

DatagramServer(nPort);

Wywołujemy funkcję serwera z parametrem nPort (numer portu)

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

21.

22.

23.

24.

25.

26.

27.

28.

29.

30.

31.

32.

33.

34.

35.

36.

37.

38.

} { void DatagramServer(short nPort) SOCKET theSocket; theSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); { if (theSocket == INVALID_SOCKET) PRINTERROR("socket()"); return; } SOCKADDR_IN saServer;

Program 2 serwer

saServer.sin_family = AF_INET; saServer.sin_addr.s_addr = INADDR_ANY; saServer.sin_port = htons(nPort); int nRet; nRet = bind(theSocket,(LPSOCKADDR)&saServer, sizeof(struct sockaddr)); { if (nRet == SOCKET_ERROR) PRINTERROR("bind()"); closesocket(theSocket); return; } int nLen; nLen = sizeof(SOCKADDR); char szBuf[256]; nRet = gethostname(szBuf, sizeof(szBuf)); if (nRet == SOCKET_ERROR) { PRINTERROR("gethostname()"); closesocket(theSocket); return; } cout << "Serwer o nazwie: " << szBuf << " nasłuchuje na porcie: " << nPort << endl; SOCKADDR_IN saClient; memset(szBuf, 0, sizeof(szBuf)); nRet = recvfrom(theSocket, szBuf, sizeof(szBuf),0,(LPSOCKADDR)&saClient,&nLen); cout << "Dane otrzymane od klienta: " << szBuf << endl; sendto(theSocket, szBuf,strlen(szBuf), 0, (LPSOCKADDR)&saClient, nLen); closesocket(theSocket); return;

.

funkcja void DatagramServer(short nPort) 3 deklarujemy zmienną typu SOCKET;

Zmienna ta w pliku winsock.h jest zdefiniowana jako typedef u_int SOCKET; Zmienna ta nazywa się deskryptorem gniazda i do niej funkcja socket() zapisuje jaki rodzaj gniazda został stworzony po jej wywołaniu; mogą to przykładowo być: #define INVALID_SOCKET (0) #define SOCKET_ERROR #define SOCK_STREAM (-1) 1 #define SOCK_DGRAM #define SOCK_RAW #define SOCK_RDM 2 3 4 #define SOCK_SEQPACKET 5

4

theSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

Aby wykonać operacje wyjścia wejścia w sieci proces musi najpierw wywołać funkcję socket określając żądany rodzaj protokołu komunikacyjnego, np TCP IPv4 Funkcja ta ma 3 parametry: family, type, protocol i zwraca zmienną SOCKET, Paramety funkcji socekt mogą przyjmować następujące wartości: family określa rodzinę protokołów i może przyjmować: - AF_INTET - IPv4 - AF_INTET6 - IPv6 - AF_LOCAL protokół z dziedziny Unix - AF_ROUTE - gniazda wyznaczania tras - AF_KEY - gniazda kluczowe - AF_NETBIOS - dla netbiosu - AF_IPX dla protokołu IPX/SPX i inne zależnie od protokołu type określa rodzaj gniazda: - SOCK_STREAM - gniazdo strumieniowe - SOCK_DGRAM - gniazdo datagramowe - SOCK_RAW - gniazdo surowe i inne zależnie od protokołu protocol wskazanie dostawcy transportowego (w unix wstawia się 0) - IPPROTO_TCP - IPPROTO_UDP - IPPROTO_RAW - IPPROTO_ICMP

Nie wszystkie połączenia family + type + protocol są poprawne. Odpowiednie kombinacje można znaleźć w literaturze Nas interesują tylko połączenia AF_INET + SOCK_STREAM + IPPROTO_TCP co implikuje użycie protokołu TCPv4 + TCP AF_INET + SOCK_DGRAM + IPPROTO_UDP co implikuje użycie protokołu TCPv4 + UDP AF_INET + SOCK_RAW + IPPROTO_RAW co implikuje użycie protokołu IPv4 i gniazd surowych

10 SOCKADDR_IN saServer Deklaracja zmiennej SOCKADDR_IN

SOCKADDR_IN jest zdefiniowane jako: typedef struct sockaddr_in SOCKADDR_IN; gdzie struct sockaddr_in zdefinowana w winsocket.h dana jest poniżej: struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; struktutra sockaddr: struct sockaddr { // AF_INET jezeli IPv4, ale ogólnie tak jak w socket // wybieramy na którym porcie chcemy się łączyć // struktura zdefiniowana poniżej // to jest tylko wypełnienie, żeby struktura miała taką samą długość jak u_short sa_family; char sa_data[14]; }; struct in_addr zdefiniowana jest poniżej struct in_addr { union { struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; struct { u_short s_w1,s_w2; } S_un_w; u_long S_addr; } S_un; to jest unia (rezerwuje obszar w pamięci do zapisywania danych różnego typu w tym samym miejscu) czyli ogólnie zależnie od zastosowanego standardu możemy tam wpisać dane różnego typu i zawsze będzie działało. Obecnie stosuje się trzecią z możliwości tej unii czyli u_long - 32 bity na adres, po jednym bajcie między kropkami

Struktura sockaddr_in

short u_short struct in_addr char sockaddr_in sin_family sin_port sin_addr sin_zero[8] Postać tej struktury zależy od systemu operacyjnego. Standard Posix.1g wymaga istnienia tylko trzech pierwszych pól. Różne systemy operacyjne wykorzystują jeszcze dodatkowe pola.

protokół warstwy trzeciej, przypisujemy wartość AF_INET – IPv4 •porty 0-1023 zarezerwowane są dla powszechnie znanych usług •porty 1024-49151 porty zwykłych użytkowników •porty 49152-65535 porty dynamiczne i prywatne użytkownicy powinni korzystać z portów z drugiego zakresu struktura adresowa 32 bity to jest tylko wypełnienie, żeby struktura miała taką samą długość jak sockaddr

sin_addr

struct in_addr { union { struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; } S_un; struct { u_short s_w1,s_w2; } S_un_w; u_long S_addr; to jest unia (rezerwuje obszar w pamięci do zapisywania danych różnego typu w tym samym miejscu) czyli ogólnie zależnie od zastosowanego standardu możemy tam wpisać dane różnego typu i zawsze będzie działało. Obecnie stosuje się trzecią z możliwości tej unii czyli u_long - 32 bity na adres, po jednym bajcie między kropkami

11 saServer.sin_family = AF_INET;

Wypełniamy strukturę saServer. Wybieramy protokół IPv4

12 saServer.sin_addr.s_addr = INADDR_ANY

Pozwala serwerowi zaakceptować połączenie z klientem przez dowolny interfejs co ma znaczenie jeżeli stacja ma wiele interfejsów

13 saServer.sin_port = htons(nPort);

do kolejnego pola rekordu wpisujemy port na którym serwer będzie nasłuchiwał przy czym dokonujemy zmiany kolejności bitów przy użyciu funkcji htons() interpretacja nazwy tej funkcji jest taka: h - host, to, n-network, s-short

15

nRet = bind(theSocket, //

wpisujemy deskryptor gniazda otrzymany w wyniku wywołania funkcji socket()

(LPSOCKADDR)&saServer,

//przekazujemy adres struktury zawierającej gniazdową strukturę adresową

sizeof(struct sockaddr));

adresowej //określa rozmiar gniazdowej struktury Funkcja bind przypisuje gniazdu lokalny adres protokołowy Adres protokołowy składa się z 32-bitowego adresu IPv4 oraz 16 bitowego numeru portu (dla IPv6 ma 128bitów) funkcja bind zwraca SOCKET_ERROR, WSAEADDRINUSE gdy jakiś inny proces używa tego adresu i gniazda. Jeżeli drugi raz wywołamy w tym samym programie bind z tymi samymi parametrami otrzymamy WSAFAULT

16-18

if (nRet == SOCKET_ERROR) { PRINTERROR("bind()"); closesocket(theSocket); return; }

Wyświetlamy błąd przy użyciu naszego makra

21

int nLen;

Do tej zmiennej wpiszemy rozmiar struktury adresowej

24

nRet = gethostname(szBuf, sizeof(szBuf));

funkcja przekazuje do zmiennej szBuf nazwę stacji lokalnej szBuf wksaźnik do tablicy znakowej do której zostanie wpisana nazwa lokalna komputera drugi argument - rozmiar tablicy funkcja zwraca 0 kiedy wszystko OK, 1 gdy wystąpił błąd

25-29

Korzystamy z makra aby wyświetlić ewentualny błąd i zamknąć gniazdo.

30

Wyświetlamy nazwę serwera

31

SOCKADDR_IN saClient;

Tworzymy drugą strukturę adresową do przechowywania adresu klienta.

32

memset(szBuf, 0, sizeof(szBuf));

Zerujemy bufor danych.

szBuf – wskaźnik bufora 0 – wartość, która zostanie wpisana w wybrany zakres sizeof(szBuf) – rozmiar pamięci, który ma zostać wypełniony zerami

33

nRet = recvfrom(theSocket,

dane

szBuf, sizeof(szBuf),

// Socket na którym będziemy odbierać

//

Bufor do którego zapiszemy dane

//

Rozmiar bufora w bajtach

0, //

Flagi

(LPSOCKADDR)&saClient, //

wskaźnik do struktury adresowej do której zapiszemy adreshosta z któego przyszły dane

&nLen); //

Wskaźnik do rozmiar bufora do przechowywania gniazdowej struktury adresowej

34

Wyświetlamy na serwerze to co przyszło od klienta

35

sendto(theSocket, szBuf,

wysyłamy dane

strlen(szBuf), //

socket przez który będziemy

//

wysyłać dane

//

wskaźnik do bufora, z którego

//

długość bufora

0, (LPSOCKADDR)&saClient,

której dane wysyłamy

// //

flagi gniazdowa struktura adresowa do

nLen) //

rozmiar adresowej struktury adresowej do której wysyłamy dane

36

closesocket(theSocket);

zamknięcie gniazda. Funkcja powoduje zwolnienie deskryptora gniazda. Wszystkie ewentualne wywołania odnoszące się do tego gniazda będasię kończyły błędem WSAENOTSOCK

21.

22.

23.

24.

25.

26.

27.

28.

29.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

}; #include #include #include #include using namespace std;

Program 2 klient

void DatagramClient(char *szServer, short nPort); #define DLUGOSC_ADRESU 256 { int main(int argc, char **argv) WORD wVersionRequested = MAKEWORD(1,1); WSADATA wsaData; int nRet; short nPort; char adres_hosta[DLUGOSC_ADRESU]; cout << "Podaj adres hosta : "; cin.getline(adres_hosta, DLUGOSC_ADRESU); cout << "Podaj numer portu : "; cin >> nPort; nRet = WSAStartup(wVersionRequested, &wsaData); if (wsaData.wVersion != wVersionRequested) { cout << " Wrong version” << endl; return 0; } DatagramClient(adres_hosta, nPort); WSACleanup(); system("PAUSE"); return 0;

6

void DatagramClient(char *szServer, short nPort);

prototyp funkcji klienta, przyjmuje jako parametr adres serwera i numer portu serwera na który ma się połączyć

7

#define DLUGOSC_ADRESU 256

definiujemy stałą długość adresu (liczba znaków)

12

int nRet;

zmienna do przechowywania wartości zwracanych przez funkcje.

13

short nPort;

zmienna do przechowywania portu serwera z którym będziemy się łączyć

14

char adres_hosta[DLUGOSC_ADRESU];

tablica do przechowywania adresu serwera z którym będziemy się łączyć

19-24

zmienna do przechowywania wartości zwracanych przez funkcje.

25

DatagramClient(adres_hosta, nPort);

wywołujemy funkcję klienta podając adres serwera i port serwera

14

char adres_hosta[DLUGOSC_ADRESU];

tablica do przechowywania adresu serwera z którym będziemy się łączyć

19-24

zmienna do przechowywania wartości zwracanych przez funkcje.

26

funkcja kończy używanie biblioteki winsock.dll .

30.

31.

32.

33.

34.

35.

36.

37.

38.

39.

40.

41.

42.

43.

44.

45.

46.

47.

48.

49.

50.

51.

52.

53.

54.

55.

56.

57.

58.

59.

60.

61.

62.

63.

{ void DatagramClient(char *szServer, short nPort) cout << endl << "Datagram Client sending to server: " << szServer << endl; cout << "on port: " << nPort << endl; LPHOSTENT lpHostEntry;

Program 2 klient

lpHostEntry = gethostbyname(szServer); if (lpHostEntry == NULL) { cout << "Program zakonczyl sie bledem : " << WSAGetLastError() << endl; return; } SOCKET theSocket; theSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); cout << "No to wyswietlamy sobie socket : " << theSocket << endl; { if (theSocket == INVALID_SOCKET) PRINTERROR("socket()"); return; } SOCKADDR_IN saServer; saServer.sin_family = AF_INET; saServer.sin_addr = *( (LPIN_ADDR)*(lpHostEntry->h_addr_list)); saServer.sin_port = htons(nPort); } char szBuf[256]; int nRet; strcpy(szBuf, "Dane od klienta"); nRet = sendto(theSocket, szBuf, strlen(szBuf), 0, (LPSOCKADDR)&saServer, sizeof(struct sockaddr)); { if (nRet == SOCKET_ERROR) PRINTERROR("sendto()"); closesocket(theSocket); return;

64. memset(szBuf, 0, sizeof(szBuf)); 65.

int nFromLen; 66.

63.

recvfrom(theSocket, szBuf, sizeof(szBuf), 0, (LPSOCKADDR)&saServer, &nFromLen); if (nRet == SOCKET_ERROR) { 64.

65.

66.

67.

PRINTERROR("recvfrom()"); closesocket(theSocket); return; 68.

69.

70.

} cout << "Dane otrzymane z serwera : " << szBuf << endl; closesocket(theSocket); 71.

72. } return;

32-33

wyświetlamy na ekranie adres hosta do którego chcemy się połączyć i port na który chcemy się połączyć

34

LPHOSTENT lpHostEntry;

deklaracja zmiennej LPHOSTENT LPHOSTENT jest zdefiniowane jako typedef struct hostent *LPHOSTENT Struktura hostent zawiera wszystkie adresy IP stacji.

char char int int char hostent

Struktura hostent

Oficjalna nazwa (kanoniczna) stacji (rekord A) *h_name **h_aliases h_addrtype h_length **h_addr_list Alias 1 \0 Alias 2 \0 Alias n \0 tablica aliasów rodzaj adresu stacji IPv4/IPv6 rozmiar adresu (IPv4) lub 16 (IPv6) bajtów in_addr Adres ip 1 Adres ip 2 Adres ip 3 h_length AF_INET 4 tablica struktur sin_addr (unia)

35

lpHostEntry = gethostbyname(szServer);

jeżeli działanie funkcji gethostbyname() zakończy się powodzeniem, zwraca wskaźnik do struktury hostent zawierającej wszystkie adresy IP stacji lub NULL w przypadku niepowodzenia. Parametr reprezentuje tekstową nazwę szukanej stacji.

Aby wypełnić strukturę hostent na podstawie adresu ip należy użyć funkcji

gethostbyaddr(const char*,int,int),

• • • gdzie: wskaźnik do adresu ip zapisanego w sieciowej kolejności bajtów długość pierwszego parametru w bajtach typ adresu IPv4/IPv6

36-40

sprawdzamy czy nie wystąpił błąd (czy funkcja gethostbyname nie zwróciła NULL)

41-48

tworzymy gniazdo datagramowe UDP i sprawdzamy czy nie wystąpił błąd.

49

SOCKADDR_IN saServer;

deklarujemy zmienną do przechowywania gniazdowej struktury adresowej.

50

saServer.sin_family = AF_INET;

wypełniamy gniazdową strukturę adresową – wybieramy IPv4.

51

saServer.sin_addr = *( (LPIN_ADDR)*(lpHostEntry->h_addr_list));

na podstawie adresu wpisanego w lpHostEntry, w pierwszym elemencie tablicy wypełniamy gniazdową strukturę adresową.

LPIN_ADDR jest zdefiniowane jako: typedef struct in_addr *LPIN_ADDR; czyli operacja

(LPIN_ADDR)

jest rzutowaniem wartości wpisanej jako pierwszej do tablicy znajdującej się w strukturze wskazywanej przez lpHostEntry. Rzutowaniu podlega obiekt wpisany jako pierwszy element w h_addr_list, czyli *h_addr_list, wskazywany przez wskaźnik do struktury lpHostEntry

52

saServer.sin_port = htons(nPort);

wpisujemy port na którym się chcemy łączyć przy czym dokonujemy zmiany kolejności bitów przy użyciu funkcji htons() interpretacja nazwy tej funkcji jest taka: h - host, to, n-network, s-short

53

char szBuf[256];

utworzenie bufora do wysyłania danych

55

strcpy(szBuf, "Dane od klienta");

skopiowanie danych do bufora

56-62

wysłanie danych do serwera (analogicznie jak w przykładzie z serwerem)

65-71

odebranie danych od serwera

72

wyświetlenie otrzymanych danych na ekranie

32-33

wyświetlamy na ekranie adres hosta do którego chcemy się połączyć i port na który chcemy się połączyć

34

LPHOSTENT lpHostEntry;

deklaracja zmiennej LPHOSTENT LPHOSTENT jest zdefiniowane jako typedef struct hostent *LPHOSTENT Struktura hostent zawiera wszystkie adresy IP stacji.

Część 2 - UDP podsumowanie

• Charakterystyka gniazd UDP • Przebieg komunikacji przy użyciu gniazd UDP • Funkcje socket, bind, recvfrom, sendto, closesocket • Struktury hostent, sockaddr_in, addr_in

Część 3 - TCP

• Charakterystyka gniazd TCP • Przebieg komunikacji przy użyciu gniazd TCP (uzgadnianie trójfazowe, kończenie połączenia, stany połączenia TCP) • Funkcje wywoływane w celu przesłania danych i ich parametry • Struktury niezbędne w komunikacji TCP

Część 3 - TCP

• • • • • • • Protokół TCP: jest połączeniowy – oprogramowanie TCP tworzy połączenia (connection) między klientami a serwerami. Klient protokołu TCP ustanawia połączenie, z serwerem, wymienia dane z tym serwerem po czym kończy połączenie zapewnia niezawodność przesyłania danych. Po wysłaniu danych do partnera żąda potwierdzenia ich otrzymania. Jeżeli nie otrzyma potwierdzenia, to automatycznie ponownie wysyła dane i przez dłuższy czas czeka na potwierdzenie zawiera algorytmy przeznaczone do dynamicznego oszacowania czasu powrotu (

roud trip time

- RTT) ustala kolejność danych, przypisując kolejny numer każdemu wysłanemu bajtowi. Jeżeli wyślemy dane w ilości przekraczającej wielkość segmentu, dane zostaną podzielone na segmenty, segmenty zostaną ponumerowane i wysłane. W przypadku, gdy segmenty przybędą do odbiorcy w niewłaściwej kolejności, strona odbierająca posortuje je odpowiednio. Gdy dane zostaną zdublowane usunie zdublowane kopie danych zapewnia sterowanie przepływem – oprogramowanie TCP zawsze informuje swojego partnera o tym ile bajtów chce od niego przyjąć. Nazywa się to oknem oferowanym (advertised window). Rozmiar wolnego okna jest równy rozmiarowi wolnego miejsca w buforze odbiorcy. jest w pełni dwukierunkowe (full-duplex), tzn. program użytkowy może prze to samo połączenie jednocześnie przesyłać i odbierać dane Opisany w RFC 793

Komunikacja TCP/IP

Aplikacja n Aplikacja 3 Aplikacja 2 Aplikacja 1 Gniazdo n Gniazdo 3 Gniazdo 2 Gniazdo 1 Host Adres ip: 192.168.1.2

Gniazdo n Gniazdo 3 Gniazdo 2 Gniazdo 1 Aplikacja n Aplikacja 3 Aplikacja 2 Aplikacja 1 Host Adres ip: 192.168.1.2

1.

2.

3.

4.

Ustanawianie połączenia (uzgadnianie trójfazowe)

Klient socket, connect SYN

J

Serwer socket, bind, listen, accept SYN

K,

ACK

J

+1 connect (powraca) SYN

K,

ACK

K

+1 accept (powraca) Serwer musi być gotowy na przyjęcie połączenia tzw. otwarcie bierne połączenia (

passive open

) (funkcje:

socket

,

bind

,

listen

) – Klient rozpoczyna otwarcie aktywne połączenia (active open), funkcja połączenie

connect.

Powoduje to wysłanie segmentu danych SYN (synchronize), zawierający początkowy numer kolejnych danych, które klient będzie przesyłać przez to Serwer potwierdza przyjęcie segmentu SYN od klienta i wysyła własny segment SYN, zawierający początkowy numer kolejnych danych, które serwer będzie przesyłać przez to połączenie. W segmencie SYN serwer przesyła również potwierdzenie ACK (acknowledgement) Klient potwierdza przyjęcie segmentu SYN od serwera przez wysłanie ACK

segment SYN

• Segment SYN może zawierać informacje na temat opcji protokołu TCP. Wybrane opcje: 1.

MSS (

Maximum Segment Size

przez dane połączenie ) maksymalny rozmiar segmentu maksymalny rozmiar segmentów danych jakie można przesyłać – jeśli określi się tę opcję to oprogramowanie TCP informuje jaki jest 2.

3.

Zmiana skali oferowanego okna. Maksymalny rozmiar okna TCP wynosi 65535 (ponieważ pole w nagłówku wynosi 16 bitów). W niektórych połączeniach (np. łącza satelitarne) wymagają większych okien. Ta opcja przesuwa rozmiar okna o 0-14 bitów, dzięki czemu rozmiar okna może wynosić 65535*10 przez serwer.

14 . Opcja ta może być wysłana tylko przez klienta i musi być potwierdzona Znacznik czasowy – oznaczenie czasowe pakietów, w celu ich usunięcia, w przypadku kiedy pojawią się one z opóźnieniem w wyniku retransmisji (pierwotnie zostaną uznane za zagubione, po czym po pewnym czasie się „odnajdą”) opcje 2 i 3 nazywane są opcjami „długiego grubego łącza” (

long fat pipe

)

Zakończenie połączenia TCP

Klient Serwer FIN

M

close (zamknięcie aktywne) read przekazuje 0 (zamknięcie bierne) potw.

M

+1 close FIN

N

potw.

N+1

1.

2.

Jeden z programów użytkowych (w naszym przykładzie jest to program klienta) wywołuje funkcję

close

(zamknięcie aktywne), oprogramowanie TCP wysyła segment FIN Serwer odbiera segment FIN i wykonuje zamknięcie bierne (passive close). Oprogramowanie TCP potwierdza przyjęcie segmentu FIN. Informacja o otrzymaniu segmentu FIN jest również przesyłana do programu użytkowego jako znacznik końca pliku (po wszystkich danych, które mogą już oczekiwać w kolejce do odebrania przez program użytkowy). Dla programu użytkowego jest to jednoznacznie z tym że nie otrzyma już więcej danych przez to połączenie.

3.

Po pewnym czasie program użytkowy po stronie serwera wywołuje funkcję

close

(), aby zamknąć swoje gniazdo, co jest równoważne z wysłaniem segmentu FIN 4.

Klient potwierdza przyjęcie segmentu FIN od serwera Zamykanie połączenia zazwyczaj wymaga wysłania 4 segmentów (czasami można wysłać FIN wraz z danymi, lub potwierdzenie wraz ze znacznikeim FIN )

Diagram przejść między stanami dla połączenia TCP CLOSED otwarcie bierne SYN_RCVD Zamknięcie aktywne FIN_WAIT_1 Odb: ACK FIN_WAIT_2 LISTEN otwarcie jednoczesne wysł, SYN, ACK SYN_SENT zamkniecie lub przekroczenie czasu oczekiwania ESTABLISHED Odb: FIN Wysł: ACK CLOSE_WAIT wysł: FIN wysł: ACK Odb: FIN CLOSING Odb: ACK wysł: ACK Odb: FIN TIME_WAIT LAST_ACK Zamknięcie bierne Odb: ACK

• • • • • • • •

Przejścia między stanami TCP

Rozważamy klienta, grube strzałki narysowane linią ciągłą Wysłanie SYN -> powoduje przejście do stanu SYN_SENT. Z tego stanu możemy przejść do: – ESTABLISHED Jeżeli otrzymaliśmy potwierdzenie (SYN od serwera + ACK) – – CLOSED – jeżeli dostatecznie długo nie otrzymamy odpowiedzi SYN_RCVD – jeżeli nastąpi otwarcie jednoczesne Rozważamy serwer, grube strzałki narysowane linią przerywaną Otwarcie bierne, przechodzimy w stan LISTEN. Jeżeli odbierzemy SYN wysyłamy ACK i SYN i przechodzimy w SYN_RCVD. Jeżeli w SYN_RCVD odbierzemy ACK, to przejdziemy do ESTABLISHED. Jeżeli otrzymamy RST to przechodzimy ponownie do LISTEN – (przerwanie połączenia ze strony klienta – serwer otrzymał RST). Serwer może wysłać RST jeżeli nie port na który łączy się klient nie jest gotowy na przyjęcie danych (LISTEN) Ze stanu ESTABLISHED można wyjść przez zamknięcie aktywne (program użytkowy wysyła FIN), przechodzi następnie w stan FIN_WAIT_1 – czyli oczekuje na potwierdzenie przyjęcia znacznika FIN. Z tego stanu możemy wyjść do – FIN_WAIT_2 jeżeli otrzymamy potwierdzenie, w tym momencie dalej czekamy aż strona kończąca połączenie wyśle FIN i odpowiadamy potwierdzeniem na FIN, który otrzymaliśmy, CLOSING jeżeli – Lub do stanu CLOSING jeżeli w czasie kiedy wysłaliśmy FIN do serwera, serwer jednocześnie zakończył połączenie i w trakcie jak czekamy na potwierdzenie przez serwer zamknięcia połączenia otrzymujemy od serwera FIN. Wysyłamy wtedy ACK na jego FIN, przechodzimy do stanu CLOSING i czekamy na ACK od serwera. Jak go otrzymamy przechodzimy do TIME_WAIT – TIME_WAIT gdy serwer wyśle nam potwiedzenie ACK i FIN jednocześnie. Wysyłamy wtedy ACK i przechodzimy do stanu TIME_WAIT Przez zamknięcie pasywne (odbiór FIN) – wtedy przechodzimy do stanu CLOSE_WAIT, czekamy aż program zakończy działanie. Jak już zakończy swoje działanie wysyłamy FIN i czekamy na ACK Stan TIME_WAIT – czas przebywania w tym stanie wynosi 2MSL – maximum segment lifetime. Czas ten zależnie od implementacji może wynosić od 1 do 4 min. W sieciach określa wartość TTL w liczbie etapów transmisji (hops). Maksymalna liczba przeskoków wynosi 255. Uznaje się że MSL odpowiada wartości 255 TTL. Zatem żaden pakiet nie zdąży dotrzeć do odbiorcy i wrócić w czasie większym niż 2MSL. I tyle właśnie czasu musimy odczekać w stanie TIME_WAIT żeby przejść do stanu closed. Zapewnia to niezawodne zakończenie transmisji oraz pozwala ją wznowić bez obawy że otrzymamy jakiś zabłąkany pakiet z poprzedniej transmisji

Klient socket

,

connect

SYN_SENT

Connect powraca

ESTABLISHED

write read blokuje read powraca close

FIN_WAIT_1 FIN_WAIT_2 TIME_WAIT

SYN

J, mss = 1460

SYN

K, potw. J+1, mss = 1024

Potw.

K+

1 Dane (żądanie) Dane (odpowiedź), ACK dla żądania ACK dla odpowiedzi FIN

M

Potw.

M+

1 FIN

N

Potw.

N

+1 Serwer cocket, bind, listen, accept

SYN_RCVD

Accept powraca,

ESTABLISHED

read blokuje read powraca write read blokuje

CLOSE_WAIT

read przekazuje 0 close

LAST_ACK CLOSED

Analogia uzgadniania trójfazowego (połączenie telefoniczne)

socket()

TCP

bind() listen() connect() accept() gethostbyname() gethostbyaddr()

telefon

otrzymanie numeru telefonu poinformowanie innych o swoim numerze telefonu podłączenie aparatu do linii telefonicznej wybranie numeru telefonu (serwera) odebranie telefonu przez serwer odszukanie numeru telefonu na podstawie nazwiska odszukanie nazwiska na podstawie numeru telefonu

Wywołania funkcji gniazd podczas współpracy serwera i klienta TCP klient tcp serwer tcp socket() socket() bind() connect() listen() send() recv() closesocket Dane (odpowiedź) accept() Blokuje do chwili nawiązania połączenia recv() Przetwarzanie żądania send() recv() closesocket

Funkcja

socket

• SOCKET socket(int , int, int);

family

określa rodzinę protokołów (IPv4, IPv6, NetBios, IPX) •

type

- Rodzaj gniazda (strumieniowe, datagramowe, surowe) • Protocol – wskazanie dostawcy transportowego (TCP, UDP, ICMP) • SOCKET – deskryptor gniazda – do tej zmiennej zapisujemy jaki rodzaj gniazda został utworzony po wywołaniu funkcji socket.

Funkcja

connect

int connect(SOCKET,const struct sockaddr*,int)

• • SOCKET – deskryptor gniazda – do tej zmiennej zapisujemy jaki rodzaj gniazda został utworzony po wywołaniu funkcji socket.

Gniazdowa struktura adresowa do której jest wpisany adres i numer portu serwera • Rozmiar gniazdowej struktury adresowej przechowującej adres serwera Zwraca 0 gdy wszystko jest w porządku, -1 w przypadku wystąpienia błędu.

Funkcja connect inicjuje uzgadnianie trójfazowe. Powrót z funkcji następuje, gdy zostanie ustanowione połączenie, lub wystąpi błąd Przed wywołaniem funkcji connect klient nie musi wywoływać funkcji bind, ponieważ system przypisze temu połączeniu port efemeryczny.

Jeżeli: 1.

2.

Klient TCP nie otrzyma odpowiedzi na segment SYN, funkcja zwróci WSAETIMEDOUT. Czas po którym to nastąpi zależy od systemu (np. 75s w BSD) Jeżeli serwer odpowiedział wysyłając RST na segment SYN (żaden proces nie czeka na połączenie), zostanie wysłana stała WSAECONNREFUSED

Funkcja

send

int send(SOCKET,const char*,int,int); Funkcja wysyła dane do zdalnego komputera • Deskryptor gniazda przez które będą przesyłane dane • Wskaźnik do bufora przechowującego dane • Liczba znaków w tym buforze • Flagi – wpisujemy 0. Można ewentualnie ustawić wysłanie danych poza kolejnością, lub włączyć opcję, aby pakiety nie były trasowane • Funkcja zwraca liczbę wysłanych bajtów lub kod błędu: – SOCKET_ERROR – niepowodzenie przesłania danych – WSAECONNABORTED – przerwanie obwodu na skutek przekroczenia limitu czasu oczekiwania lub błędu protokołu – WSAECONNRESET – aplikacja na zdalnym komputerze resetuje połączenie – WSAETIMEDOUT – zerwanie połączenia na skutek awarii sieci lub wyłączenia zdalnego komputera

Funkcja

recv

int recv(SOCKET,char*,int,int); • SOCKET – deskryptor gniazda – do tej zmiennej zapisujemy gniazdo przez które ma ją być odbierane dane • Wskaźnik do bufora do którego będą zapisywane dane • Liczba bajtów, które chcemy odebrać, lub rozmiar bufora • Flagi MSG_PEEK powoduje skopiowanie danych do bufora odbiorczego bez kasowania danych z bufora systemowego (message peeking – podglądanie wiadomości) – nie należy stosować ponieważ wymaga ponownego wywołania recv w celu odebrania danych Jeżeli dane są wysyłane strumieniowo, to są buforowane i zwracają tyle bajtów, ile zażąda aplikacja.

Funkcja

closesocket

int closesocket(SOCKET); Zamknięcie gniazda, powoduje zwolnienie deskryptora gniazda. Wszystkie dalsze odwołania do tego gniazda będą się kończyły błędem WSAENOTSOCK. Jeżeli nie istnieją inne odwołania do tego gniazda, zostaną zwolnione wszystkie zasoby związane z tym deskryptorem. Usunięte zotaną dane znajdujące się w kolejce

Funkcja

listen

int listen(SOCKET,int); Po utworzeniu gniazda przez funkcję socket, przyjmuje się że jest to gniazdo aktywne, tzn. gniazdo klienta, który w następnym kroku wykona funkcję

connect

do LISTEN) . Funkcja listen przekształca to gniazdo w gniazdo bierne. Od tego momentu system operacyjny powinien akceptować żądania połączenia skierowane do tego gniazda (przejście z CLOSED Funkcja

bind

dołącza jedynie do gniazda określony adres, natomiast listen powoduje rozpoczęcie nasłuchiwania.

• SOCKET – deskryptor gniazda – do tej zmiennej zapisujemy jaki rodzaj gniazda został utworzony po wywołaniu funkcji socket.

• Maksymalna liczba połączeń, które system ustawia w kolejce do tego gniazda. Jeżeli ustawimy np. 2, połączenie trzecie zakończy się błędem WSAECONNREFUSED.

Określenie maksymalnej liczby połączeń - Kolejki połączeń W systemie istnieją 2 kolejki połączeń: 1.

Kolejka połączeń nawiązywanych (pozycje odpowiadające każdemu segmentowi SYN otrzymanemu od klienta). Dla tych połączeń serwer oczekuje na zakończenie uzgadniania trójfazowego. Gniazda, dla których połączeń tworzy się tę kolejkę znajdują się w stanie SYN_RCVD • • • • • 2.

Kolejka połączeń nawiązanych – dla tych połączeń serwer zakończył uzgadnianie trójfazowe. Gniazda, dla których połączeń tworzy się tę kolejkę znajdują się w stanie ESTABLISHED.

Jeżeli uzgadnianie trójfazowe kończy się normalnie, element kolejki połączeń nawiązywanych przejdzie na koniec kolejki połączeń nawiązanych. Kiedy aplikacja wywoła fukcję accept, wtedy pierwszy element kolejki połączeń nawiązanych będzie przekazany do aplikacji. Jeżeli kolejka połączeń nawiązanych będzie pusta, aplikacja będzie uśpiona do momentu pojawienia się w niej danych Interpretacja parametru funkcji listen jest niejednoznaczna „maksymalna liczba niezałatwionych połączeń”. Zazwyczaj przyjmuje się że jest to suma kolejki połączeń nawiązywanych i nawiązanych. W niektórych implementacjach jednak wpspółczynik ten jest mnożony przez 1.5. Wartości liczby połączeń dla różnych systemów w rzeczywistości mogą być zupełnie inne. Np. ustalając wartość parametru 5, można pozwolić na: 5 połączeń Linux, Widnows, 6 połączeń Solaris 2.5.1

7 połączeń HP-UX 8 połączeń AIX 9 połączeń Solaris 2.6

Parametr ten ma znaczenie w walce z SYN-floodingiem mogłaby być mniejsza – proponuje się zmianę jego znaczenia na maksymalną liczbę połączeń nawiązanych dla danego gniazda odsyłanych przez system do kolejki. Wtedy wartość ta accept Zakończenie uzgadniania trójfazowego Kolejka połączeń nawiązanych Kolejka połączeń nawiązywanych SYN

Funkcja

accept

• •

SOCKET accept(SOCKET,struct sockaddr*,int*);

serwer wywołuje funkcję accept, aby przekazała następne połączenie nawiązane, pobrane z wierzchu kolejki takich połączeń. Jeżeli kolejka połączeń będzie pusta, serwer będzie uśpiony, jeżeli mamy do czynienia z gniazdem blokującym.

SOCKET – deskryptor gniazda – do tej zmiennej zapisujemy jaki rodzaj gniazda został utworzony w przez serwer po wywołaniu funkcji socket, i użyty w bind i listen.

gniazdowa struktura adresowa, do której zostanie zapisany adres klienta (gniazdowa struktura adresowa otrzymana od klienta) • wskaźnik do liczby bajtów zapamiętanych w gniazdowej strukturze adresowej po jej odebraniu wartość zwracana przez accept jest deskryptorem gniazda. Przy wywołaniu funkcji socket podajemy deskryptor gniazda, które nasłuchuje w wyniku wykonania funkcji socket, bind, listen – jest to tzw.

gniazdo nasłuchujące

, podczas gdy wartość zwracana przez accept jest deskryptorem

gniazda połączonego

. Serwer tworzy jedno gniazdo nasłuchujące a następnie jedno gniazdo połączone dla każdego połączenia z klientem, dla którego zostało zakończone uzgadnianie trójfazowe. Kiedy serwer kończy obsługę jednego klienta, gniazdo połączone jest zamykane; Wartość zwracana przez

accept

może być albo deskryptorem gniazda albo –1 gdy błąd.

Jeżeli nie jest nam potrzebny adres protokołowy klienta to parametr 2 i 3 może przyjmować 0

Funkcja

connect

• SOCKET socket(int , int, int);

family

określa rodzinę protokołów (IPv4, IPv6, NetBios, IPX) •

type

- Rodzaj gniazda (strumieniowe, datagramowe, surowe) • Protocol – wskazanie dostawcy transportowego (TCP, UDP, ICMP) • SOCKET – deskryptor gniazda – do tej zmiennej zapisujemy jaki rodzaj gniazda został utworzony po wywołaniu funkcji socket.

22.

23.

24.

25.

26.

27.

28.

29.

30.

31.

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

21.

#include #include #include #define DLUGOSC_ADRESU 256

Program 3 klient

{ using namespace std; void StreamClient(char *szServer, short nPort); #define PRINTERROR(s) \ fprintf(stderr,"\n%: %d\n", s, WSAGetLastError()) int main(int argc, char **argv) WORD wVersionRequested = MAKEWORD(1,1); WSADATA wsaData; int nRet; short nPort; char adres_hosta[DLUGOSC_ADRESU]; cout << "Podaj adres hosta : "; cin.getline(adres_hosta, DLUGOSC_ADRESU); cout << "Podaj numer portu : "; cin >> nPort; } cout << endl << "Laczymy sie z hostem o adresie : " << adres_hosta << endl <<"na port : " << nPort << endl; nRet = WSAStartup(wVersionRequested, &wsaData); { if (wsaData.wVersion != wVersionRequested) fprintf(stderr,"\n Wrong version\n"); return 0; } StreamClient(adres_hosta, nPort); WSACleanup(); return 0;

52.

53.

54.

55.

56.

57.

58.

59.

60.

61.

42.

43.

44.

45.

46.

47.

48.

49.

50.

51.

32.

33.

34.

35.

36.

37.

38.

39.

40.

41.

{ void StreamClient(char *szServer, short nPort) { cout << endl << "Datagram Client sending to server: " << szServer << endl; cout << "on port: " << nPort << endl; LPHOSTENT lpHostEntry;

Program 3 klient

lpHostEntry = gethostbyname(szServer); if (lpHostEntry == NULL) PRINTERROR("gethostbyname()"); return; } SOCKET theSocket; theSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); { if (theSocket == INVALID_SOCKET) PRINTERROR("socket()"); return; } SOCKADDR_IN saServer; saServer.sin_family = AF_INET; } saServer.sin_addr = *((LPIN_ADDR)*lpHostEntry->h_addr_list); saServer.sin_port = htons(nPort); // Numer portu podany przez użytkownika int nRet; nRet = connect(theSocket, (LPSOCKADDR)&saServer, sizeof(struct sockaddr { if (nRet == SOCKET_ERROR) PRINTERROR("socket()"); closesocket(theSocket); return;

72.

73.

74.

75.

76.

77.

78.

79.

80.

81.

62.

63.

64.

65.

66.

67.

68.

69.

70.

71.

} char szBuf[256]; strcpy(szBuf, "From the Client { nRet = send(theSocket, szBuf, strlen(szBuf), 0); if (nRet == SOCKET_ERROR)

Program 3 klient

PRINTERROR("send()"); closesocket(theSocket); return; } nRet = recv(theSocket, szBuf, sizeof(szBuf), 0); { if (nRet == SOCKET_ERROR) PRINTERROR("recv()"); closesocket(theSocket); return; } cout << "Dane otrzymane od serwera: " << szBuf << endl; closesocket(theSocket); return;

7

Program 3 klient

void StreamClient(char *szServer, short nPort);

prototyp funkcji klienta TCP, przyjmuje jako parametr adres serwera i numer portu serwera na który ma się połączyć

8-9

#define PRINTERROR(s) \ fprintf(stderr,"\n%: %d\n", s, WSAGetLastError())

makro do wyświetlania błędów

18

cin.getline(adres_hosta, DLUGOSC_ADRESU);

przekazujemy adres hosta z którym chcemy się łączyć.

20

Program 3 klient

cin >> nPort;

wprowadzamy adres portu na który chcemy się łączyć

28 StreamClient(adres_hosta, nPort);

wywołujemy funkcję klienta

32 void StreamClient(char *szServer, short nPort)

funkcja klienta strumieniowego

34

Program 3 klient

cout << endl << "Datagram Client sending to server: " << szServer << endl ;

wyświetlamy adres serwera do którego chcemy się połączyć

35

cout << "on port: " << nPort << endl

wyświetlamy numer portu na który chcemy się łączyć

36

LPHOSTENT lpHostEntry;

wskaźnik do struktury przechowującej strukturę hostent serwera

Program 3 klient

37

lpHostEntry = gethostbyname(szServer); ;

wypełniamy strukturę wywołując funkcję gethostbyname()

39-42

sprawdzamy czy podany przez nas adres został odnaleziony

43-44

SOCKET theSocket; theSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

tworzymy deskryptor gniazda IPv4, strumieniowego, protokół TCP

50

Program 3 klient

tworzymy wskaźnik do pokazywania na gniazdową strukturę adresową

51-53

wypełniamy gniazdową strukturę adresową

51

saServer.sin_family = AF_INET ;

wybieramy protokół IPv4

52

saServer.sin_addr = *((LPIN_ADDR)*lpHostEntry->h_addr_list); ;

pobieramy ze struktury hostent adres serwera do którego chcemy się łączyć

53

Program 3 klient

saServer.sin_port = htons(nPort);

wpisujemy do struktury sockaddr_in numer portu serwera na który chcemy się łączyć

54

int nRet;

zmienna do przechowywania wartości zwracanych przez funkcje

55

nRet = connect(theSocket, (LPSOCKADDR)&saServer, sizeof(struct sockaddr));

55

Program 3 klient

nRet = connect(theSocket, (LPSOCKADDR)&saServer, sizeof(struct sockaddr));

Łączymy się do serwera przez gniazdo theSocket, pod adres przechowywany w strukturze hostent (parametr2), o rozmiarze (parametr3) (uzgadniania trójfazowe) Klient rozpoczyna otwarcie aktywne (active open). Oprogramowanie TCP Klienta wysyła segment danych SYN (synchronize) zawierajacy początkowy numer kolejny danych które ten klient będzie przesyłać przez to połączenie.

Zawiera on tylko nagłówek IP, TCP i ewentualnie opcje TCP. Serwer musi potwierdzić przyjęcie SYN zawierający początkowy numer kolejnych danych W jednym skegmencie z SYN zostaje wysłane potwierdzenie ACK (acknowledgement) Klient musi potwierdzić przyjęcie SYN od serwera. Nazywa się to uzgadnianiem Trójfazowym (analogia na str. 59 Stevensa). Przez wywołanie funkcji connect() do gniazda TCP powodujemy ustanowienie połączenia TCP z serwerem określonym przez gniazdową strukturę adresową Przed wywołaniem funkcji connect klient nie musi wywoływać funkcji bind ponieważ system sam wybierze sobie port efemeryczny i źródłowy adres IP.

W zależności od tego jak się zachowa serwer możliwych jest kilka wartości przesłanych od serwera do klienta: ETIMEDOUT jeżeli klient nie otrzyma odpowiedzi na swój segment SYN ECONNREFUSED odmowa połączenia - nic nie słucha na tym porcie EHOSTUNREACH lub ENETURNREACH nie można osiągnąć hosta funkcja connect zwraca 0 jeżeli wszystko ok i -1 jeżeli wystąpił błąd

56-61

Program 3 klient

sprawdzamy czy nie nastąpił błąd przy połączeniu

62

char szBuf[256]; ;

tworzymy bufor

63

strcpy(szBuf, "From the Client ” );

kopiujemy dane do bufora

63

nRet = send(theSocket, szBuf, strlen(szBuf), 0);

wysyłamy dane z bufora szBuf, przez gniazdo theSocket, maksymalna długość danych wynosi strlen(szBuf)

64-70

sprawdzamy czy nie nastąpił błąd przy wysyłaniu danych

71

Program 3 klient

nRet = recv(theSocket, szBuf, sizeof(szBuf), 0);

odbieramy dane od serwera przez gniazdo theSocket, i zapisujemy do bufora szBuf

73-77

sprawdzamy czy nie nastąpił błąd przy odbieraniu danych danych

78 cout << "Dane otrzymane od serwera: " << szBuf << endl;

wyświetlamy dane otrzymane od serwera

79 closesocket(theSocket); ;

zamykamy gniazdo

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

21.

22.

23.

24.

25.

26.

#include #include #include using namespace std; void StreamServer(short nPort);

Program 3 serwer

#define PRINTERROR(s) \ fprintf(stderr,"\n%: %d\n", s, WSAGetLastError()) int main(int argc, char **argv) { WORD wVersionRequested = MAKEWORD(1,1); WSADATA wsaData; int nRet; short nPort; cout << "Startujemy program serwera udp" << endl; { cout << "Podaj port na ktorym mam nasluchiwac: "; cin >> nPort; nRet = WSAStartup(wVersionRequested, &wsaData); if (wsaData.wVersion != wVersionRequested) fprintf(stderr,"\n Wrong version\n"); return -1; } StreamServer(nPort); WSACleanup(); return 0; }

27.

28.

29.

30.

31.

32.

33.

34.

35.

36.

37.

38.

39.

40.

41.

51.

52.

53.

54.

55.

56.

42.

43.

44.

45.

46.

47.

48.

49.

50.

{ void StreamServer(short nPort) SOCKET listenSocket; if (listenSocket == INVALID_SOCKET) { PRINTERROR("socket()"); return; } SOCKADDR_IN saServer; saServer.sin_family = AF_INET; saServer.sin_addr.s_addr = INADDR_ANY; saServer.sin_port = htons(nPort); int nRet; { nRet = bind(listenSocket, (LPSOCKADDR)&saServer, sizeof(struct sockaddr)); (nRet == SOCKET_ERROR) PRINTERROR("bind()"); closesocket(listenSocket); return; } int nLen; nLen = sizeof(SOCKADDR); char szBuf[256]; nRet = gethostname(szBuf, sizeof(szBuf)); } { if (nRet == SOCKET_ERROR) PRINTERROR("gethostname()"); closesocket(listenSocket); return; if

57.

58.

59.

60.

61.

62.

63.

64.

65.

66.

67.

68.

69.

70.

71.

72.

73.

74.

75.

76.

77.

78.

79.

80.

81.

82.

83.

84.

85.

86.

87.

88.

89.

90.

{ cout << "Serwer o nazwie: " << szBuf << " nasłuchuje na porcie: " << nPort << endl; cout << "Zaczynam nasłuchiwanie !!!" << endl; nRet = listen(listenSocket, SOMAXCONN); if (nRet == SOCKET_ERROR)

Program 3 serwer

PRINTERROR("listen()"); closesocket(listenSocket); return; } SOCKET remoteSocket; // zmienna na deskryptor gniazda klienta cout << "Połączenie blokujące " << endl; remoteSocket = accept(listenSocket, NULL, NULL); { if (remoteSocket == INVALID_SOCKET) PRINTERROR("accept()"); closesocket(listenSocket); return; } memset(szBuf, 0, sizeof(szBuf)); nRet = recv(remoteSocket, szBuf, { if (nRet == INVALID_SOCKET) PRINTERROR("recv()"); sizeof(szBuf), closesocket(listenSocket); // zamykamy gniazdo closesocket(remoteSocket); return; } cout << "Dane otrzymane od klienta : " << szBuf << endl; 0); strcat(szBuf, "From the Server"); nRet = send(remoteSocket, szBuf, strlen(szBuf), 0); closesocket(remoteSocket); closesocket(listenSocket); } return;

23

Program 3 serwer

StreamServer(nPort);

wywołanie funkcji serwera z numerem portu na którym ma nasłuchiwać

27

void StreamServer(short nPort)

funkcja serwera

29

SOCKET listenSocket;

tworzymy deskryptor gniazda nasłuchującego

30

listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) ;

tworzymy strumieniowe gniazdo nasłuchujące, IPv4, protokół TCP

31-35

Program 3 serwer

sprawdzamy czy nie wystąpił błąd

36

SOCKADDR_IN saServer;

tworzymy wskaźnik do gniazdowej struktury adresowej do przechowywania adresu serwera

37-39

wypełniamy gniazdową strukturę adresową

37

saServer.sin_family = AF_INET ;

protokół IPv4

38

Program 3 serwer

saServer.sin_addr.s_addr = INADDR_ANY

; Pozwala serwerowi zaakceptować połączenie z klientem przez dowolny interfejs co ma znaczenie jeżeli stacja ma wiele interfejsów

39

saServer.sin_port = htons(nPort) ;

podajemy port na którym ma nasłuchiwać serwer

41

nRet = bind(listenSocket, (LPSOCKADDR)&saServer, sizeof(struct sockaddr));

przywiązujemy gniazdo nasłuchujące do gniazdowej struktury adresowej serwera

42-46

obsługa błędów

48 nLen = sizeof(SOCKADDR); ;

ustalamy rozmiar gniazdowej struktury adresowej

Program 3 serwer

49 char szBuf[256];

tworzymy bufor

50 nRet = gethostname(szBuf, sizeof(szBuf));

zwracamy do bufora nazwę komputera na którym jest uruchomiony serwer

51-56

obsługa błędów

57-58

wyświetlenie informacji o nazwie serwera i nasłuchującym porcie

59

Program 3 serwer

nRet = listen(listenSocket, SOMAXCONN);;

zaczynamy nasłuchiwanie na gnieździe listensocket, a maksymalna liczba połączeń w kolejce wynosi SOMAXCONN, zdefiniowanego jako #define SOMAXCONN 5 Po utworzeniu gniazda przez funkcję socket przyjmuje się że jest to gniazdo aktywne. Funkcja listen przekształca gniazdo niepołączone w gniazdo bierne. Oznacza to że jądro systemu powinno akceptować nadchodzące żądania połączenia, skierowane do tego gniazda. J est to przejście ze stanu "closed" do "listen".

Drugi argument funkcji określa maksymalną liczbę połączeń, które jądro systemu powinno ustawić w kolejce do tego gniazda funkcję tę wywołuje się po powrocie z funkcji socekt() i bind() ale przed accept.

W rzeczywistości drugi z parametrów określa sumę połączeń nawiązywanych (takich, których serwer otrzymał segment SYN od klienta, ale jeszcze nie nie zakończyło się uzgadnianie trójfazowe TCP) i połączeń już nawiązanych (serwer otrzymał ACK od klienta, potwierdzający segment wysłany przez klienta)

61-65

obsługa błędów

Program 3 serwer

66 SOCKET remoteSocket

tworzymy zmienną do zapisania deskryptora gniazda połączonego. Gniazdo to będzie związane z pierwszym klientem, który połączy się do serwera

68 remoteSocket = accept(listenSocket, NULL, NULL);

akceptujemy połaczenia przychodzące Serwer wywołuje funkcję accept aby przekazała następne połączenie nawiązane pobrane z wierzchu kolejki połączeń. Jeżeli kolejka połączeń jest pusta proces serwera będzie uśpiony.

funkcja zwraca deskrytor gniazda klienta SOCKET PASCAL accept(SOCKET,struct sockaddr*,int*); ta funkcja zwraca gniazdową strukturę adresową dla gniazda, które zostało utworzone do odbierania danych od klienta, który się do niego połączył. Tzn. teraz będziemy mieli 2 gniazda: listenSocket jest gniazdem nasłuchującym (listening socket), remoteSocket - jest gniazdem połączonym (connected socket) Parametry: deskryptor gniazda nasłuchującego; struktura adresowa procesu, który odpowiada za połączenie z klientem. Jeżeli ten parametr nie jest nam potrzebny (nie potrzebujemy do niczego adresu protokołowego procesu klienta, można wpisać 0); liczba bajtów zapamiętana w strukturze adresowej przekazanej od procesu obsługującego klienta

69-74

obsługa błędów

75 memset(szBuf, 0, sizeof(szBuf));

czyszczenie bufora

Program 3 serwer

76 nRet = recv(remoteSocket, szBuf,

odbiór danych przez gniazdo połączone do bufora

sizeof(szBuf), 0); 77-83

obsługa błędów

84 cout << "Dane otrzymane od klienta : " << szBuf << endl

wyświetlenie danych od klienta

86 nRet = send(remoteSocket, szBuf, strlen(szBuf), 0);

wysłanie danych przez gniazdo połączone do klienta

87 closesocket(remoteSocket);

zamknięcie gniazda połączonego

88 closesocket(listenSocket);

zamknięcie gniazda nasłuchującego

Program 3 serwer

Część 3 - TCP

• Charakterystyka gniazd TCP • Przebieg komunikacji przy użyciu gniazd TCP (uzgadnianie trójfazowe, kończenie połączenia, stany połączenia TCP) • Funkcje wywoływane w celu przesłania danych i ich parametry • Struktury niezbędne w komunikacji TCP

Część 4 Modele wejścia-wyjścia

• Modele wejścia-wyjścia • Tryby wyjścia-wyjścia • Funkcja select • Struktury niezbędne przy zwielokrotnieniu wejścia-wyjścia

Zwielokrotnienie wejścia-wyjścia

Rozpatrzmy sytuację, w której konieczne jest zwielokrotnienie wejścia-wyjścia, np.: • Jeden klient obsługuje więcej niż jeden deskryptor gniazda • Jeden klient obsługuje jednocześnie większą liczbę gniazd • Ten sam proces serwera obsługuje zarówno gniazda nasłuchujące jak i gniazda połączone • Ten sam proces serwera obsługuje gniazda TCP i UDP • Ten sam serwer udostępnia więcej niż jedną usługę

• Tryb pracy gniazda – sposób działania funkcji Winsock wywoływanych do obsługi gniazda: – Blokujący – Nieblokujący • Model gniazda (model wejścia-wyjścia) – sposób zarządzania i wykonywania operacji we-wy przez aplikacje. Modele gniazd „rozbudowują” tryby pracy gniazd – Blokujący – Nieblokujący – Zwielokrotniony – Sterowany sygnałami – Asynchroniczny

Model blokujący

• Model stosowany w omówionych przykładach TCP i UDP. Rozpatrujemy na przykładzie UDP Program użytkowy System operacyjny recvfrom Funkcja systemowa Brak gotowego datagramu Oczekiwanie na dane Datagram gotowy Kopiowanie datagramu Przetwarzanie datagramu Wynik pomyślny Kopiowanie zakończone Kopiowanie danych z systemu do procesu użytkownika

Model blokujący

• Wywołanie funkcji recvfrom powoduje przekazanie sterowania do sytemu.

• Funkcja recvfrom wykonuje się dopóty, dopóki nadejdzie jakiś datagram, który zostanie odebrany i skopiowany do bufora programu, albo wystąpi błąd. Czyli funkcja recvfrom może nigdy nie powrócić do programu • Po zapisaniu danych do bufora funkcja powraca do programu i otrzymany datagram może zostać przetworzony.

Model nieblokujący

• Model w którym co jakiś czas sprawdzamy czy w buforze systemu nie ma danych do odebrania Program użytkowy recvfrom recvfrom System operacyjny Funkcja systemowa WSAEWOULDBLOCK Brak gotowego datagramu Funkcja systemowa Brak gotowego datagramu WSAEWOULDBLOCK Funkcja systemowa recvfrom Datagram gotowy Oczekiwanie na dane Kopiowanie datagramu Przetwarzanie datagramu Wynik pomyślny Kopiowanie zakończone Kopiowanie danych z systemu do procesu użytkownika

Model nieblokujący

• Zasada działania wejścia nieblokującego: – Kiedy okaże się, że żądanej przez nas operacji we-wy nie da się zakończyć inaczej niż usypiając nasz proces, wtedy system nie powinien usypiać naszego procesu, lecz awaryjnie zakończyć operację, przekazując do aplikacji informację o błędzie • W pierwszych odwołaniach do systemu funkcja zwróciła wartość WSAEWOULDBLOCK, co oznacza, że w buforze systemowym nie ma żadnych danych do odebrania. Trzecie odwołanie jest pomyślne i dane zostają skopiowane do bufora programu. • Użycie pętli w której wywołuje się funkcję recvfrom (lub recv) dla nieblokującego deskryptora nazywa się odpytywaniem (polling) • Ten tryb jest znacznie bardziej obciążający dla procesora () • Przełączanie gniazda z trybu blokującego do trybu nieblokującego odbywa się przy użyciu funkcji zdefiniowanej jako: • int ioctlsocket(SOCKET,long,u_long *);

int ioctlsocket(SOCKET,long,u_long *); • Gniazdo, którego tryb chcemy ustawić • Predefiniowany znacznik polecenia – FIONBIO – ustawienie gniazda w tryb blokujący lub nieblokujący – FIONRED – zwraca ilość danych, które jednorazowo mogą być odczytane z gniazda – SIOCATMARK – sprawdzenie czy zostały odczytane dane poza kolejnością • Parametr ustawiany jedynie przy opcji FIONBIO. Jeżeli 0 to przestawiamy w tryb blokujący, jeżeli 1 to w tryb nieblokujący

1.

2.

3.

4.

5.

6.

7.

8.

17.

18.

19.

20.

21.

22.

9.

10.

11.

12.

13.

14.

15.

16.

SOCKET theSocket; } { theSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (theSocket == INVALID_SOCKET) PRINTERROR("socket()"); return; Program będący przykładem modelu nieblokującego, wykorzystujący tryb nieblokujący u_long iMode = 1; .

nRet = ioctlsocket(theSocket, FIONBIO, &iMode); if (nRet == SOCKET_ERROR) cout << "Umieszczenie gn iazda w trybie nieblokującym zakończyło się niepowodzeniem" << endl; .

.

int i=0; do { nRet = recvfrom(theSocket,szBuf, sizeof(szBuf), 0,(LPSOCKADDR)&saClient, &nLen); cout << (i++) << endl; Sleep(1000); } while (nRet == -1);

1-2

Tworzymy gniazdo

3-7

Sprawdzamy, czy nie ma błędu

8

Program będący przykładem modelu nieblokującego, wykorzystujący tryb nieblokujący

u_long iMode = 1;

Tworzymy zmienną do ustawienia trybu nieblokującego (parametr 3 ioctlsocket)

9 nRet = ioctlsocket(theSocket, FIONBIO, &iMode);

Dla gniazda theSocket, ustawiamy tryb nieblokujący

15

Ustawiamy sobie licznik, żeby sprawdzić ile razy funkcja recvfrom wykonała się z błędem

16-23

Pętla do odbierania danych z bufora systemu.

18 nRet = recvfrom(theSocket,szBuf, sizeof(szBuf), 0,(LPSOCKADDR)&saClient, &nLen);

Odczytujemy dane przy użyciu funkcji recvfrom, przez gniazdo theSocket, do bufora szBuf, o rozmiarze sizeof(szBuf). Jeżeli w buforze systemowym nie ma żadnych danych funkcja powarca do systemu z błędem (zwraca -1). Wtedy wyświetlamy wartość licznika i zwiększamy go o 1. Czekamy 1s i kolejny raz wykonujemy pętlę. Pętla jest przerywana w chwili odebrania danych przez serwer.

Model zwielokrotniony

• Model stosowany w omówionych przykładach TCP i UDP. Rozpatrujemy na przykładzie UDP Aplikacja blokuje się na wywołaniu funkcji select, czekając na gotowość do czytania z jednego z wielu gniazd Program użytkowy select Funkcja systemowa Dane gotowe do odczytania System operacyjny Brak gotowego datagramu Datagram gotowy recvfrom Funkcja systemowa Kopiowanie datagramu Proces blokuje się podczas kopiowania danych do bufora programu użytkowego Przetwarzanie datagramu Wynik pomyślny Kopiowanie zakończone Oczekiwanie na dane Kopiowanie danych z systemu do procesu użytkownika

Model zwielokrotniony

• Zasada działania modelu zwielokrotnionego: – Aplikacja blokuje się po wywołaniu funkcji odebrania zostaje wywołana funkcja jednego w modelu blokującym. postaci:

select recvfrom.

czekając aż będzie można pobrać dane z gniazda. W chwili, kiedy dane są gotowe do Funkcja ta blokuje się do chwili pomyślnego odebrania danych z bufora systemowego. Funkcja select może oczekiwać na dane z dowolnego deskryptora, a nie tylko z • W modelu zwielokrotnionym korzystamy z funkcji select int select(int nfds,fd_set*,fd_set*,fd_set*,const struct timeval*);

• •

int select(int nfds, fd_set*, fd_set* ,fd_set* ,const struct timeval*);

nfds określa liczbę sprawdzanych deskryptorów, jego wartość jest o 1 większa od ostatniego sprawdzonego deskryptora. W gniazdach berkeleyowskich można ograniczyć liczbę deskryptorów, które są sprawdzane, co pozwala na zwiększenie wydajności. W Windows ignorowane.

fd_set * (trzy kolejne argumenty) służą do przekazania deskryptorów, dla których jądro ma sprawdzić gotowość do czytania. Mają one następującą postać:

typedef struct fd_set { u_int fd_count;

SOCKET fd_array[FD_SETSIZE]; } fd_set;

Jest to zatem struktura zawierająca liczbę deskryptorów gniazd oraz wartości deskryptorów zapisane w tablicy o rozmiarze 64: #define FD_SETSIZE 64 Drugi z parametrów zawiera strukturę przechowującą tablicę deskryptorów gniazd służących do odczytu (sprawdza możliwość odczytu), trzeci parametr zawiera strukturę przechowującą tablicę deskryptorów gniazd służących do zapisu (sprawdza możliwość zapisu), czwarty parametr zawiera strukturę przechowującą tablicę deskryptorów gniazd służących do odczytu danych poza kolejnością. Jeżeli któryś z warunków nas nie interesuje (np. sprawdzenie, czy można odebrać dane poza kolejnością, do funkcji

select

przekazujemy wartość NULL) Struktura timeval dana jako:

struct timeval { long tv_sec; long tv_usec; };

Określa czas oczekiwania na gotowość dowolnego z deskryptorów. Możliwe są 3 opcje: – System ma czekać w nieskończoność, tzn. sterowanie jest przekazane przez system do aplikacji dopiero gdy któryś z deskryptorów jest gotowy do operacji wy-we. Wtedy jako parametr przekazujemy wskaźnik NULL do funkcji select; – Możemy określić maksymalny czas oczekiwania podając wartości tv_sec i tv_usec w sekundach i mikrosekundach. Do funkcji select przekazujemy wskaźnik do struktury timeval – Możemy zrezygnować z oczekiwania – system przekazuje sterowanie do aplikacji bezpośrednio po sprawdzeniu stanu deskryptorów (inny sposób realizacji

pollingu

). Do funkcji select należy przekazać wskaźnik do struktury timeval wypełnionej wartościami zerowymi.

Funkcja select zwraca łączną liczbę uchwytów gniazd, w których oczekują funkcje I/O, jeżeli zostanie przekroczony czas oczekiwania zwracane jest 0, jeżeli z jakiegoś powodu funkcja zakończy się niepowodzeniem, zwracane jest -1 • Funkcja select identyfikuje gniazda czytające (parametr 2), jeżeli: Dane są gotowe do odczytu, • • Połączenie zostało zamknięte, zresetowane lub zakończone, • Funkcja accept zakończyła się powodzeniem, jeżeli została wywołana funkcja listen i istniały oczekujące dane Funkcja select identyfikuje gniazda zapisujące (parametr 3), jeżeli: Dane mogą być wysłane • • • Połączenie zostało nawiązane, jeżeli realizowane jest w trypie połączenia nieblokującego Funkcja select identyfikuje gniazda zapisujące (parametr 3), jeżeli: Próba połączenia zakończyła się błędem, jeżeli realizowane jest wywołanie połączenia nieblokującego Dane poza kolejnością są dostępne do odczytu.

Struktura fd_set

musi być wypełniona zanim zostanie wywołana funkcja select. Do operowania na strukturze fd_set służą następujące makra: FD_CLR(SOCKET, fd_set *) – usunięcie gniazda SOCKET ze struktury wskazywanej przez fd_set* FD_ISSET(SOCKET, fd_set *) – sprawdzenie czy gniazdo SOCKET jest elementem struktury wskazywanej przez fd_set*, jeżeli tak, zwracana jest wartość TRUE FD_SET(SOCKET, fd_set *) – dodanie gniazda SOCKET do struktury wskazywanej przez fd_set* FD_ZERO(fd_set *) – inicjacja struktury fd_set. Po wywołaniu funkcji zbiór staje się pusty. Należy zawsze wywołać przed wypełnieniem.

Jeżeli chcemy przetestować gniazdo pod kątem możliwości odczytu, musimy je umieścić w zbiorze jest częścią zbioru

readfs readfs.

i oczekiwać na zakończenie funkcji

select

. Gdy wywołanie funkcji select zakończy się trzeba sprawdzić, czy gniazdo nadal Jeżeli tak, to z gniazda można odczytywać.

Program serwera korzystający z instrukcji select

#include #include #include #include using namespace std; #define PORT 1234 // nr portu na ktorym nasluchujemy #define DATA_BUFSIZE 8192 // rozmiar bufora danych char bufor[256]; // struktura do opisu stanu gniazda typedef struct _SOCKET_INFORMATION { CHAR Buffer[DATA_BUFSIZE]; // bufor WSABUF DataBuf; // struktura zdefiniowana w winsock 2 // typedef struct _WSABUF { // unsigned long len; // char *buf; // } WSABUF, *LPWSABUF; SOCKET Socket; // gniazdo OVERLAPPED Overlapped; // DWORD BytesSEND; // liczba bajtow wyslanych DWORD BytesRECV ; // liczba bajtow otrzymanych } SOCKET_INFORMATION, * LPSOCKET_INFORMATION; DWORD TotalSockets = 0; // calkowita liczba gniazd do obsluzenia LPSOCKET_INFORMATION SocketArray[FD_SETSIZE]; // w tych strukturach bedziemy przechowywac // informacje o poszczegolnych gniazdach - czy // otrzymaly lub wyslaly dane // FD_SETSIZE okreslone jest w winsock.h jako 64

Program 4

{ int main(void) SOCKET ListenSocket; // deskryptor gniazda dasłuchującego SOCKET AcceptSocket; // deskryptor gniazda klienta SOCKADDR_IN InternetAddr; // struktura adresowa serwera WSADATA wsaData; // struktura wsadata do załadowania biblioteki INT Ret; // Wartość zwracana przez funkcje // struktura fd_set przechowuje zestaw gniazd. Ma postac: // typedef struct fd_set { // u_int fd_count; // // } fd_set; SOCKET fd_array[FD_SETSIZE]; // w naszym przypadku bedziemy w tej strukturze przechowywac zestaw gniazd // gotowych do zapisu danych oraz gotowych do odczytu danych FD_SET WriteSet; FD_SET ReadSet; DWORD i; // zmienna pomocnicza DWORD Total; // w tej zmiennej bedziemy przechowywali calkowita // liczbe gniazd ktore beda gotowe do odzczytu/zapisu // zmienna ta jest zwracana przez funkcję select ULONG NonBlock; // zmienna pomocnicza DWORD Flags; // zmienna pomocnicza DWORD SendBytes; // zmienna pomocnicza DWORD RecvBytes; // zmienna pomocnicza WORD wVersionRequested = MAKEWORD(1,1); //wersja winsock cout << "Startujemy program serwera tcp" << endl; cout << "Ladujemy winsock..."; Ret = WSAStartup(wVersionRequested, &wsaData); // Inicjalizacja WinSock cout << " zrobione" << endl;

// Tworzymy gniazdo nasluchujace przy uzyciu WSASocket // WSASocket(int, int, int, LPWSAPROTOCOL_INFO, GROUP, DWORD) // przyjmuje nastepujace parametry: // // af określa rodzinę protokołów (IPv4, IPv6, NetBios, IPX // type - Rodzaj gniazda (strumieniowe, datagramowe, surowe) // protocol – wskazanie dostawcy transportowego (TCP, UDP, ICMP) // lpProtocolInfo - jezeli wczesniej zdefniujemy liste protokolow // , ktora przekazemy jako ten parametr zostanie wybrany ten protokol, ktory // bedzie wynikal z parametrow af, type, protocol // group - nie uzywany // dwFlags - wybor modelu komunikacyjnego winsock. Wybrana opcja oznacza // ze program bedzie mogl obslugiwac nakladane operacje we/wy cout << "Tworzymy gniazdo..."; ListenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); cout << " Zrobione" << endl; //wypelniamy gniazdowa strukture adresowa serwera InternetAddr.sin_family = AF_INET; // protokol ipv4 InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY); // dowolny intefejs InternetAddr.sin_port = htons(PORT); // numer portu cout << "Bindujemy..."; // "przypisujemy gniazdu lokalny adres protokołowy bind(ListenSocket, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)); cout << "Zrobione" << endl; cout << "Rozpoczynamy nasluchiwanie..."; // rozpoczęcie nasłuchiwania. Długość kolejki oczekujących połączeń wynosi 5.

listen(ListenSocket, 5); cout << " No to sluchamy... " << endl;

// Zmiana trybu gniazda z blokującego na nieblokujący.

// funkcja ioctlsocket(SOCKET,long,u_long) // SOCKET - deskryptor gniazda // long - predefiniowany znacznik polecenia, ktore chcemy wykonac =~ "polecenie" // u_long - wskaznik do zmiennej specyficznej dla danego polecenia =~ "parametr polecenia" // parametr long = FIONBIO oraz parametr u_long = 1 oznacza przestawienie // gniazda w tryb nieblokujacy, u_long = 0 oznacza przestawienie gniazda w // tryb blokujacy. Przy uzyciu ioctlsocket mozna takze okreslic ile danych // moze byc jednorazowo odczytana cout << "Przelaczamy gniazdo w tryb nieblokujacy..."; NonBlock = 1; ioctlsocket(ListenSocket, FIONBIO, &NonBlock); cout << "Zrobione" << endl; { while(1) // Przygotowanie zestawów gniazd odczytujących i zapisujacych // do przyjmowania powiadomień o sieciowych operacjach I/O // Czyli po prostu zerujemy struktury ReadSet i WriteSet FD_ZERO(&ReadSet); FD_ZERO(&WriteSet); // przypisujemy gniazdo nasluchujace do zestawu gniazd ReadSet FD_SET(ListenSocket, &ReadSet); // Ustawienie powiadamiania o zapisie i odczycie dla każdego // gniazda w oparciu o bieżący stan bufora. Jeżeli w buforze // znajdują się jakieś dane, ustawiany jest zestaw Write, // w przeciwnym razie Read.

for (i = 0; i < TotalSockets; i++) // dla wszystkich gniaz sprawdzamy // na poczatku nie ma zadnych gniazd w ktorych otrzymalismy dane // wiec polecenie jest pomijane TotalSockets = 0; if (SocketArray[i]->BytesRECV > SocketArray[i]->BytesSEND) { FD_SET(SocketArray[i]->Socket, &WriteSet); // dodajemy gniazdo // do zestawu WriteSet // jezeli tu dodalismy // oznacza to ze cos do { // niego zapisalismy cout << "dodane gniazdo do WRITESET " << endl; } else FD_SET(SocketArray[i]->Socket, &ReadSet); // dodajemy gniazdo // do zestawu ReadSet // jezeli tu dodalismy // oznacza to ze cos do // niego "przyszlo" // z zewnatrz cout << "dodane gniazdo do READSET" << endl; } cout << "wywolujemy funckce select...";

// wywolujemy funkcje // select(int,fd_set*,fd_set*,fd_set*, const struct timeval*) // int - jest ignorowane // fd_set* przekazujemy readset - tu bedziemy sprawdzali mozliwosc odczytu // fd_set* przekazujemy writeset - tu bedziemy sprawdzali mozliwos zapisu // fd_set* przekazujemy NULL - tu moglibysmy sprawdzac czy przyszly dane // poza kolejnoscia // timeval* - tuaj mozna przekazac strukture timeval postaci: //struct timeval { // long tv_sec; // long tv_usec; //}; - okresla ona ile czasu bedziemy czekac jezeli dane nie beda // nadchodzily ani nie beda wysylane. Wpisalismy NULL, czyli czekamy // w nieskonczonosc // do zmiennej Total zostanie zwrocona liczba gniazd w ktorych // "cos sie stalo" - dane zostaly wyslane lub odebrane (Total = select(0, &ReadSet, &WriteSet, NULL, NULL)); cout << " zrobione" << endl; cout << "Cos sie zadzialo w " << Total << " gniazdach" << endl; // Sprawdzenie, czy jakies polaczenia nie nadeszly do nasłuchującego // gniazda ListenSockeet.

{ if (FD_ISSET(ListenSocket, &ReadSet)) Total--; // Tworzymy gniazdo do polaczenia

if ((AcceptSocket = accept(ListenSocket, NULL, NULL)) != INVALID_SOCKET) { cout << "Tworzymy gniazdo klienta" << endl; // Ustawienie gniazda akceptującego w trybie bez blokowania, // dzięki czemu serwer nie zostanie zablokowany na żądaniu WSASend cout << "Ustawiamy gniazdo klienta w tryb nieblokujacy... "; NonBlock = 1; ioctlsocket(AcceptSocket, FIONBIO, &NonBlock); cout << "zrobione" << endl; LPSOCKET_INFORMATION SI; cout << "Zaakceptowano polaczenie na gniezdzie: "<< AcceptSocket << endl; // Przygotowanie struktury SocketInfo SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR, sizeof(SOCKET_INFORMATION)); SI->Socket = AcceptSocket; SI->BytesSEND = 0; SI->BytesRECV = 0; // dodajemy nasze gniazdo do socket array SocketArray[TotalSockets] = SI; } } // zwiekszamy liczbe socketow do przeanalizowania o jeden TotalSockets++;

// Sprawdzanie gniazd pod kątem powiadomień o zapisie i odczycie, // analizujemy po kolei az do momentu kiedy w Total nie zostana // nam zadne gniazda do obrobienia.

cout << "Sprawdzamy wszystkie .... " << Total << "...gniazda na ktorych cos sie dzieje po kolei" << endl; { for (i = 0; Total > 0 && i < TotalSockets; i++) // zapisujemy informacje o naszym gniezdzie do zmiennej socket info LPSOCKET_INFORMATION SocketInfo = SocketArray[i]; // Jeżeli oznaczony jest zestaw odczytujący dla tego gniazda, // oznacza to, że są w nim dostępne dane do odczytu.

// sprawdzamy czy to gniazdo jest w zestawie ReadSet { if (FD_ISSET(SocketInfo->Socket, &ReadSet)) Total--; // wyrzucamy jedno gniazdo bo wlasnie // sie nim zajmujemy // przepisujemy dane i rozmiar bufora do DataBuf SocketInfo->DataBuf.buf = SocketInfo->Buffer; SocketInfo->DataBuf.len = DATA_BUFSIZE; cout << "odczytujemy dane z gniazda " << i << endl; Flags = 0; // WSARecv(SOCKET,LPWSABUF,DWORD,LPDWORD,LPDWORD, // LPWSAOVERLAPPED,LPAWSAOVELAPPED_COMPLETION_ROUTINE) // SOCKET - gniazdo // LPWSABUF - bufor odbiorczy, tablica struktur WSABUF // DWORD - liczba struktur w powyzszej tablicy // LPDWORD - liczba bajtow otrzymana w trakcie tego wywolania // LPDWORD - flaga // LPWSAOVERLAPPED - stosowane w operacjach nakladanych // LPAWSAOVELAPPED_COMPLETION_ROUTINE - stosowane w operacjach nakladanych WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes,&Flags, NULL, NULL);

SocketInfo->BytesRECV = RecvBytes; } } strcpy(bufor,SocketInfo->DataBuf.buf); cout << "otrzymalismy dane od klienta: " << bufor << endl; // Jeżeli odebrano zero bajtów, oznacza to, że partner zamknął // połączenie. Ponizej obsluga takiej sytuacji { if (RecvBytes == 0) LPSOCKET_INFORMATION SI = SocketArray[i]; closesocket(SI->Socket); printf("Zamykanie gniazda numer %d\n", SI->Socket); GlobalFree(SI); // Zmniejszenie tablicy gniazd - wyrzucamy gniazdo z // nie nastapilo polaczenie for (int k = i; k < TotalSockets; k++) { SocketArray[k] = SocketArray[k + 1]; } TotalSockets--; continue; // Jeżeli oznaczony jest zestaw odczytujący, oznacza to, że // bufory wewnętrzne są gotowe do odbioru dalszych danych

{ if (FD_ISSET(SocketInfo->Socket, &WriteSet)) Total--; SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->BytesSEND; SocketInfo->DataBuf.len = SocketInfo->BytesRECV - SocketInfo->BytesSEND; // Wysylamy dane do klienta cout << "wysylamy dane do klienta " << endl; strcat(bufor," a to dodalem od serwera"); SocketInfo->DataBuf.len=sizeof(bufor); strcpy(SocketInfo->DataBuf.buf,bufor); WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &SendBytes, 0,NULL, NULL); SocketInfo->BytesSEND += SendBytes; } } { if (SocketInfo->BytesSEND == SocketInfo->BytesRECV) SocketInfo->BytesSEND = 0; SocketInfo->BytesRECV = 0; } // wyslalismy dane wiec mozemy zakonczyc polaczenie // i wyrzucic nasze gniazdo z listy socketow LPSOCKET_INFORMATION SI = SocketArray[i]; closesocket(SI->Socket); printf("Zamykanie gniazda numer %d\n", SI->Socket); GlobalFree(SI); // Zmniejszenie tablicy gniazd - wyrzucamy gniazdo z { // nie nastapilo polaczenie for (int k = i; k < TotalSockets; k++) SocketArray[k] = SocketArray[k + 1]; } TotalSockets--; } } cout << "Wszystkie gniazda zrobione " << endl;

Wynik działania programu

Model wejścia-wyjścia sterowanego sygnałami (winsock2)

• Zasada działania modelu sterowanego sygnałami: – system może generować sygnał, który informuje aplikację o zdarzeniach sieciowych występujących w jednym lub kilku gniazdach. • przy użyciu funkcji

WSAEVENT WINAPI WSACreateEvent(void);

tworzy się obiekt zdarzenia dla każdego gniazda; WSAEVENT jest uchwytem zdarzenia; • następnie przy użyciu funkcji

int WINAPI WSAEventSelect(SOCKET, WSAEVENT, long);

przypisuje się obiekt uchwyt zdarzenia WSAEVENT do gniazda SOCKET. parametr long reprezentuje maskę bitową określającą kombinację różnych typów zdarzeń sieciowych, o których program ma być informowany.

• funkcja

DWORD WINAPI WSAWaitForMultipleEvents(DWORD, const WSAEVENT *, BOOL, DWORD, BOOL);

odbiera powiadomienie o zdarzeniu sieciowym, zwraca wartość wskazującą na obiekt zdarzenia, który spowodował zakończenie funkcji • gdy już znamy gniazdo, które spowodowało zdarzenie sieciowe funkcja

int WINAPI WSAEnumNetworkEvents(SOCKET, WSAEVENT, LPWSANETWORKEVENTS);

zwraca dostępne zdarzenia sieciowe dla tego gniazda

Model wejścia-wyjścia sterowanego sygnałami • Model stosowany w omówionych przykładach TCP i UDP. Rozpatrujemy na przykładzie UDP Program użytkowy System operacyjny proces nadal się wykonuje

WSACreateEvent WSAEventSelect WSAWaitForMultipleEvents

Oczekiwanie na dane Proces blokuje się podczas kopiowania danych do bufora programu użytkowego recvfrom Przetwarzanie datagramu Dane gotowe do odczytania Funkcja systemowa Wynik pomyślny Datagram gotowy Kopiowanie datagramu Kopiowanie zakończone Kopiowanie danych z systemu do procesu użytkownika

Model wejścia-wyjścia asynchroniczny

• Zasada działania modelu asynchronicznego: – program zleca systemowi rozpoczęcie operacji wyjścia-wejścia i następnie poinformowanie aplikacji o zakończeniu całej operacji (tj. skopiowaniu danych do bufora aplikacji). • model ten nie jest dokładnie implementowany w winsock. Model WSAAsyncSelect() nazwany pod windows asynchronicznym, jest w gruncie rzeczy modelem sterowanym sygnałami, z tą jednak różnicą, że sygnały są przekazywane do procedury okna a nie do uchwytu obiektu zdarzenia.

Model wejścia-wyjścia asynchroniczny

• Przedstawiony model jest modelem teoretycznym (Posix.1)

Program użytkowy System operacyjny Funkcja systemowa brak gotowego datagramu Oczekiwanie na dane proces nadal się wykonuje Datagram gotowy Kopiowanie datagramu Przetwarzanie datagramu powiadomienie o dostarczeniu danych Kopiowanie zakończone Kopiowanie danych z systemu do procesu użytkownika

Programowanie sieciowe w C#

• środowisko programowania • Przykładowy serwer tcp i udp • obiekty wykorzystywane w połączeniach sieciowych

Programowanie sieciowe w C#

• Wykorzystywana biblioteka:

– System.Net.Sockets; Wykorzystywane klasy biblioteki: • TcpListener • TcpClient • NetworkStream – System.Net; Wykorzystywane klasy biblioteki: • IPAddress – System.IO

• BinaryWriter

System.Net.Sockets.TcpListener

• Klasa dostarcza prostych metod które nasłuchują i akceptują nadchodzące połączenia. • Wykorzystuje model blokujący synchroniczny.

• Konstruktor klasy przeważnie wywołuje się z lokalnym adresem ip i numerem portu.

• Metoda

Start()

jest używana do rozpoczęcia nasłuchiwania. Nadchodzące połączenia będą kolejkowane do chwili wywołania metody Stop(), lub do chwili osiągnięcia maksymalnej liczby połączeń • Metoda

Stop()

– zamyka TcpListener. Nieobsłużone połączenia znajdujące się w kolejce zostaną utracone. Zaakceptowane połączenia muszą być pozamykane niezależnie. Metoda nie zamyka połączeń ustanowionych, dla których istnieje TcpClient • Metoda

AcceptTcpClient()

– metoda blokująca zwracająca obiekt TcpClient. Obiekt TcpClient jest wykorzystywany do odbierania i wysyłania danych • Metoda

Pending()

– pozwala stwierdzić czy w kolejce są nieobsłużone połączenia

• • • • • • •

System.Net.Sockets.TcpClient

Klasa dostarcza prostych metod które pozwalają na połączenie, wysyłanie i otrzymywanie strumienia danych przez sieć. Wykorzystuje model blokujący synchroniczny.

A przypadku serwera aby wymieniać dane przy użyciu TcpClient najpierw TcpListener lub Socket musi nasłuchiwać w celu odebrania nadchodzących połączeń. Po nadejściu połączenia zostaje utworzony TcpClient (TcpListener.AcceptTcpClient) Aby nawiązać połączenie przy użyciu TcpClient można wywołać konstruktor

TcpClient(adres,port)

z nazwą i numerem portu zdalnego hosta lub wywołać

TcpClient.Connect(adres, port)

Aby wysyłać lub otrzymywać dane należy przy użyciu metody

TcpClient.GetStream()

stworzyć strumień przypisany do danego TcpClient-a Aby przesyłać dane można wpisać je do strumienia przy użyciu obiektu System.IO.BinaryWriter() lub przy użyciu metody

Write()

Aby zakończyć wysyłanie danych wykorzystujemy metodę

TcpClient.Close(),

która niszczy obiekt klienta nie rozłączając połączenia

• • • • • • •

System.Net.Sockets.NetworkStream

Klasa dostarcza prostych metod które pozwalają na wysyłanie i otrzymywanie danych przez gniazda strumieniowe. Wykorzystuje model blokujący, synchroniczny i asynchroniczny.

TcpClient tworzy obiekt NetworkStream NetworkStream jest związany z konkretnym połączonym gniazdem.

Zamknięcie strumienia nie powoduje zamknięcia gniazda Do strumienia można pisać przy użyciu metody

Write(bufor,offset, rozmiar)

Ze strumienia można czytać przy użyciu metody

Read(bufor, offset, rozmiar)

• • •

System.IO.BinaryWriter

Klasa dostarcza prostych metod które pozwalają na zapisywanie z formatowaniem do strumienia. Instancję obiektu tworzy się podając w konstruktorze strumień do którego chcemy pisać Do pisania do obiektu BinaryWriter służy metoda przeładowana metoda

Write(bufor).

W zależności od potrzeby można ją wywołać z parametrami typu: Boolean, Byte, Char, Decimal, Double, Int16, Int32, Int64, Sbyte, Single, string, uint16, uint32, uint64.

System.Net.IPAddress

• • Klasa przechowuje adres IP Aby wpisać adres do obiektu na podstawie zmiennej typu string należy wywołać metodę Parse(

adres

), gdzie

adres

jest stringiem w której zapisany jest adres ip

Program wykorzystujący TCP w C# • Serwer okienkowy działający na dowolnym porcie oczekujący na połączenia TCP • Klient łączący się do serwera i wysyłający do niego tekst (możliwość ustawienia adresu docelowego i portu)

File-> New Project-> Windows Application Name: TCPKlient

Label1 texBox1

Klient TCP

Label2 numericUpDown1 listBox1 Button1

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms;

using System.Net.Sockets; using System.Net; using System.IO; // dodajemy niezbędne przestrzenie nazw

Klient TCP

{ namespace TCPKlient { public partial class Form1 : Form { public Form1() InitializeComponent(); { } private void button1_Click(object sender, EventArgs e)

string host = textBox1.Text; //tu będziemy przechowywali nazwę hosta w postaci tekstowej int port = System.Convert.ToInt16(numericUpDown1.Value); // odczytujemy port z okna try { TCPKlient klient = new TCPKlient(host, port); //tworzymy klienta TCP listBox1.Items.Add("Nawiązano połączenie z" + host + " na porcie " + port); // wyświetlamy informację o nawiązaniu połączenia NetworkStream ns = klient.GetStream(); // tworzymy obiekt strumienia BinaryWriter pisanie = new BinaryWriter(ns); //tworzymy obiekt do pisania do strumienia pisanie.Write

( "tekst do przeslania"); listBox1.Items.Add

// wpisujemy tekst do strumienia ("tekst zostal wyslany"); //wyświetlamy informację o wysłaniu tekstu klient.Close(); // kończymy połączenie } catch(Exception ex) { listBox1.Items.Add(("Błąd nawiązania połączenia"); }

}}}

MessageBox.Show(ex,ToString());

Button3 listBox1 Button1 Button2 textBox1

Serwer TCP

numericUpDown1

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Net.Sockets; using System.Net; using System.IO;

Serwer TCP

namespace TCPServer { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private TcpListener serwer = null; private TcpClient klient = null; // tworzymy pole do przechowywania obiektu TcpListener // tworzymy pole do przechowywania obiektu TcpClient

private void button2_Click(object sender, EventArgs e) { IPAddress adresIP = null; try { adresIP = IPAddress.Parse(textBox1.Text); //przetwarzamy tekst z obiektu texBox i wpisujemy do obiektu adresIP } catch { MessageBox.Show( "Błędny adres IP", "Błąd"); textBox1.Text = String.Empty; // jeśli coś poszłoby źle wyświetlamy błąd // i czyścimy okno return; } int port = System.

Convert.ToInt16(numericUpDown1.Value); // numer portu odczytujemy z numeric UpDown try { serwer = new serwer.Start(); TcpListener(adresIP, port); klient = serwer.AcceptTcpClient(); // Jeśli nadejdzie połączenie tworzymy nową instancję klienta TcpClient listBox1.Items.Add( "Nawiązano polaczenie"); //Wyświetlamy co trzeba button2.Enabled = false; //tworzymy obiekt serwera TCP nasłuchującego // strartujemy serwer, rozpoczynamy nasłuchiwanie // możemy pobawić się z aktywnością buttonów button3.Enabled = true; NetworkStream ns = klient.GetStream(); //tworzymy strumień przypisany do naszego klienta BinaryReader czytanie = new BinaryReader(ns); //tworzymy obiekt BinaryReader BinaryWriter pisanie = listBox1.Items.Add( new BinaryWriter(ns); // i obiekt BinaryWriter "klient probuje sie polaczyc"); //wyświetlamy komunikat listBox1.Items.Add("Otrzymalem od klienta " + czytanie.ReadString()); //wyświetlamy tekst otrzymany od klienta klient.Close(); // niszczymy klienta // zatrzymujemy serwer serwer.Stop(); } catch ( Exception ex) { listBox1.Items.Add( "Blad inicjalizacji serwera"); MessageBox.Show(ex.ToString(), "Blad"); //obsługa błędów } }

} private void button3_Click(object sender, EventArgs e) {

Serwer TCP

//możemy również zakończyć pracę serwera przyciskiem stop serwer.Stop(); klient.Close(); listBox1.Items.Add( "Zakonczono prace serwera"); button2.Enabled = true; // i aktywować przyciski button3.Enabled = false; } }

Wynik działania programu

.NET Remoting

Ogólna idea .NET Remoting

Obiekt X Metoda M serwer (program) Y • Klient łączy się z serwerem Y • Wywołuje metodę M obiektu X • Jest to technologia dostępna tylko w .NET

• Jest następcą technologii DCOM (Distributed Component Object Model) – nie jest już rozwijana • Analogiczna technologia w Javie nazywa się RMI (Remote Method Invocation) • Nie kopiujemy już bufora z jednego programu do drugiego (TCP,UDP). Zamiast tego zdalnie wywołujemy metodę obiektu a całe zagadnienie transmisji jest obsłużone przez odpowiednie obiekty kanał Klient serwer (komputer)

• • • •

Remoting

Remoting dostarcza infrastrukturę do wywoływania metod na zdalnych obiektach i zwracanie rezultatów Wykorzystuje on do swych celów SOAP jak i binarną wersję XML poprzez protokoły HTTP/TCP Procedura tworzenia programu w .Net Remoting – Stworzenie obiektu odpowiedzialnego za logikę aplikacji który będzie odbierał żądania na wybranym porcie – Stworzenie serwera, który będzie odpowiedzialny za rejestrację i zarządzanie stworzonym obiektem – Stworzenie aplikacji klienckich korzystających z udostępnionych usług Metody przesyłania obiektu do klienta – Przesłanie pełnej kopii obiektu (mniejszy ruch sieciowy, zwłaszscza gdy klasa ma wiele metod zwracających pojedyncze wartości, każdy klient ma kopię obiektu tylko dla siebie, wraz ze stanem obiektów wewnątrz klasy). Klasa taka musi mieć atrybut Seriazable. Czyli do przesłania obiektu między klientem i serwerem wykorzystywane jest przesyłanie przez wartość. Jeżeli obiekt jest przekazywany jako parametr, serializowany, transportowany i rekonstruowany po drugiej stronie.

– Przesłanie referencji do klasy na serwerze – Klasa musi dziedziczyć po System.MarshalByRefObject. Klasa ta zapewnia czasem życia obiektu i serializacją danych. Jeżeli obiekt dziedziczy po MarshalByRefObject może zostać zamieniony w zdalny obiekt. W chwili wywołania zdalnego obiektu na serwerze klient otrzymuje tzw. Proxy do zdalnego obiektu. Wszystkie parametry wywołania metody są zamieniane na wiadomości i przesyłane do serwera gdzie parametry są przekazywane do stosu i metoda jest wywoływana (analogicznie dane są zwracane do klienta)

Remoting

• Tworząc serwer należy określić: – port na którym będzie nasłuchiwać serwer – Rodzaj kanału którym będą przekazywane parametry wywołania • Binarny TCP – wyższa wydajność, dane są serializowane binarnie i przesyłane przy użyciu TCP • HTTP – gorsza wydajność, łatwiejsze przejście przez firewalle) informacje są przetworzone przez SOAP formatter do XML lub do postaci binarnej, zserializowane i wysłane przy użyciu HTTP – Tryb inicjalizacji obiektu • Obiekt aktywowany przez serwer – używany gdy stan obiektu nie musi być przechowywany między wywołaniami, lub kilka obiektów używa tej samej instancji obiektu • Obiekt aktywowany przez klienta – klient zarządza czasem życia obiektu. Wszystkie obiekty muszą być zarejestrowane zanim klient może ich użyć.

– Single call – automatyczne tworzenie obiektu za każdym wywołaniem przez klienta i niszczenie po wykonaniu zadania (brak możliwości przechowywania informacji w obiekcie – po każdym wywołaniu wszystkie pola będą miały wartości określone przez konstruktor) – Singleton – jeden obiekt jest przeznaczony do obsługi wszystkich żądań w trakcie działania aplikacji – zmiany wprowadzone przez jednego klienta będą widoczne przez drugiego klienta.

– Czy komunikacja ma być obustronna czy jednostronna

Kanały

• W chwili wywołania zdalnej metody parametry wywołania zostają przesłane do serwera. W ten sam sposób przekazywane są wyniki wykonania metody. • Klient może wybrać dowolny kanał dostępny na serwerze • Przynajmniej jeden kanał musi być zarejestrowany po stronie serwera, aby zarejestrować obiekt • Nie wolno rejestrować więcej niż raz kanału który słucha na tym samym porcie • Ten sam kanał może nasłuchiwać na jednym porcie • Klient może komunikować się ze zdalnym obiektem wykorzystując dowolny zarejestrowany kanał • Klient musi wywołać metodę RegisterChannel obiektu ChannelService przed rozpoczęciem komunikacji. Kanał wtedy zostaje „przypisany” danemu klientowi w oddzielnym wątku. Ponieważ każdy klient jest „obsługiwany” w oddzielnym wątku możliwe jest jednoczesne połączenie wielu klientów

• • •

Przykładowy program wykorzystujący .NET Remoting

Tworzymy bibliotekę w której znajduje się klasa odpowiedzialną za naszą usługę Klasa dziedziczy po MarshalByRefObject Klasa ma jedną metodę, która zwraca tekst przekazany jako parametr metody •biblioteka PrzykladRemoting •klasa: ZdalnyObiekt •metoda: Zwroc tekst

Przykładowy program wykorzystujący .NET Remoting

• Tworzymy serwer wykorzystujący bibliotekę PrzykładRemoting • Rezerwujemy port 12345 do nasłuchiwania • Rejestrujemy kanał niezabezpieczony • Rejestrujemy serwis w usłudze remoting Serwer wykorzystujący bibliotekę PrzykładRemoting oraz oraz przestrzenie nazw: System.Runtime.Remoting

System.Runtime.Remoting.Channels

System.Runtime.Remoting.Channels.Http

Przykładowy program wykorzystujący .NET Remoting

• Tworzymy klienta, który łączy się z usługą udostępnianą przez serwer, który • wywołuje zdalną usługę, • przekazuje jej parametry • odbiera wyniki i wyświetla w oknie klienta

Tworzenie klasy

ZdalnyObiekt

• File->New Project->Class Library • Nadajemy nazwę PrzykladRemoting • Listing klasy:

} { using System; using System.Collections.Generic; using System.Text; namespace KlasaRemoting { public class ZdalnyObiekt : MarshalByRefObject public ZdalnyObiekt() { } { } public string ZwrocTekst(string tekst) return ("Otrzymalem od klienta tekst: " + tekst); }

• • • • •

Serwer .Net remoting korzystający z klasy

ZdalnyObiekt

Tworzymy aplikację windows (File->New Project->Windows Application) Nadajemy nazwę SerwerRemoting Do projektu dodajemy referencję do biblioteki System.Runtime.Remoting.dll (project->add reference->.NET ->System.Runtime.Remoting) i do biblioteki KlasaRemoting (znajduje się w katalogu projektu bin/release) Dodajemy przestrzenie nazw: – System.Runtime.Remoting

– System.Runtime.Remoting.Channels

– System.Runtime.Remoting.Channels.Http

– KlasaRemoting Tworzymy formę w postaci przedstawionej na rys. i oprogramowujemy metodę Button1_click TexBox1 Button1

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using KlasaRemoting; { namespace SerwerRemoting { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { try HttpServerChannel kanal = new HttpServerChannel(12345); ChannelServices.RegisterChannel(kanal, false); RemotingConfiguration.RegisterWellKnownServiceType(typeof(ZdalnyObiekt), "NazwaUslugiIObiektu", WellKnownObjectMode.SingleCall); textBox1.Text = "Serwer zostal uruchomiony"; } } { catch (Exception exc) MessageBox.Show(exc.Message, "Blad"); }}}

Opis programu serwera

HttpServerChannel kanal = new HttpServerChannel(12345);

// utworzenie kanału http

ChannelServices.RegisterChannel(kanal, false);

// rejestrujemy kanał (drugi parametr odpowiada za opcję zabezpieczony=true, niezabezpieczony=false)

RemotingConfiguration.RegisterWellKnownServiceType(typeof(ZdalnyObiekt), "NazwaUslugiIObiektu", WellKnownObjectMode.SingleCall);

// rejestrujemy nasz serwis w usłudze Remoting pierwszy argument – typ klasy która będzie dostępna jako usługa, drugi argument – unikalna nazwa łącząca usługę z obiektem trzeci argument – sposób tworzenia obiektu po stronie serwera (każde nowe połączenie będzie obsługiwane przez nową instancję obiektu, Singleton – wszystkie nowe połączenia są są obsługiwane przez tę samą instancję obiektu)

Testowanie serwera

• po wpisaniu w przeglądarkę : http://localhost:12345/NazwaUslugiIObiektu?wsdl

powinna pojawić się strona z informacjami o naszym obiekcie < definitions name ="

ZdalnyObiekt

" targetNamespace ="

http://schemas.microsoft.com/clr/nsassem/KlasaRemoting/KlasaRemoting3%2C%20Ver sion%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull

" xmlns ="

http://schemas.xmlsoap.org/wsdl/

" xmlns:tns ="

http://schemas.microsoft.com/clr/nsassem/KlasaRemoting/KlasaRemoting3%2C%20Version%3 D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull

" xmlns:xsd ="

http://www.w3.org/2001/XMLSchema

" xmlns:suds ="

http://www.w3.org/2000/wsdl/suds

" xmlns:xsi ="

http://www.w3.org/2001/XMLSchema-instance

xmlns:wsdl ="

http://schemas.xmlsoap.org/wsdl/

" " xmlns:soapenc ="

http://schemas.xmlsoap.org/soap/encoding/

" xmlns:ns2 ="

http://schemas.microsoft.com/clr/nsassem/KlasaRemoting.ZdalnyObiekt/KlasaRemoting3%2C %20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull

" xmlns:ns0 ="

http://schemas.microsoft.com/clr/nsassem/KlasaRemoting/KlasaRemoting3%2C%20Version% 3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull

" xmlns:ns1 ="

http://schemas.microsoft.com/clr/ns/System

" xmlns:soap ="

http://schemas.xmlsoap.org/wsdl/soap/

"> < message name ="

ZdalnyObiekt.ZwrocTekstInput

"> < part name ="

tekst

" type ="

xsd:string

" /> < message name ="

ZdalnyObiekt.ZwrocTekstOutput

"> < part name ="

return

" < portType name ="

ZdalnyObiektPortType

"> < operation name =" type ="

xsd:string ZwrocTekst

" " />

tekst

> "> < input name ="

ZwrocTekstRequest

" message ="

tns:ZdalnyObiekt.ZwrocTekstInput

" /> < output name ="

ZwrocTekstResponse

" /> message ="

tns:ZdalnyObiekt.ZwrocTekstOutput

" < binding name ="

ZdalnyObiektBinding

" type ="

tns:ZdalnyObiektPortType

"> < soap:binding style ="

rpc

" transport ="

http://schemas.xmlsoap.org/soap/http

" /> < suds:class type ="

ns0:ZdalnyObiekt

" rootType ="

MarshalByRefObject

" /> < operation name ="

ZwrocTekst

"> < soap:operation soapAction ="

http://schemas.microsoft.com/clr/nsassem/KlasaRemoting.ZdalnyObiekt/KlasaRemoting3#Zw rocTekst

" /> < suds:method attributes ="

public

" /> < input name ="

ZwrocTekstRequest

"> < soap:body use ="

encoded

" encodingStyle ="

http://schemas.xmlsoap.org/soap/encoding/

" namespace ="

http://schemas.microsoft.com/clr/nsassem/KlasaRemoting.ZdalnyObiekt/KlasaRemoting3

" /> < output name ="

ZwrocTekstResponse

"> < soap:body use ="

encoded

" encodingStyle ="

http://schemas.xmlsoap.org/soap/encoding/

" namespace ="

http://schemas.microsoft.com/clr/nsassem/KlasaRemoting.ZdalnyObiekt/KlasaRemoting3

" /> < service name ="

ZdalnyObiektService

"> < port name ="

ZdalnyObiektPort

" binding ="

tns:ZdalnyObiektBinding

"> < soap:address location ="

http://localhost:12345/NazwaUslugiIObiektu

" />

• • • • •

Klient łączący się z serwerem i uruchamiający zdalną metodę

Tworzymy aplikację windows (File->New Project->Windows Application) Nadajemy nazwę KlientRemoting Do projektu dodajemy referencję do biblioteki System.Runtime.Remoting.dll (project->add reference->.NET ->System.Runtime.Remoting) i do biblioteki KlasaRemoting (znajduje się w katalogu projektu bin/release) Dodajemy przestrzenie nazw: – System.Runtime.Remoting

– System.Runtime.Remoting.Channels

– System.Runtime.Remoting.Channels.Http

– KlasaRemoting Tworzymy formę w postaci: textBox1 numericUpDown1 listBox1 Button1

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms;

using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using KlasaRemoting;

{

namespace KlientRemoting

{ public partial class Form1 : Form { public Form1() InitializeComponent(); } private void button1_Click(object sender, EventArgs e) {

string adres = textBox1.Text; adres = "http://" + adres; int port = (int)numericUpDown1.Value; HttpClientChannel kanal = null; try { kanal = new HttpClientChannel(); ChannelServices.RegisterChannel(kanal, false); ZdalnyObiekt obiekt = (ZdalnyObiekt)Activator.GetObject(typeof(ZdalnyObiekt), adres + ":" + port.ToString() + "/NazwaUslugiIObiektu"); listBox1.Items.Add(obiekt.ZwrocTekst(" ___tekst wyslany do serwera___")); ChannelServices.UnregisterChannel(kanal); listBox1.Items.Add("polaczenie zostalo zakonczone");} catch (Exception ex) {MessageBox.Show(ex.Message, "blad"); listBox1.Items.Add("Blad polaczenia"); ChannelServices.UnregisterChannel(kanal);

}}}}

Opis programu klienta

kanal = new HttpClientChannel();

// tworzymy obiekt kanał do połączenia z serwerem

ZdalnyObiekt obiekt = (ZdalnyObiekt)Activator.GetObject( typeof(ZdalnyObiekt), adres + ":" + port.ToString() + "/NazwaUslugiIObiektu");

tworzymy instancję obiektu ZdalnyObiekt, przy użyciu metody GetObject klasy Activator pierwszy argument metody GetObject jest typ obiektu drugi argument to obiekt Uri (adres + port + Uri)

listBox1.Items.Add(obiekt.ZwrocTekst(" ___tekst wyslany do serwera___"));

do naszego listBox’a dodajemy tekst zwrócony przez metodę ZwrocTekst obiektu ZdalnyObiekt.

ChannelServices.UnregisterChannel(kanal);

wyrejstrowujemy kanał z usługi Remoting

Wynik działania programu

Konfiguracja serwera przy użyciu pliku XML

• • • W dotychczasowym podejściu konfiguracja serwera odbywała się przy użyciu następujących poleceń: HttpServerChannel kanal = new HttpServerChannel(12345); ChannelServices.RegisterChannel(kanal, false); RemotingConfiguration.RegisterWellKnownServiceType(typeof(ZdalnyObiekt), "NazwaUslugiIObiektu", WellKnownObjectMode.SingleCall); Podejście takie wymaga każdorazowego kompilowania kodu w celu zmiany parametrów połączenia. Konfiguracji można również dokonać przy użyciu zewnętrznego pliku xml przy użyciu polecenia RemotingConfiguration.Configure( "../../config.xml", false); W tym celu dodajemy do projektu plik xml (Add->New Item->XML file) o poniższej treści, określający dokładnie te same parametry co w przypadku rozpatrywanego powyżej przykładu < configuration > < system.runtime.remoting

> < application > < service > < wellknown mode ="SingleCall„ objectUri ="NazwaUslugiIObiektu" < activated type ="KlasaRemoting.ZdalnyObiekt, KlasaRemoting3" /> type ="KlasaRemoting.ZdalnyObiekt, KlasaRemoting3" /> < channels > < channel ref ="http" port ="12345"/>

>

Web Services

Co to jest WebService?

• Web Services framework jest rozproszonym, opartym na XML systemem obiektów, serwisów i komponentów – Składają się na niego: SOAP, WSDL, UDDI – Jego zadaniem jest zapewnienie komunikacji między komputerami w sieci • Głównym założeniem jest zbudowanie rozproszonego systemu zdalnego wywołania procedur, niezależnego od platformy i języka programowania, przy użyciu istniejących standardów.

– Większość standardów została zdefiniowana przez W3C, Oasis – Web Serwisy działają prawiłowo tak długo jak długo można mapować wiadomości XML do typów, struktur, klas itd. Języka progamowani • Definicje Web Service są bardzo luźne • Dziedziczy wady i zalety sieci komputerowej – Jest łatwo skalowalny, prosty, rozproszony – Brak scentralizowanego zarządzania, mała wydajność

Porównanie architektura CGI/Web Service

• Architektura CGI – Przeglądarka komunikuje się z serwerem przy użyciu funkcji GET/POST protokołu http – Serwlety lub skrypty CGI przetwarzają parametry i podejmują działania (jak np. podłączenie się do bazy danych) – System klient/serwer Przeglądarka HTTP GET/POST Serwer WWW DB lub Appl.

JDBC

Porównanie architektura CGI/Web Service

• Web Service – Interakcja może następować przez przeglądarkę lub klienta – Jeden serwer może wywołać procedury na innym serwerze – Ma architekturę wielowarstwową Przeglądarka Server www WSDL SOAP Serwer www JDBC DB lub Appl.

GUI Klient WSDL SOAP

Podstawowe pojęcia

• SOAP – Simple Object Access Protocol

– Format wiadomości XML między klientem i serwisem

• WSDL – Web Service Description Language

– Opisuje jak można skorzystać z serwisu – Zawiera informacje o tym jak tworzyć wiadomości SOAP – WSDL jest językiem opartym na XML do opisu interfejsu programowania aplikacji (API)

Podstawowe zależności w Web Services

Korzystający z webserwisu WSDL Service Broker UDDI WSDL HTTP SOAP Dostarczający Web Serwisu

Stos protokołów Web Service

Procesy Opisy (WSDL) Wiadomości Soap Extensions Soap Komunikacja (HTTP, SMTP, FTP itd.)

Web Service

• Po co używać Web Service – Web Serwis zapewnia separację między możliwościami i interfejsami użytkownika – Umożliwia prosty dostęp do zasobów firmy np. Google APIs: http://www.google.com/apis/ • Web Service należy używać gdy: – Nie potrzebujemy dużej niezawodności – Nie potrzebujemy dużej szybkości – Gdy kilka organizacji musi ze sobą współpracować (jedna organizacja pisze oprogramowanie wykorzystywane przez serwisy innej organizacji) – Serwisy muszą być upgradowane niezależnie od klientów – Serwis może być łatwo opisany przy użyciu semantyki wykorzystującej proste zapytania i odpowiedzi i stany

Kilka słow o XML

• XML jest językiem do budowania języków • Główne zasady: dobrze sformułuj i uzasadnij • Poszczególne dialekty XML są zdefiniowane w XML Schemas – Sam XML jest zdefiniowany przy użyciu własnego schematu • XML może być rozszerzany przez przestrzenie nazw • Istnieje wiele dialektów XML, w tym nie dostosowane do www • Istnieje wiele narzędzi do tworzenia XML

XML a Web Service

• XML jest idealny do przetwarzania rozproszonego – Jest tylko sposobem opisu danych – Jest niezależny od platformy i języka • WSDL – Web Service Description Language – Opisuje jak użyć Web Serwisu – Może wykorzystać SOAP i inne protokoły do zdalnego wywołania procedur • SOAP – Simple Object Access Protocol – Rozszerzenie XML do przekazywania komunikatów zdalnego wywołania procedur – Może być przenoszony przez HTTP, SMTP itd.

Architektura Web Service

Interfejs użytkownika Serwer interfejsu użytkownika DB Serwis 1 Serwisy DB Serwis 2 DB Host 1 System operacyjny i systemy kolejkowania Host 2 DB Host 3

Web Service Description Language (WSDL)

• • Do tego aby wiedzieć jak użyć Web Serwisu potrzebny jest opis. Tradycyjnie używa się do tego opisu tekstowego API, strony internetowej manuala itp. Lepszym sposobem jest wykorzystanie standardowego języka, przy użyciu którego opiszemy jak używać serwisu. Służy do tego Web Service Description Language (WSDL) WSDL dostarcza następujących informacji: – Opisuje wszystkie publicznie dostępne funkcje – Określa wszystkie typy danych potrzebne do wysyłania pytań i otrzymywania odpowiedzi – Dostarcza informacji o użytym protokole (np. SOAP) – Określa adres pod którym serwis jest dostępny (URL) • WSDL bazuje na XML – Przy użyciu WSDL można definiować API dla dowolnego serwisu • Jeden dokument WSDL może opisywać wiele wersji interfejsu • Jeden dokument WSDL może opisywać wiele związanych ze sobą serwisów

• • • • • •

Elementy języka WSDL

jest podstawowym elementem każdego dokumentu WSDL. Określa nazwę web serwisu i użyte przestrzenie nazw XML zawiera definicję typów danych które są używane w wymienianych wiadomościah. Dokumenty WSDL używają XML Schema do definicji typów danych. Jeżeli serwis używa prostych wbudowanych typów, ten element nie jest potrzebny Wiadomość (Message) zdefiniowana abstrakcyjnie wiadomość która będzie wymieniana między klientem i serwerem. Element ten można porównać do deklaracji funkcji w tradycyjnych językach programowania. Definiuje dane na których operują funkcje – – – Zazwyczej wiadomości są używane do grupowania zapytań i odpowiedzi Każda metoda/funkcja w interfejsie zawiera 0-1 wiadomości pytania i 0-1 wiadomości odpowiedzi Składa się z elementów składowych – części (parts) – Zazwyczaj potrzebna jest jedna część dla przesłania jednej zmiennej. Część może być albo prostym typem XML lub typem złożonym.

operacje dostarczane przez Web Service Jest to najważniejszy element dokumentu WSDL. Definiuje usługi Web Services i powiązane z nimi wiadomości SOAP. Element portType można porownać do klasy w tradycyjnym scentralizowanym programowaniu.

Opis protokołu (format wiadomości i protokół transportowy) używanego przez Web Service dla każdej usługi definiuje adresy pod którymi można wywołać serwis. Zazwyczaj zawiera adres URL pozwalający na wywołanie serwisu SOAP

Struktura dokumentu WSDL

- główy element dokumentu WSDL . . . . . . . - dołączone pliki . . . . . . . - Jakie dane będą przesyłane . . . . . . . - Jaka wiadomość będzie przesyłana . . . . . . . - jakie operacje (funkcje) będą wspierane . . . . . . . - jak wiadomość zostanie przetransportowana . . . . . . . - jaka jest lokalizacja serwisu

Struktura dokumentu WSDL dla standardowej usługi HelloWorld

+ + + + + ++

Omówienie podstawowych elementów pliku wsdl na podstawie usługi HelloWorld

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

1 Określenie wykorzystywanej przestrzeni nazw (należy podać unikalną przestrzeń nazw w Internecie – dlatego np. własna domena – choć nie jest to konieczne). Przestrzeń tempuri.org jest domyślnie używana przez ASP.Net Web services. Można ustawić przestrzeń nazw na dowolną inną (patrz tempuri.org) 2 Określa typy danych, które mogą być wymieniane w wiadomości 3 Określiliśmy schemat 4 Definiujemy element o nazwie HelloWorld 7 Definiujemy element HelloWorldResponse, który jest elementem złożonym i składa się z elementów minOccurs i maxOccurs.

1.

2.

3.

4.

5.

6.

1 Definicja wiadomości przesyłanej do serwera 2 Element

part

. Dla zapytania określa parametry funkcji. W naszym przypadku definiujemy parametry jako „parameters” 4 Definicja odpowiedzi wysyłanej przez serwer 5 Element

part

. Dla zapytania określa parametry funkcji. W naszym przypadku definiujemy parametry jako „parameters” 1.

2.

3.

4.

5.

6.

1 Definicja operacji które będą wykonywane po stronie serwera 2 Nazwa operacji 3 Operacja wejściowa, nazwa musi być unikalna; tns oznacza targetNamespace 4 Operacja wyjściowa , nazwa musi być unikalna; tns oznacza targetNamespace

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

1 Określenie sposobu przesyłania danych 2 wskazuje, że wykorzystany zostanie protokół SOAP. Atrybut transport wskazuje na sposób transportu protokołu SOAP (zgodny z http://schemas.xmlsoap.org/soap/http ) 3 Wskazuje powiązanie operacji do konkretnej implementacji SOAP. Atrybut soapAction określa że nagłówek SOAPAction będzie używany do identyfikacji serwisu 5 Wejście 6 określenie sposobu tłumaczenia na SOAP jako literal (może być

literal

lub

rpc

) – literal oznacza że na podstawie WSDL zostanie wygenerowana wiadomość SOAP, litera po literze, nie jako zdalne wywołanie procedury 8 wyjście 9 określenie sposobu tłumaczenia na SOAP jako literal (może być

literal

lub

rpc

) – literal oznacza że na podstawie WSDL zostanie wygenerowana wiadomość SOAP, litera po literze, nie jako zdalne wywołanie procedury

1 Określenie lokalizacji serwisu

SOAP (Simple Object Access Protocol)

• SOAP jest protokołem komunikacyjnym służącym do wymianywiadomości pomiędzy aplikacjami.

• SOAP przeznaczony jest do wymiany danych w sieci internet.

• SOAP jest niezależny od platformy sprzętowej oraz języka • programowania.

• Wiadomości SOAP są opisane za pomocą XML.

• SOAP jest standardem W3C: http://www.w3.org/TR/soap/ • Wykorzystuje komunikację rozproszoną w postaci Remote Procedure Call • Protokół SOAP dzieli się na 3 części: – Kopertę soap (SOAP envelope) jest to definicja opisująca co znajduje się w wiadomości, dla kogo jest przeznaczona i czy wiadomość jest obowiązkowa lub opcjonalna.

– Zasady kodowania (SOAP encoding rules) – możliwość definiowania typów wykorzystywanych przez aplikację.

– Reprezentacja RPC (SOAP RPC representation) – określa reguły opisu zdalnych procedur i ich odpowiedzi.

• Wiadomość jest podstawową jednostką komunikacji między nadawcą i odbiorcą • Wiadomość SOAP może wykorzystywać różne protokoły jako medium transportowe (HTTP, TCP, SMTP) • Aplikacja SOAP, która odebrała wiadomość SOAP musi przetworzyć ją zgodnie z zasadami: – Rozpoznać wszystkie części wiadomości.

– Zdecydować, czy wszystkie części wiadomości są wspierane przez aplikację.

– Jeżeli aplikacja nie jest adresatem wiadomości to musi ją przekazać do adresata.

Struktura SOAP

• Specyfikacja SOAP zawiera – – – Specyfikację koperty, która definiuje reguły enkapsulacji danych, z uwzględnieniem danych specyficznych dla danej aplikacji, takich jak nazwy metod, parametrów, zwracanych wartości Reguły kodowania danych bazujące na XML Schema Konwencje RPC, wysyłane parametry, zwracane rezultaty ze zdalnych metod • Wiadomość SOAP zawiera – Kopertę enkapsulującą całość danych. Koperta wykorzystuje przestrzeń nazw XML do określania wersji protokołu

1 element ciała (Body) – zawiera ono informacje zakodowane przy użyciu XML. Jest to zdalne wywołanie metody. Może zawierać błędy zwracane przez zdalną aplikację Koperta Nagłówek Blok nagłówka 1 Ciało Wiadomość

Przykład zapytania SOAP

POST /StockQuote HTTP/1.1

Host: www.stockquoteserver.com

Content-Type: text/xml; charset="utf-8" Content-Length: nnnn SOAPAction: "Some-URI" DIS Koperta, ciało

Przykład odpowiedzi SOAP

HTTP/1.1 200 OK Content-Type: text/xml; charset="utf-8" Content-Length: nnnn 34.5

• • • • • • • • • • • • •

UDDI

Universal Discovery Description and Integration registy – spis dostawców usług WebService (książka telefoniczna zawierająca spis usług dostarczanych przez usługodawców).

Jej założeniem jest zebranie w jednym miejscu wszystkich dostępnych web serwisów oraz możliwości ich wyszukiwania. Dostępny pod http://uddi.xml.org/ Pierwotnie został stworzony przez trzy firmy Microsoft, IBM i Ariba Standard UDDI definiuje sposób publikowania i poszukiwania komponentów oprogramowania zbudowanych w technologii SOA (Service Oriented Architecture) Główną funkcjonalnością UDDI jest prezentacja metadanych o WebSerwisie w Internecie lub Intranecie, opis metod kontroli dostępu do rejestru i dystrybucji rekordów do innych rejestrów.

UDDI udostępnia mechanizmy do klasyfikacji, katalogowania, zarządzania WebSerwisami. Innymi słowy udostępnia środki do znalezienia serwisu i uruchomienia serwisu UDDI określa podstawowe typy które pozwalają na opis funkcjonalności WebSerwisu (businessService), informacje o dostawcy serwisu (businessEntity), techniczne szczegóły serwisu (bindingTemplate), atrybuty oreaz metadane, relacje między dostawcami (publisherAssertion) oraz API UDDI zapewnia definicje zależności hierarchicznych między implementacjami UDDI. Wyróżnia się dwa rodzaje serwerów UDDI: – – Węzeł (node) – zapewnia zestaw podstawowych funkcjonalności zdefiniowanych w specyfikacji Rejestr (registry) – składa się z jednego lub więcej węzłów. Zapewnia pełną funkcjonalność zdefiniowaną w specyfikacji Każdy może stworzyć własne UDDI UDDI jest zbudowane przy użyciu protokołu SOAP Pozwala na rejestrację swoich usług Zawiera filtry spamowe

Problemy z UDDI

• Może zawierać bardzo dużo aplikacji • Czy można ufać dostępnym aplikacjom • Czy możliwe są oszustwa • Jakość dostępnych danych • Szybkość dostępu do danych

Lokalne UDDI

• Można używać UDDI w kontrolowanym środowisku • Serwisy dla dużych korporacji • Zauwanie w organizacji • Możliwość udostępniania serwisów dla zweryfikowanych odbiorców

WebService kontra .NET Remoting

Web Service

Niezależny od platformy Serializacja przy użyciu XmlSerializer Łatwiejszy model programistyczny Szeroki zasięg

.NET Remoting

Zależny od platformy Serializacja przy użyciu IFormatter Trudniejszy model programistyczny Wąski zasięg Większa kontrola konfiguracji serwera i klienta Możliwość oprogramowania WebSerwisu przez zastosowanie SOAPFormatter’a

Porównanie wydajnościowe odczyt jednej danej z bazy

• ASMX = ASP.NET WebService • WS – windows service • ASMX_SOAPExtn – soap exntension

Porównanie wydajnościowe odczyt 1 rekordu z bazy i zwrócenie wyniku do klienta

• ASMX = ASP.NET WebService • WS – windows service • ASMX_SOAPExtn – soap exntension

Jak znaleźć interesujący WebService?

• Otwieramy stronę seekda.com • Wpisujemy hasło które nas interesuje: weather • Wyszukujemy np. GlobalWeather

Jak znaleźć interesujący WebService?

• Możemy obejrzeć opis GlobalWeather w WSDL (poniżej fragment)

Jak znaleźć interesujący WebService?

• Oglądamy szczegóły serwisu GlobalWeather • Przechodzimy do zakładki Use Now

Jak znaleźć interesujący WebService?

• Mamy tu wypisane dostępne metody serwisu GlobalWeather • Klikamy w metodę GetWeather i możemy dostać się do testera WebSerwice

Jak znaleźć interesujący WebService?

• W testerze WebService wpisujemy

London

wynik wywołania otrzymujemy plik xml w pole

CityName.

Jako • Napisanie klienta WebSerwisu polega na napisaniu programu wstawiającego w pole CityName łańcucha London i „przetworzenie” zwrotnego pliku xml (SOAP)

Pierwszy WebService

• Uruchamiamy Visual Web Developer • Wybieramy File->New WebSite • ASP.NET WebService, Language C#

Kod WebSerwisu

using System; using System.Web; using System.Web.Services; using System.Web.Services.Protocols; [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class Service : System.Web.Services.WebService

{ public Service () { } //Uncomment the following line if using designed components //InitializeComponent(); [WebMethod]

public string PierwszaMetoda(string lancuch) {

}

return lancuch + "___ tekst dodany do lancucha"; //tworzymy metodę PierwszaMetoda zwracająca łańcuch, który //jest przekazany do metody jako parametr // zwracamy lancuch + tekst dodany

}

Po uruchomieniu WebSerwisu Wyświetla się strona informacyjna naszej usługi sieciowej Możemy obejrzeć opis naszej usługi w języku WSDL:

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

Ciekawsze fragmenty zawarte w opisie usługi

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

Strona testowa serwisu

Strona jest dostępna pod adresem localhost:51207/WebService1/Service.asmx?op=PierwszaMetoda Możemy przez stronę przekazać zmienną i zobaczyć wynik:

Wartość zwracana przez WebSerwis

Kolejne zadanie polega na napisaniu klienta, który będzie wysyłał dane do naszego WebSerwisu i odbierał od niego dane

• • • • • • • Uruchamiamy Visual C# Express Edition File->New Project->Windows Application, name WebServiceClient Do projektu dodajemy referencję do naszej usługi sieciowej Project->Add Web Reference: localhost:51207/WebService1/Service.asmx

Serwis musi być w trakcie dodawania uruchomiony!!!

Wciskamy Add Reference Referencję możemy znaleźć w referencjach projetu: Nadajemy usłudze nazwę, która będzie obowiązywała lokalnie w kliencie: UsługaZdalna

• Tworzymy formę zgodnie z rysunkiem: • Oprogramowujemy metodę button1_Click

private void button1_Click(object sender, EventArgs e) { try { UslugaZdalna.Service serwis = new WebServiceClient.UslugaZdalna.Service(); //utworzenie obiektu reprezentującego // zdalną usługę listBox1.Items.Add("Tekst otrzymany od serwisu:"); listBox1.Items.Add(serwis.PierwszaMetoda("___Tekst wyslany do serwisu___")); // Wywołujemy zdalną metodę // Wywołanie następuje przez lokalny // obiekt

serwis

} } { catch (Exception ex) MessageBox.Show(ex.Message); // obsługa błędów }

Wynik działania programu:

Analogicznie należy oprogramować odpowiednie metody serwisów dostępnych w msn. Należy do referencji dodać serwis: xxx

Różne opcje wywołania serwisu

• Oprócz zastosowania programu w C# możliwe jest również samodzielne napisanie komunikatu SOAP, który będzie wyglądał tak:

POST /WebService1/Service.asmx HTTP/1.1 Host: localhost Content-Type: text/xml; charset=utf-8 Content-Length: length S OAPAction: http://mojeuri.org/PierwszaMetoda string

• Lub wysłanie HTTP POST

POST /WebService1/Service.asmx/PierwszaMetoda HTTP/1.1 Host: localhost Content-Type: application/x-www-form-urlencoded Content-Length:

length lancuch

=

string

Odpowiednie odpowiedzi będą wyglądały następująco

HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length:

length

string

• Odpowiedź na HTTP POST

HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length:

length

string

Podsumowanie wykładu

• Na wykładzie zostały przedstawione cztery technologie umożliwiające tworzenie aplikacji sieciowych:

– Winsock (socket) – System.Net.Sockets w C# – .Net Remoting – WebServices