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 dane 1 PrześlijAktualneDane NULL 2 SprawdzDane dane do sprawdzenia (np. Wykonany ruch) Strona serwera Odpowiedzi servera ID opis dane 1 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 CClientInterface{ public: CPlansza* PrzeslijAktualneDane(); int SprawdzDane(CPlansza* plansza) private: CSFRAME KomunikatOdebrany; CSFRAME KomunikatWysłany; }; class CServerInterface{ public: void OdbierzŻądanie(); CPlansza* PrzeslijAktualneDane(); int SprawdzDane(CPlansza* plansza) private: CSFRAME KomunikatOdebrany; CSFRAME KomunikatWysłany; };
Interface – implementacja cd. Strona klienta 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); }
Gniazda – usługi serwera 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) #define BUFF_LEN 32 int 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 !!!!
Komunikacja raz jeszcze WSAStartup(...) socket(...) WSAStartup(...) socket(...) bind(...) listen(...) accept(...) recv(...) connect(...) send(...) Komunikacja raz jeszcze send(...) recv(...) Czarne strzałki wskazują miejsca, w których powinny znaleźć się poszczególne funkcje wykorzystywane do komunikacji Czerwone strzałki pokazują przepływ danych między klientem a serwerem