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;
}