Komunikator – część I – Podstawowe informacje o gniazdach
Transkrypt
Komunikator – część I – Podstawowe informacje o gniazdach
Komunikator – część I – Podstawowe informacje o gniazdach komunikacyjnych Biblioteka wxWidgets rozróżnia 3 klasy gniazd komunikacyjnych: • • • wxSocketServer - gniazdo serwera, służy do nasłuchu żądań połączenia od klientów wxSocketClient – gniazdo komunikacyjne do wymiany danych miedzy klientem a serwerem wxSocketBase – jest klasą bazową dla obu powyższych klas i definiuje prawie całą ich funkcjonalność Gniazdo serwera tworzy się od razu na podanym adresie – obiekcie klasy wxIPV4address, który ma dwa podstawowe pola danych: nazwę lub IP hosta, oraz numer portu na którym serwer będzie nasłuchiwać. Gniazdo klienta tworzy się konstruktorem bezparametrowym. Dopiero przy próbie łączenia się należy podać adres i port serwera docelowego. Po utworzeniu każdego gniazda, należy określić zdarzenia, na jakie ma ono reagować. W tym celu trzeba ustawić flagi odpowiednie dla wybranych zdarzeń i włączyć nasłuch. Trzeba także wpisać makro EVT_SOCKET do tabeli zdarzeń i zdefiniować funkcje obsługi zdarzeń. Każde gniazdo może obsłużyć następujące zdarzenia: • wxSOCKET_INPUT - gdy są dane dostępne do pobrania. • wxSOCKET_OUTPUT - gdy gniazdo jest gotowe do wysłania danych. • wxSOCKET_CONNECTION – zgłoszono żadanie połaczenia (serwer), lub ustanowiono połączenie (klient) • wxSOCKET_LOST - gdy połaczenie zostało zamknięte. Zdarzeniom tym odpowiadają flagi: • wxSOCKET_INPUT_FLAG – włączenie obsługi zdarzenia wxSOCKET_INPUT • wxSOCKET_OUTPUT_FLAG – włączenie obsługi zdarzenia wxSOCKET_OUTPUT • wxSOCKET_CONNECTION_FLAG – włączenie obsługi zdarzenia wxSOCKET_CONNECTION • wxSOCKET_LOST_FLAG – włączenie obsługi zdarzenia wxSOCKET_LOST Wybór zdarzeń, na jakie gniazdo ma reagować, dokonuje się w metodzie SetNotify(), po czym włącza się nasłuch gniazda na wybrane zdarzenia metodą Notify(true). Przykład tworzenia gniazda klienta: wxSocketClient *k = new wxSocketClient(); k->SetEventHandler(*this, ID_SOCKET); k->SetNotify(wxSOCKET_CONNECTION_FLAG | wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG); k->Notify(true); Przykład tworzenia na lokalnym komputerze gniazda serwera, który nasłuchuje na porcie 3000: wxIPV4address adres; adres.Hostname(”localhost”); // lub adres.Hostname(”127.0.0.1”); adres.Service(3000); wxSocketServer *s = new wxSocketServer(adres); s->SetEventHandler(*this, ID_SERVER); s->SetNotify(wxSOCKET_CONNECTION_FLAG | wxSOCKET_LOST_FLAG); s->Notify(true); Obsługa zdarzeń gniazda (w wersji statycznej) wymaga dodania do tabeli zdarzeń następującego makra: EVT_SOCKET(id_gniazda, funkcja_obsługi) Funkcja obsługi zdarzenia pobiera parametr – obiekt klasy wxSocketEvent, w którym kryje się informacja m.in. o typie zdarzenia które wystąpiło. Dzięki temu można osobno oprogramować poszczególne typy zdarzeń, np.: void Okno::Obsluga(wxSocketEvent & event) { switch ( event.GetSocketEvent() ) { case wxSOCKET_INPUT : { // czytanie danych break; } case wxSOCKET_LOST : SetStatusText("połączenie zamkniete",0); break; case wxSOCKET_CONNECTION : SetStatusText("połączenie otwarte",0); break; default : SetStatusText("nie wiem co jest grane...",0); } } Gdy do gniazda serwera nadejdzie żądanie połączenia od klienta, serwer akceptuje połączenie metodą Accept(), tworząc nowe gniazdo komunikacyjne klasy wxSocketClient, na potrzeby komunikacji z danym klientem. Dla nowego gniazda ustawia flagi (odbiór/wysyłka danych i rozłączenie) i włącza jego nasłuch: nowe gniazdo komunikacyjne czeka na dane od klienta. A gniazdo serwera nasłuchuje sobie dalej… switch (event.GetSocketEvent() ) { case wxSOCKET_CONNECTION : { // gdy serwer odebrał żądanie połączenia od klienta gniazdo = new wxSocketBase(); // przydział pamięci na nowe gniazdo komunikacyjne gniazdo = s->Accept(false); // utworzenie nowego gniazda komunikacyjnego if (gniazdo) { // jeśli się powiodło… gniazdo->SetEventHandler(*this,ID_SOCKET); gniazdo->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG); gniazdo->Notify(true); SetStatusText("Połaczony nowy klient, czekam na dane",0); } break; } // inne zdarzenia gniazda serwera } Przesyłanie danych Gniazdo komunikacyjne pobiera do bufora w pamięci, lub wysyła do odbiorcy, określoną liczbę bajtów, nie troszcząc się o to co one zawierają. Najprościej więc najpierw przesłać np. jeden bajt, który określi rozmiar danych, liczbę (0-255). Będzie to informacja o tym, jaki duży bufor przygotować i ile bajtów pobrać/wysłać w następnej przesyłce, która będzie już zawierać dane użytkowe. Przykład pobierania danych: case wxSOCKET_INPUT : { gniazdo->SetNotify(wxSOCKET_LOST_FLAG); // zablokuj gniazdo na zdarzenie odbioru następnych danych unsigned char n; gniazdo->Read(&n,1); // pobierz 1 bajt, zawierający liczbę bajtów nastepnej przesyłki char *b = new char[n]; // przygotuj bufor dla danych, w rozmiarze n gniazdo->Read(b,n); // pobierz n bajtów danych do bufora b wxString bt(b); // konwersja bufora b na wxString WxEdit1->AppendText(bt+’\n’); // wyświetl pobrane dane, zakończ znakiem nowej linii delete []b; SetStatusText("odebrana wiadomość",0); gniazdo->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);//odblokuj zdarzenie INPUT break; }