Programowanie Usług Sieciowych

Download Report

Transcript Programowanie Usług Sieciowych

Programowanie Usług
Sieciowych
Tematyka wykładu
Wprowadzenie do Informatyki
Podstawy Programowania
Programowanie Obiektowe
Inżynieria Oprogramowania I
Podstawy Sieci Komputerowych
Inżynieria Oprogramowania II
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
Model DoD
(Department of
Defense )
aplikacji
prezentacji
aplikacji
sesji
pr. 2 - 5
transportowa
transportowa
sieciowa
sieciowa
łącza danych
fizyczna
dostępu do sieci
pr. 1
Pojęcia podstawowe – warstwy
Model DoD
(Department of
Defense )
aplikacji
transportowa
zastosowania
TCP
UDP
sieciowa
IPv4, IPv6
dostępu do sieci
dostępu do sieci
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
ping
traceroute
IPv6
Prog 1
Prog 2
Prog 1
TCP
UDP
IPv4
IPv6
Prog 2
traceroute
ping
ICMP
Warstwa dostępu
do sieci
ICMPv6
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 WinSock (w Dev-Cpp plik
nagłówkowy winsock.h (i winsock2.h) jest
położony w c:\dev-cpp\include\
• 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 <iostream>
#include <winsock.h>
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
wVersion
WORD
wHighVersion
char
szDescription[WSADESCRIPTION_LEN+1]
char
szSystemStatus[WSADESCRIPTION_LEN+1]
unsigned short iMaxSockets
unsigned short iMaxUdpDg
char
* 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
wVersion;
WORD
wHighVersion;
char
szDescription[WSADESCRIPTION_LEN+1];
char
szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char *
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 <iostream>
#include <winsock.h>
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
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;
}
1 cd
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
Gniazdo n
Gniazdo n
Aplikacja n
Aplikacja 3
Gniazdo 3
Gniazdo 3
Aplikacja 3
Aplikacja 2
Gniazdo 2
Gniazdo 2
Aplikacja 2
Aplikacja 1
Gniazdo 1
Gniazdo 1
Aplikacja 1
Host
Host
Adres ip: 192.168.1.2
Adres ip: 192.168.1.2
Komunikacja UDP
Klient
Klient
Datagram UDP
Datagram UDP
Do kogo wysyłamy
Do kogo wysyłamy
Do którego gniazda
Do którego gniazda
Kto wysyła
Kto wysyła
Z którego gniazda
Z którego gniazda
Datagram UDP
IPv4/
IPv6
Do kogo wysyłamy
Kto wysyła
Datagram UDP
IPv4/
IPv6
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()
bind()
recvfrom()
recvfrom()
Blokuje do czasu nadejścia
datagramu od klienta
Przetwarzanie żądania
close()
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 32bitowego 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 <iostream>
#include <winsock.h>
Program 2 serwer
1.
2.
using namespace std;
void DatagramServer(short nPort);
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 załadowana do pamięci pokrywa się
z tą o którą „prosiliśmy”
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.
void DatagramServer(short nPort)
{
SOCKET theSocket;
theSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (theSocket == 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(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;
Program 2 serwer
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
(-1)
#define SOCK_STREAM
1
#define SOCK_DGRAM
2
#define SOCK_RAW
3
#define SOCK_RDM
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;
// AF_INET - jezeli IPv4, ale ogólnie tak jak w socket
u_short
sin_port;
// wybieramy na którym porcie chcemy się łączyć
struct in_addr sin_addr;
// struktura zdefiniowana poniżej
char sin_zero[8];
// to jest tylko wypełnienie, żeby struktura miała taką samą długość jak
struktutra sockaddr:
struct sockaddr {
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
sockaddr_in
short
sin_family
u_short
sin_port
struct in_addr
sin_addr
char
sin_zero[8]
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
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.
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;
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
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));
//określa rozmiar gniazdowej struktury
adresowej
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, // Socket na którym będziemy odbierać
dane
szBuf,
// Bufor do którego zapiszemy dane
sizeof(szBuf),
// 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,
// socket przez który będziemy
// wysyłać dane
// wskaźnik do bufora, z którego
szBuf,
wysyłamy dane
strlen(szBuf),
// długość bufora
0,
// flagi
(LPSOCKADDR)&saClient,
// gniazdowa struktura adresowa do
której dane wysyłamy
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
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.
#include <string.h>
#include <winsock.h>
#include <iostream>
#include <cstdlib>
using namespace std;
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;
};
Program 2 klient
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;
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;
}
Program 2 klient
64. memset(szBuf, 0, sizeof(szBuf));
65.
int nFromLen;
66.
recvfrom(theSocket, szBuf, sizeof(szBuf), 0, (LPSOCKADDR)&saServer, &nFromLen);
63.
if (nRet == SOCKET_ERROR)
64.
{
65.
PRINTERROR("recvfrom()");
66.
closesocket(theSocket);
67.
return;
68.
}
69.
cout << "Dane otrzymane z serwera : " << szBuf << endl;
70.
closesocket(theSocket);
71.
return;
72. }
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.
Struktura hostent
Oficjalna nazwa
(kanoniczna) stacji
(rekord A)
hostent
char
*h_name
Alias 1 \0
char
**h_aliases
Alias 2 \0
int
h_addrtype
int
h_length
char
**h_addr_list
tablica aliasów
Alias n \0
rodzaj adresu stacji
IPv4/IPv6
AF_INET
rozmiar adresu (IPv4)
lub 16 (IPv6) bajtów
4
in_addr
Adres ip 1
Adres ip 2
Adres ip 3
h_length
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
Gniazdo n
Gniazdo n
Aplikacja n
Aplikacja 3
Gniazdo 3
Gniazdo 3
Aplikacja 3
Aplikacja 2
Gniazdo 2
Gniazdo 2
Aplikacja 2
Aplikacja 1
Gniazdo 1
Gniazdo 1
Aplikacja 1
Host
Host
Adres ip: 192.168.1.2
Adres ip: 192.168.1.2
Ustanawianie połączenia
(uzgadnianie trójfazowe)
Klient
socket, connect
Serwer
SYN J
socket, bind,
listen, accept
SYN K, ACK J+1
connect
(powraca)
1.
2.
3.
4.
SYN K, ACK K+1
accept
(powraca)
Serwer musi być gotowy na przyjęcie połączenia (funkcje: socket, bind, listen) –
tzw. otwarcie bierne połączenia (passive open)
Klient rozpoczyna otwarcie aktywne połączenia (active open), funkcja 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
połączenie
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) maksymalny rozmiar segmentu –
jeśli określi się tę opcję to oprogramowanie TCP informuje jaki jest
maksymalny rozmiar segmentów danych jakie można przesyłać
przez dane połączenie
2. 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*1014. Opcja ta
może być wysłana tylko przez klienta i musi być potwierdzona
przez serwer.
3. 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
close
Serwer
FIN M
read przekazuje 0
(zamknięcie aktywne)
potw. M+1
FIN N
(zamknięcie bierne)
close
potw. N+1
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
2.
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 )
1.
Diagram przejść między stanami dla połączenia TCP
CLOSED
otwarcie bierne
LISTEN
otwarcie jednoczesne
SYN_RCVD
SYN_SENT
wysł, SYN, ACK
ESTABLISHED
Odb: FIN
Wysł: ACK
zamkniecie lub
przekroczenie
czasu
oczekiwania
CLOSE_WAIT
wysł: FIN
Zamknięcie aktywne
FIN_WAIT_1
wysł: ACK
Odb: FIN
Odb: ACK
FIN_WAIT_2
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:
–
–
–
•
•
•
•
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
–
–
–
•
•
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
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
Wymiana pakietów w połączeniu TCP
socket,
Serwer
connect
cocket, bind, listen,
accept
SYN J, mss = 1460
SYN_SENT
SYN K, potw. J+1, mss = 1024
SYN_RCVD
Connect powraca
ESTABLISHED
Potw. K+1
Accept powraca,
ESTABLISHED
write
Dane (żądanie)
read blokuje
Dane (odpowiedź),
ACK dla żądania
read powraca
read blokuje
read powraca
write
read blokuje
ACK dla odpowiedzi
close
FIN_WAIT_1
FIN M
CLOSE_WAIT
Potw. M+1
FIN_WAIT_2
TIME_WAIT
FIN N
read przekazuje 0
close
LAST_ACK
Potw. N+1
CLOSED
Analogia uzgadniania trójfazowego
(połączenie telefoniczne)
TCP
socket()
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
serwer tcp
klient tcp
socket()
socket()
bind()
connect()
listen()
send()
accept()
Blokuje do chwili
nawiązania połączenia
recv()
Przetwarzanie żądania
recv()
closesocket
Dane (odpowiedź)
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. 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)
2. 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. 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
do LISTEN)
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ń:
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 – proponuje się zmianę jego znaczenia na maksymalną
liczbę połączeń nawiązanych dla danego gniazda odsyłanych przez system do kolejki. Wtedy wartość ta
mogłaby być mniejsza
1.
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.
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.
#include <stdio.h>
#include <winsock.h>
#include <iostream>
#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;
}
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.
void StreamClient(char *szServer, short nPort)
{
cout << endl << "Datagram Client sending to server: " << szServer << endl;
cout << "on port: " << nPort << endl;
LPHOSTENT lpHostEntry;
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;
}
Program 3 klient
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
char szBuf[256];
strcpy(szBuf, "From the Client
nRet = send(theSocket, szBuf, strlen(szBuf), 0);
if (nRet == SOCKET_ERROR)
{
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;
Program 3 klient
}
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
37
Program 3 klient
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 <stdio.h>
#include <iostream>
#include <winsock.h>
using namespace std;
void StreamServer(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;
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;
}
Program 3 serwer
27.
28.
29.
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.
void StreamServer(short nPort)
{
SOCKET
listenSocket;
listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
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;
}
Program 3 serwer
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)
{
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,
sizeof(szBuf),
0);
if (nRet == INVALID_SOCKET)
{
PRINTERROR("recv()");
closesocket(listenSocket);
// zamykamy gniazdo
closesocket(remoteSocket);
return;
}
cout << "Dane otrzymane od klienta : " << szBuf << endl;
strcat(szBuf, "From the Server");
nRet = send(remoteSocket, szBuf, strlen(szBuf), 0);
closesocket(remoteSocket);
closesocket(listenSocket);
return;
}
Program 3 serwer
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
Program 3 serwer
48
nLen = sizeof(SOCKADDR); ;
ustalamy rozmiar gniazdowej struktury adresowej
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.
Jest 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
Program 3 serwer
69-74
obsługa błędów
75
memset(szBuf, 0, sizeof(szBuf));
czyszczenie bufora
76
nRet = recv(remoteSocket,
szBuf,
sizeof(szBuf),
odbiór danych przez gniazdo połączone do bufora
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
0);
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
Aplikacja blokuje się
na wywołaniu funkcji
recvfrom
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
System operacyjny
Funkcja systemowa
recvfrom
recvfrom
Brak gotowego datagramu
WSAEWOULDBLOCK
Funkcja systemowa
Brak gotowego datagramu
Aplikacja blokuje się
na wywołaniu funkcji
recvfrom
WSAEWOULDBLOCK
recvfrom
Funkcja systemowa
Oczekiwanie
na dane
Datagram gotowy
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.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
SOCKET theSocket;
theSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (theSocket == INVALID_SOCKET)
{
PRINTERROR("socket()");
return;
}
u_long iMode = 1;
nRet = ioctlsocket(theSocket, FIONBIO, &iMode);
if (nRet == SOCKET_ERROR)
cout << "Umieszczenie gniazda 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);
Program będący przykładem
modelu nieblokującego,
wykorzystujący tryb nieblokujący
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
Program użytkowy
Aplikacja
blokuje się
na
wywołaniu
funkcji
select,
czekając na
gotowość do
czytania z
jednego z
wielu gniazd
Proces
blokuje się
podczas
kopiowania
danych do
bufora
programu
użytkowego
select
System operacyjny
Funkcja
systemowa
Brak gotowego datagramu
Oczekiwanie
na dane
Dane gotowe
do odczytania
recvfrom
Przetwarzanie
datagramu
Funkcja
systemowa
Wynik
pomyślny
Datagram gotowy
Kopiowanie datagramu
Kopiowanie zakończone
Kopiowanie
danych z
systemu do
procesu
użytkownika
Model zwielokrotniony
• Zasada działania modelu zwielokrotnionego:
– Aplikacja blokuje się po wywołaniu funkcji select czekając aż będzie
można pobrać dane z gniazda. W chwili, kiedy dane są gotowe do
odebrania zostaje wywołana funkcja recvfrom. 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
jednego w modelu blokującym.
• W modelu zwielokrotnionym korzystamy z funkcji select
postaci:
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 readfs i oczekiwać na zakończenie funkcji select. Gdy
wywołanie funkcji select zakończy się trzeba sprawdzić, czy gniazdo nadal
jest częścią zbioru readfs. Jeżeli tak, to z gniazda można odczytywać.
Program serwera korzystający z
instrukcji select
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <iostream>
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;
//
SOCKET fd_array[FD_SETSIZE];
// } fd_set;
// 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
WSACreateEvent
WSAEventSelect
WSAWaitForMultipleEvents
proces nadal
się wykonuje
Oczekiwanie
na dane
Dane gotowe
do odczytania
Proces
blokuje się
podczas
kopiowania
danych do
bufora
programu
użytkowego
recvfrom
Przetwarzanie
datagramu
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
Datagram gotowy
proces nadal
się wykonuje
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
Klient TCP
Label1
texBox1
numericUpDown1
Label2
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;
Klient TCP
// dodajemy niezbędne przestrzenie nazw
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");
// wpisujemy tekst do strumienia
listBox1.Items.Add("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());
Serwer TCP
Button3
textBox1
listBox1
Button1
Button2
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; // tworzymy pole do przechowywania obiektu TcpListener
private TcpClient klient = null;
// tworzymy pole do przechowywania obiektu TcpClient
private void button2_Click(object sender, EventArgs e)
{
IPAddress adresIP = null;
// zmienna typu IPAddress do przechowywania adresu IP serwera
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"); // jeśli coś poszłoby źle wyświetlamy błąd
textBox1.Text = String.Empty; // i czyścimy okno
return;
}
int port = System.Convert.ToInt16(numericUpDown1.Value); // numer portu odczytujemy z numeric UpDown
try
{
serwer = new TcpListener(adresIP, port); //tworzymy obiekt serwera TCP nasłuchującego
serwer.Start();
// strartujemy serwer, rozpoczynamy nasłuchiwanie
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;
// 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 = new BinaryWriter(ns); // i obiekt BinaryWriter
listBox1.Items.Add("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
serwer.Stop();
// zatrzymujemy serwer
}
catch (Exception ex)
{
listBox1.Items.Add("Blad inicjalizacji serwera"); //obsługa błędów
MessageBox.Show(ex.ToString(), "Blad");
}
}
Serwer TCP
Serwer TCP
private void button3_Click(object sender, EventArgs e)
{
serwer.Stop();
//możemy również zakończyć pracę serwera przyciskiem 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
•
•
•
•
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
Obiekt X
Metoda M
serwer
(program) Y
kanał
serwer
(komputer)
Klient
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
<?xml version="1.0" encoding="UTF-8" ?> - <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:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:suds="http://www.w3.org/2000/wsdl/suds" 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>- <message
name="ZdalnyObiekt.ZwrocTekstOutput"> <part name="return" type="xsd:string" /> </message><portType name="ZdalnyObiektPortType">- <operation name="ZwrocTekst" parameterOrder="tekst"> <input
name="ZwrocTekstRequest" message="tns:ZdalnyObiekt.ZwrocTekstInput" /> <output
name="ZwrocTekstResponse" message="tns:ZdalnyObiekt.ZwrocTekstOutput"
/> </operation> </portType>- <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"
/> </input>- <output name="ZwrocTekstResponse"> <soap:body use="encoded"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://schemas.microsoft.com/clr/nsassem/KlasaRemoting.ZdalnyObiekt/KlasaRemoting3"
/> </output> </operation> </binding>- <service name="ZdalnyObiektService">- <port
name="ZdalnyObiektPort" binding="tns:ZdalnyObiektBinding"> <soap:address
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:
listBox1
Button1
numericUpDown1
textBox1
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"
type="KlasaRemoting.ZdalnyObiekt, KlasaRemoting3" />
<activated type="KlasaRemoting.ZdalnyObiekt, KlasaRemoting3" />
</service>
<channels>
<channel ref="http" port="12345"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Web Services
Co to jest WebService?
• Web service jest systemem zaprojektowanym do
obsługi współpracy między maszynami przez
sieć. Ma interfejs opisany w języku zrozumiałym
dla komputera (tym językiem jest WSDL). Inne
systemy współpracują z Web serwisem w przy
użyciu komunikatów SOAP, zazwyczaj
przekazywanych przy użyciu HTTP i
serializowanych przy użyciu XML w połączeniu z
innymi standardami sieci Web
Czym są Web serwisy?
•
•
•
•
•
Serwisami, które używają otwartych protokołów
samoopisującymi się serwisami
Są dostępne przez usługę UDDI
Mogą być wykorzystane przez inne aplikacje
Wykorzystują HTTP (jest protokołem transportu)
i XML (jest językiem wymiany) do wymiany
danych między platformami
Cechy WebService
• Umożliwia ustandaryzowaną komunikację
między programami napisanymi w różnych
językach i dla różnych platform
• Wykorzystuje istniejące standardy
sieciowe
WebService – podstawowe
pojęcia:
SOAP
•
•
•
•
•
•
•
•
•
Protokół komunikacyjny usług WebServices
Jedyny fragment soap którego implementacja jest wymagana to format XML
komunikatów. Jeżeli mamy fragment XML zawierający elementy SOAP – mamy
komunikat SOAP
Soap opisuje sposób prezentacji danych w postaci XML oraz sposób zdalnego
wywoływania procedur.
SOAP umożliwia opakowywanie dokumentu XML
Określa wygląd komunikatu HTTP zawierający komunikat SOAP (SOAP nie wymaga
http, można korzystać z SMTP, TCP/IP) jednak większość usług wykorzystuje http.
Przeważnie tworzenie komunikatu SOAP odbywa się automatycznie na poziomie
narzędzi wyższego poziomu (nie jest konieczne jego „ręczne” tworzenie).
Został zaimplementowany na wielu różnych platformach sprzętowych i
programowych.
Obsługiwane typy wywołań funkcji i typy parametrów zależą od implementacji SOAP
(ewentualne niezgodności wynikają z przeważnie z implementacji a nie samego
protokołu SOAP)
Zakłada się że bezpieczeństwo protokołu SOAP jest zapewnione przez protokół
transportowy w związku z tym standard nie opisuje kwestii bezpieczeństwa.
WSDL
• Web Service Description Language – język opisu Web Serwisu
• Jest to dokument XML opisujący zbiór komunikatów SOAP oraz
sposób wymiany tych komunikatów. Określa on jednoznacznie co
powinien zawierać komunikat żądania oraz co będzie znajdować się
w komunikacie odpowiedzi
• Stosowana notacja XML jest niezależna od języków programowania
i platformy.
• Określa zawartość komunikatu, adres pod którym usługa jest
dostępna, protokół komunikacyjny
URI
• Adres pod którym jest dostępny WebSerwice
UDDI
•
•
•
•
•
•
•
Universal Discovery Description and Integration – spis dostawców usług WebService
(książka telefoniczna zawierająca spis usług dostarczanych przez usługodawców).
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
WebService kontra .NET
Remoting
Web Service
.NET Remoting
Niezależny od platformy
Zależny od platformy
Serializacja przy użyciu XmlSerializer
Serializacja przy użyciu IFormatter
Łatwiejszy model programistyczny
Trudniejszy model programistyczny
Szeroki zasięg
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)
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
targetNamespace="http://www.webserviceX.NET"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"
xmlns:tns="http://www.webserviceX.NET"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<wsdl:types>
<s:schema elementFormDefault="qualified"
targetNamespace="http://www.webserviceX.NET">
<s:element name="GetWeather">
<s:complexType>
<s:sequence>
<s:element maxOccurs="1" minOccurs="0" name="CityName" type="s:string"/>
<s:element maxOccurs="1" minOccurs="0" name="CountryName" type="s:string"/>
</s:sequence>
</s:complexType>
</s:element>
<s:element name="GetWeatherResponse">
<s:complexType>
<s:sequence>
<s:element maxOccurs="1" minOccurs="0" name="GetWeatherResult"
type="s:string"/>
</s:sequence>
</s:complexType>
</s:element>
<s:element name="GetCitiesByCountry">
<s:complexType>
<s:sequence>
<s:element maxOccurs="1" minOccurs="0" name="CountryName" type="s:string"/>
</s:sequence>
</s:complexType>
</s:element>
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 w pole CityName. Jako
wynik wywołania otrzymujemy plik xml
• 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:
<?xml version="1.0" encoding="utf-8" ?>
- <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
xmlns:tns="http://mojeuri.org/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" targetNamespace="http://mojeuri.org/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
- <wsdl:types>
- <s:schema elementFormDefault="qualified" targetNamespace="http://mojeuri.org/">
- <s:element name="PierwszaMetoda">
- <s:complexType>
- <s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="lancuch" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
- <s:element name="PierwszaMetodaResponse">
- <s:complexType>
- <s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="PierwszaMetodaResult" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
</s:schema>
</wsdl:types>
- <wsdl:message name="PierwszaMetodaSoapIn">
<wsdl:part name="parameters" element="tns:PierwszaMetoda" />
</wsdl:message>
- <wsdl:message name="PierwszaMetodaSoapOut">
<wsdl:part name="parameters" element="tns:PierwszaMetodaResponse" />
</wsdl:message>
- <wsdl:portType name="ServiceSoap">
- <wsdl:operation name="PierwszaMetoda">
<wsdl:input message="tns:PierwszaMetodaSoapIn" />
<wsdl:output message="tns:PierwszaMetodaSoapOut" />
</wsdl:operation>
</wsdl:portType>
- <wsdl:binding name="ServiceSoap" type="tns:ServiceSoap">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
- <wsdl:operation name="PierwszaMetoda">
<soap:operation soapAction="http://mojeuri.org/PierwszaMetoda" style="document" />
- <wsdl:input>
<soap:body use="literal" />
</wsdl:input>
- <wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
- <wsdl:binding name="ServiceSoap12" type="tns:ServiceSoap">
<soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />
- <wsdl:operation name="PierwszaMetoda">
<soap12:operation soapAction="http://mojeuri.org/PierwszaMetoda" style="document" />
- <wsdl:input>
<soap12:body use="literal" />
</wsdl:input>
- <wsdl:output>
<soap12:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
- <wsdl:service name="Service">
- <wsdl:port name="ServiceSoap" binding="tns:ServiceSoap">
<soap:address location="http://localhost:51207/WebService1/Service.asmx" />
</wsdl:port>
- <wsdl:port name="ServiceSoap12" binding="tns:ServiceSoap12">
<soap12:address location="http://localhost:51207/WebService1/Service.asmx" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Ciekawsze fragmenty zawarte w
opisie usługi
<?xml version="1.0" encoding="utf-8" ?>
- <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://mojeuri.org/"
xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
targetNamespace="http://mojeuri.org/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
- <s:schema elementFormDefault="qualified" targetNamespace="http://mojeuri.org/">
- <s:element name="PierwszaMetoda">
<s:element minOccurs="0" maxOccurs="1" name="lancuch" type="s:string" />
- <s:element name="PierwszaMetodaResponse">
<s:element minOccurs="0" maxOccurs="1" name="PierwszaMetodaResult" type="s:string" />
- <wsdl:message name="PierwszaMetodaSoapIn">
<wsdl:part name="parameters" element="tns:PierwszaMetoda" />
- <wsdl:message name="PierwszaMetodaSoapOut">
<wsdl:part name="parameters" element="tns:PierwszaMetodaResponse" />
- <wsdl:portType name="ServiceSoap">
- <wsdl:operation name="PierwszaMetoda">
<wsdl:input message="tns:PierwszaMetodaSoapIn" />
<wsdl:output message="tns:PierwszaMetodaSoapOut" />
- <wsdl:binding name="ServiceSoap" type="tns:ServiceSoap">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
- <wsdl:operation name="PierwszaMetoda">
<soap:operation soapAction="http://mojeuri.org/PierwszaMetoda" style="document" />
<soap:body use="literal" />
<soap:body use="literal" />
- <wsdl:binding name="ServiceSoap12" type="tns:ServiceSoap">
<soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />
- <wsdl:operation name="PierwszaMetoda">
<soap12:operation soapAction="http://mojeuri.org/PierwszaMetoda" style="document" />
<soap12:body use="literal" />
<soap12:body use="literal" />
- <wsdl:service name="Service">
- <wsdl:port name="ServiceSoap" binding="tns:ServiceSoap">
<soap:address location="http://localhost:51207/WebService1/Service.asmx" />
- <wsdl:port name="ServiceSoap12" binding="tns:ServiceSoap12">
<soap12:address location="http://localhost:51207/WebService1/Service.asmx" />
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)
// obsługa błędów
{
MessageBox.Show(ex.Message);
}
}
Wynik działania programu:
Analogicznie należy oprogramować odpowiednie
metody serwisów dostępnych w msn. Należy do
referencji dodać serwis: msn
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
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<PierwszaMetoda xmlns="http://mojeuri.org/">
<lancuch>string</lancuch>
</PierwszaMetoda>
</soap:Body>
</soap:Envelope>
• 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
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<PierwszaMetodaResponse xmlns="http://mojeuri.org/">
<PierwszaMetodaResult>string</PierwszaMetodaResult>
</PierwszaMetodaResponse>
</soap:Body>
</soap:Envelope>
• Odpowiedź na HTTP POST
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="http://mojeuri.org/">string</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