170 likes | 327 Views
Komunikacja przez sieć z wykorzystaniem biblioteki WINSOCK. mgr.inż. Piotr Kaczmarek Instytut Automatyki i Inżynierii Informatycznej. TCP/IP. Stanowi połączenie dwóch protokołów
E N D
Komunikacja przez sieć z wykorzystaniem biblioteki WINSOCK mgr.inż. Piotr Kaczmarek Instytut Automatyki i Inżynierii Informatycznej
TCP/IP • Stanowi połączenie dwóch protokołów • Umożliwia efektywne przesyłanie pakietów między sieciami (IP) oraz zapewnia kontrolę i niezawodność przesyłanie pakietów między dwoma komputerami (TCP) • Protokół ten działa również w sieciach lokalnych w których komputery posiadają adresy IP. • Mechanizm ten można również wykorzystywać do komunikacji między aplikacjami uruchomionymi na pojedynczym komputerze z wykorzystaniem adresu loop back 127.0.0.1 (localhost)
Architektura Klient-Serwer • Architektura ta dotyczy warstwy aplikacji • Wszystkie zasoby zgromadzone są na serwerze i są udostępniane przez niego komputerom klientom wyłącznie w takim zakresie w jakim ich potrzebują, • Architektura ta umożliwia ograniczenie dostępu do pewnych zasobów, hermetyzację • W architekturze tej można stosować wolne komputery klientów z małą pamięcią dyskową, ponieważ większość zadań wykonywana jest w obrębie serwera, zaś komputer klienta służy wyłącznie do wyświetlania wyniku operacji • Komunikacja między serwerem a klientami może odbywać się z wykorzystaniem protokołu TCP/IP, lecz wymaga sprecyzowania interface’u komunikacji
Algorytm komunikacji • Serwer oczekuje na żądania klientów i obsługuje je w określonej kolejności, sam jednak nie inicjuje połączenia • Należy ustalić (jeśli nie korzysta się ze standardowego protokołu (np. http lub ftp) w jaki sposób przekazywać żądania do servera • Należy ustalić jakie zadania leżą w kompetencji servera a jakie klienta • W celu aktualizacji swoich danych to aplikacja klienta musi zgłaszać odpowiednie żądanie
Interface komunikacji • Żądanie przesyłane do serwera, wywołuje w aplikacji działającej na serwerze odpowiednią funkcję, której argumentami stają się dane przesłane przez klienta • Należy zdefiniować wszystkie możliwe typy żądań oraz dane przesyłane przy każdym z tych żądań i odpowiedzi serwera i umieścić je w dwóch klasach interface’u osobno dla klienta i dla serwera
Przykładowa klasa interface’u Pomiędzy klientem i serwerem przesyłane są dane opisane strukturą: struct CSFRAME{ int ID; long DataSize void *Data}; Strona klienta Żądania:ID Opis dane1 PrześlijAktualneDane NULL2 SprawdzDane dane do sprawdzenia (np. Wykonany ruch) Strona serwera Odpowiedzi serveraID opis dane1 AkualneDane Aktualne dane (np. plansza) 2 Błąd kod błędu
Interface - implementacja • Po stronie serwera i klienta powinny znajdować się dwie klasy które realizują komunikację • Powinny one posiadać metody nazwane tak jak żądania klienta (przesłanie żądania klienta wywołuje odpowiednią funkcję na serwerze) Strona klienta Strona serwera class CServerInterface{public: void OdbierzŻądanie(); CPlansza* PrzeslijAktualneDane(); int SprawdzDane(CPlansza* plansza)private: CSFRAME KomunikatOdebrany; CSFRAME KomunikatWysłany; }; class CClientInterface{public: CPlansza* PrzeslijAktualneDane(); int SprawdzDane(CPlansza* plansza)private: CSFRAME KomunikatOdebrany; CSFRAME KomunikatWysłany; };
Strona klienta Interface – implementacja cd. Strona serwera CPlansza* CClientInterface::PrzeslijAktualneDane(){ KomunikatWyslany.ID=1; KomunikatWyslany.DataSize=0; KomunikatWyslany.Data=NULL;WyslijDoServera(KomunikatWyslany); OdbierzZSerwera(&KomunikatOdebrany); if(KomunikatOdebrany.ID==1) return KomunikatOdebrany. Data;else return NULL;}int CClientInterface:: SprawdzDane(CPlansza* plansza){ KomunikatWyslany.ID=2; KomunikatWyslany.DataSize=sizeof(CPlansza); KomunikatWyslany.Data=plansza;WyslijDoServera(KomunikatWyslany);OdbierzZSerwera(&KomunikatOdebrany); return (int*) KomunikatOdebrany. Data;} CPlansza* CServerInterface:: OdbierzŻądanie(){CPlansza *plansza;int KodBladu; CzekajNaDaneOdKlienta(&KomunikatOdebrany); switch(KomunikatOdebrany.ID){ case 1: plansza=PrzeslijAktualneDane(); KomunikatWyslany.ID=1; KomunikatWyslany.DataSize=sizeof(CPlansza); KomunikatWyslany.Data=plansza;case 2: KodBladu= SprawdzDane(KomunikatOdebrany.Data) KomunikatWyslany.ID=2; KomunikatWyslany.DataSize=sizeof(int); KomunikatWyslany.Data=&KodBladu;}WyslijDoKlienta(KomunikatWyslany);}
Wykorzystanie gniazd Gniazda – usługi serwera • Standardowo stosuje się gniazda (odpowiadające numerom telefonów) w celu realizacji pewnych usług. Gniazda są identyfikowane przez ich numer, aplikacja klienta może połączyć się z określonym gniazdem serwera 80 – httpd, 21 – ftp, powyżej numeru 1024 znajdują się gniazda z których mogą korzystać inne aplikacje • Połączenie z gniazdem można uzyskać przez wpisanie adresu IP i nr gniazda w postaci xxx.xxx.xxx:gniazdo
Inicjalizacja WinSock (klient/server) // Initialize Winsock. WSADATA wsaData; int iResult = WSAStartup( MAKEWORD(2,2), &wsaData ); if ( iResult != NO_ERROR ) printf("Error at WSAStartup()\n"); Ten kod musi zostać wywołany jednorazowo przy uruchomieniu aplikazcji wykorzystującej bibliotekę WinSock, dodatkowo należy dołączyć plik nagłowkowy #include "winsock2.h"
Tworzenie nienazwanego gniazda (klient/server) // Create a socket. SOCKET m_socket; m_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); if ( m_socket == INVALID_SOCKET ) { printf( "Error at socket(): %ld\n", WSAGetLastError() ); WSACleanup(); return; } Port stworzony przez klienta otrzymuje numer w chwili połączenia z serwerem. Numer portu na którym serwer nasłuchuje musi zostać przydzielony w następnym kroku
Połączenie z nazwanym portem (serwer) // Bind the socket. sockaddr_in service; service.sin_family = AF_INET; service.sin_addr.s_addr = inet_addr( "127.0.0.1" ); service.sin_port = htons( 27015 ); if ( bind( m_socket, (SOCKADDR*) &service, sizeof(service) ) == SOCKET_ERROR ) { printf( "bind() failed.\n" ); closesocket(m_socket); return; } Inet_addr(„IP”) przekształca ciąg znaków zwierających adres IP komputera do właściwej postaci (IN_ADDR) Do przypisania adresu można rwnież wykorzystać nazwę hosta (jeśli jest zarejestrowana w DNS: gethostbyname( „www.wp.pl”) htons konwertuje numer portu dany jako unsigned short na numer w formacie standardu TCP/IP (big-endian) Po wywołaniu funkcji bind na serwerze zostaje aktywowany port o wybranym numerze
Serwer oczekuje na żądanie klienta // Listen on the socket. if ( listen( m_socket, 1 ) == SOCKET_ERROR ) printf( "Error listening on socket.\n"); Serwer „nasłuchuje” na wybranym porcie (m_sock)., drugi argument funkcji listen określa długość kolejki zgłoszeń, dla aplikacji z wieloma klientami należy ją ustawić na wartość >1 listen pozostaje zawieszona tak długo jak nie pojawi się żądanie ze strony klienta
Połączenie klienta z serwerem (klient) sockaddr_in clientService; clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr( "127.0.0.1" ); clientService.sin_port = htons( 27015 ); if ( connect( m_socket, (SOCKADDR*) &clientService,sizeof(clientService) ) == SOCKET_ERROR) { printf( "Failed to connect.\n" ); WSACleanup(); return; } Wywołanie connect powadzi do nawiązania połączenia z serwerem zlokalizowanym pod określonym adresem (clientService) i słuchającym na wybranym porcie (27015). Serwer dla celu tego połączenia dedykuje odrębny port (jego adres przypisany zostaje do m_sock). Port nasłuchu nie służy do komunikacji z klientami a jedynie do nawiązania połączenia!!!
Akceptacja połączenia przychodzącego (serwer) // Accept connections. SOCKET AcceptSocket; printf( "Waiting for a client to connect...\n" ); while (1) { AcceptSocket = SOCKET_ERROR; while ( AcceptSocket == SOCKET_ERROR ) { AcceptSocket = accept( m_socket, NULL, NULL ); } printf( "Client Connected.\n"); break; } Po tym jak funkcja listen została przerwana przez żądanie ze strony klienta, accept aprobuje połączenie i dedykuje dla niego nowy port (AcceptSocket)
Wysyłanie i odbiór danych (klient/server) Odbiór danych: #define BUFF_LEN 32int bytesRecv = SOCKET_ERROR; char recvbuf[BUFF_LEN] = ""; //bufor danych//Odbiera dane i zapisuje je do bufora. //Zwraca ilość odebranych bajtów (bytesRecv) bytesRecv = recv( m_socket, recvbuf, BUFF_LEN, 0 ); Wysyłanie danych: int bytesSent;char sendbuf[] = „ten tekst zostanie wyslany” bytesSent = send( m_socket, sendbuf, strlen(sendbuf), 0 ); Dla serwera należy zamiast m_sock wykorzystać port stworzony do komunikacji z klientem AcceptSocket !!!!
WSAStartup(...)socket(...) WSAStartup(...)socket(...) bind(...) listen(...) accept(...) recv(...) Komunikacja raz jeszcze connect(...) send(...) send(...) recv(...) Czarne strzałki wskazują miejsca, w których powinny znaleźć się poszczególne funkcje wykorzystywane do komunikacji Czerwone strzałkipokazują przepływ danych między klientem a serwerem