Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Brian 'Beej' Hall <beej@piratehaven.org>
Tłumaczenie: Bartosz Zapałowski <bartek@klepisko.eu.org>
Biblioteki C++

FAQ

[lekcja] Rozdział 8. FAQ, czyli często zadawane pytania.

Gdzie mogę dostać te pliki nagłówkowe?

Odp: Jeśli nie masz ich jeszcze w swoim systemie, prawdopodobnie ich nie potrzebujesz. Sprawdź podręcznik dla twojej platformy. Jeśli budujesz dla Windows, musisz tylko dodać #include <winsock.h>.

Co mam zrobić, gdy bind() zgłasza "Address already in use"?

Odp: Musisz użyć setsockopt() z opcją SO_REUSEADDR na nasłuchującym gnieździe. Przejrzyj sekcję o bind() oraz sekcję o select() dla przykładów.

Gdzie mogę znaleźć listę otwartych gniazd w systemie?

Odp: Użyj netstat. Przejrzyj strony mana po szczegóły, ale powinieneś otrzymać dobry wynik wpisując:
$ netstat
Jedną trudnością może być określenie, które gniazdo należy do którego programu. :-)

Jak mogę obejrzeć tabelę routingu?

Odp: Uruchom program route (znajduje się w /sbin w większości dystrybucji Linksa) lub netstat -r>.

Jak mogę uruchomić programy klienta i serwera, jeśli mam tylko jeden komputer? Czy nie potrzebuję sieci, by pisać programy sieciowe?

Odp: Na szczęście dla ciebie, praktycznie wszystkie maszyny implementują sieciowe urządzenie zwrotne (tzw. loopback), które siedzi w jądrze i udaje prawdziwą kartę sieciową (to urządzenie widnieje jako "lo" w tabeli routingu).

Załózmy, że jesteś zalogowany na maszynie nazwanej "goat". Uruchom klienta w jednym oknie, a serwer w drugim. Lub wystartuj serwer w tle ("server &") i uruchom klienta w tym samym oknie. Dzięki urządzeniu loopback możesz wywołać zarówno client goat jak i client localhost (ponieważ "localhost" jest najprawdopodobniej zdefiniowany w twoim pliku /etc/hosts) i otrzymasz klienta rozmawiającego z serwerem bez pośrednictwa sieci!

W skrócie, żadne zmiany w kodzie nie są wymagane, by programy sieciowe chodziły na maszynach nie podłączonych do sieci! Hurraa!

Jak mogę stwierdzić, że druga strona zamknęłą połączenie?

Odp: Możesz to stwierdzić za pomocą funkcji recv(), ponieważ recv() zwróci 0.

Jak zaimplementować narzędzie "ping"? Co to jest ICMP? Gdzie mogę się dowiedzieć więcej o surowych ("raw") gniazdach oraz SOCK_RAW?

Odp: Odpowiedzi na wszystkie twoje pytania odnośnie surowych gniazd znajdziesz w książkach W. Richard Stevensa z serii "UNIX Network Programming". Patrz sekcję o książkach w tym przewodniku.

Jak budować dla Windows?

Odp: Najpierw usuń Windows i zainstaluj Linuksa lub BSD. };-). Nie, właściwie po prostu zobacz sekcję o budowaniu dla Windows we wprowadzeniu.

Jak budować dla Solaris/SunOS? Ciągle otrzymuję błędu łączenia, gdy próbuję kompilować!

Odp: Błędy łączenia zdarzają sie, poniważ maszyny Sun nie wkompilowują automatycznie biblioteki gniazd. Patrz sekcję o budowaniu dla Solaris/SunOS we wprowadzniu dla przykładu jak to zrobić.

Dlaczego select() ciągle powraca, gdy otrzyma sygnał?

Odp: Sygnały sprawiają, że blokujące wywołania systemowe zwracają -1 z errno ustawionym na EINTR. Gdy ustawisz funkcję obsługi sygnału za pomocą sigaction(), możesz ustawić flagę SA_RESTART, która powinna zrestartować wywołanie systemowe, po tym jak zostało przerwane.

Naturalnie to nie zawsze działa.

Moim ulubionym rozwiązaniem jest użycie konstrukcji goto. Wiesz, że to irytuje profesorów, więc używaj jej!

C/C++
select_restart:
if(( err = select( fdmax + 1, & readfds, NULL, NULL, NULL ) ) == - 1 ) {
    if( errno == EINTR ) {
        // jakiś sygnał właśnie nam przerwał robotę, więc restartujemy
        goto select_restart;
    }
    // obsłuż prawdziwy błąd tutaj:
    perror( "select" );
}

Oczywiście nie musisz używać goto w tym przypadkue; możesz użyć innych struktur, by to kontrolować. Ale myślę, że wyrażenie goto jest czytelniejsze.

Jak zaimplementować przedawnienie ("timeout") funkcji recv()?

Odp: Użyj select()! To ci pozwoli podać parametr przedawnenie dla deskryptora gniazda, z którego chcesz odczytywać. Możesz też owinąc całą tą funkcjonalność w jedną funkcję, taką jak ta:

C/C++
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>

int recvtimeout( int s, char * buf, int len, int timeout )
{
    fd_set fds;
    int n;
    struct timeval tv;
   
    // ustaw zestaw deskryptorów plików
    FD_ZERO( & fds );
    FD_SET( s, & fds );
   
    // ustaw strukturę timeval dla przedawnienia
    tv.tv_sec = timeout;
    tv.tv_usec = 0;
   
    // czekaj na przedawnienie lub na odczytanie danych
    n = select( s + 1, & fds, NULL, NULL, & tv );
    if( n == 0 ) return - 2; // przedawnienie!
   
    if( n == - 1 ) return - 1; // błąd
   
    // danę już muszą być, więc wywołaj normalnie recv()
    return recv( s, buf, len, 0 );
}

// Przykładowe wywołanie recvtimeout():
//...
n = recvtimeout( s, buf, sizeof( buf ), 10 ); // przedawnienie po 10 sekundach

if( n == - 1 ) {
    // wystąpił błąd
    perror( "recvtimeout" );
}
else if( n == - 2 ) {
    // wystąpiło przedawnienie
} else {
    // mamy jakieś dane w buforze
}
//...
Zauważ, że recvtimeout() zwraca -2 w przypadku przedawnienia. Dlaczego nie zwraca 0? Jeśli sobie przypomnisz, to zwrócona wartość 0 przez wywołanie recv() oznacza, że druga strona zamknęła połączenie. Więc ta wartość jest już zajęta, a -1 oznacza "błąd", więc wybrałem -2 jako moja informacja o przedawnieniu.

Jak mam szyfrować lub kompresować dane przed ich wysłaniem do gniazda?

Odp: Jedną z prostych dróg szyfrowania byłoby skorzystanie z SSL (secure sockets layer - bezpieczna wartswa gniazd), ale wychodzi to poza ramy tego przewodnka.

Ale zakładając, że chcesz dołączyć lub zaimplementować swój własny system kompresujący lub szyfrujący, jest to po prostu kwestia przemyślenia sposobu przechodzenia danych przez poszczególne kroki pomiędzy dwoma końcami. Każdy krok zmienia dane w jakiś sposób.
  • serwer odczytuje dane z pliku (lub czegokolwiek)
  • serwer szyfruje dane (dodajesz tą część)
  • serwer wysyła zaszyfrowane dane
Teraz w drugą stronę:
  • klient odbiera zaszyfrowane dane
  • klient odszyfrowuje dane (dodajesz tą część)
  • klient zapisuje dane do pliku (lub czegokolwiek)
Możesz również zrobić kompresję zamiast punktu, w którym robisz szyforwanie. Lub możesz wykonać te dwie czynności! Pamiętaj tylko o tym, że najpierw się kompresuje, potem szyfruje. :)

Dopóki klient poprawnie odtworzy to, co stworzył serwer, dane będą dobre, nieważne ile dodasz kroków pośrednich.

Tak więc, wszystko co musisz zrobić, by wykorzystać mój kod, to znalezieni miejsca, między którym dane są odczytywane i wywsyłane (używając send()) przez sieć), i dodanie kawałka kodu w tym miejscu, który będzie szyfrował.

Co to jest "PF_INET", które ciągle widzę? Czy jest to powiązane z AF_INET?

Odp: Tak, jest powiązane. Przejrzyj sekcję o socket() jeśli interesują cię szczegóły.

Jak mam napisać serwer, który przyjmuje komendy shella od klienta i je wykonuje?

Odp: Dla prostoty, załóżmy, że klient łączy się (connect()), wysyła dane (send()), i zamyka (close()) połączenie (tak więc nie ma kolejnych wywołań bez ponownego połączenia).

Klient działą wg. następującego kolejności:
  • connect() - połączenie z serwerm
  • send("/sbin/ls > /tmp/client.out")
  • close() - zamknięcie połączenia

Tymczasem, serwer przetwarza dane i je wykonuje:
  • accept() - zaakcpetowanie połączenia od klienta
  • recv(str) - wczytanie tekstu komendy
  • close() - zamknięcie połączenia
  • system(str) - wywołanie komendy

Strzeż się! Pozwalanie serwerowi wykonywać wszystko co każe klient to jak danie zdalnego dostępu do shella i pozwolenie ludziom robienia różnych rzeczy na twoim koncie, za każdym razem gdy się łączą z serwerem. Na przykład, w powyższym przykładzie, co będzie, gdy klient wyśle "rm -rf ~"? Skasuje to wszystkie pliki z twojego konta, ot co się stanie!

Tak więc jesteś już mądry i zapobiegasz wykonywania dowolnej komedny poza kilkoma wybranymi narzędziamy, o których wiesz, że są bezpieczne, jak np. narzędzie foobar:
C/C++
if( !strcmp( str, "foobar" ) ) {
    sprintf( sysstr, "%s > /tmp/server.out", str );
    system( sysstr );
}
Ale nadal nie jesteś bezpieczny, niestety: co jeśli klient wyśle "foobar; rm -rf ~"? Najbezpieczniejszą rzeczą jest napisanie funkcji, która wstawia znak ucieczki ("\") przez każdym niealfanumerycznym znakiem (włączając w to spacje, jeśli zachodzi taka potrzeba) w argumentach dla komendy.

Jak widzisz, bezpieczeństwo jest dużą sprawą, gdy serwer zaczyna uruchamiać rzeczy, które podsyła klient.

Wysyłam duże porcje danych, ale gdy wywołuję recv(), funkcja zwraca tylko 536 bajtów lub 1460 bajtów na raz. Ale jeśli uruchomię to na mojej lokalnej maszynie, funkcja zwraca wszystkie dane na raz. Co się dzieje?

Odp: Jest to wina MTU -- maksymalny rozmiar pakietu, z jakim sobie poradzi fizyczne medium. Na maszynie lokalnej, używasz urządzenia loopback, które obsłuży 8 KB lub więcej bez problemu. Ale na ethernet, które może obsłużyć tylko 1500 bajtów razem z nagłówkiem, trafiasz na limit. Przez modem, z MTU wynoszącym 576 bajtów (również z nagłówkiem), trafiasz na jeszcze mniejszy limit.

Po pierwsze, musisz się upewnić, że wszystkie dane są wysyłane (zobacz implementację funkcji sendall() po szczegóły). Jak już jesteś tego pewien, musisz wywoływać recv() w pętli, dopóki wszystkie dane nie są odczytane.

Przeczytaj sekcję Enkapsulacja danych po szczegóły o odbieraniu pełnych pakieków używając wielokrotnych wywołań recv().

Siedzę na maszynie z Windows i nie mam wywołania fork() jak również żadnego z struct sigaction. Co robić?

Odp: Jeśli mają gdzieś być, to będą w bibliotekach POSIX, które możesz mieć dostarczone z kompilatorem. Ponieważ nie mam maszyny z Windows, nie mogę odpowiedzieć, ale wydaje mi się, że Microsoft ma warstwę kompatybilności z POSIX i tam właśnie może być fork() (i pewnie też sigaction).

Przeszukaj pomoc, która przyszła z VC++ za słowem "fork" lub "POSIX" i zobacz, czy daje ci to jakieś wskazówki.

Jeśli to w ogóle nie działa, zapomnij o fork()/sigaction i zastąp je odpowiednikami Win32: CreateProcess(). Nie wiem jak używać CreateProcess() -- pobiera ona miliony argumentów, ale powinno to byc ujęte w pomocy VC++.

Od tłumacza: jest projekt Cygwin, którego celem jest przeniesienie bibliotek POSIX jak również programów Uniksowych na system Windows. Tam też znajdziesz wywołania fork() czy sigaction.

Jak bezpiecznie wysyłać dane przez TCP/IP przy użyciu szyfrowania?

Odp: Sprawdź projekt OpenSSL.

Jestem za firewallem -- jaki mam podać adres IP ludziom z zewnątrz firewalla, by mogli połączyć się z moją maszyą?

Odp: Niestety, celem firewalla jest zabronienie ludziom z zewnątrz łączenia się z maszynami z wewnątrz, więc pzowolenie ludziom z zewnątrz na połaczenie się z maszyną lokalną jest naruszeniem bezpieczeństwa.

Ale nie wszystko stracone. Nadal możesz się łączyć przez firewall, jeśli ten wykonuje maskaradę lub NAT lub coś takiego. Po prostu zaprojektuj swoje programy tak, by to one zawsze nawiązywały połaczenie i będzie dobrze.

Jeśli to cię nie satysfakcjonuje, możesz poprosić swoich adminów, by zrobili dziurę w firewallu tak, żeby ludzie mogli się z tobą łączyć. Firewall może przekazywać pakiety do ciebie przez NAT lub serwer proxy.

Ale pamiętaj, że dziura w firewallu nie jest czymś, wobec czego można przejść obojętnie. Musisz się upewnić, że nie dasz złym ludziom dostępu do sieci wewnętrznej; jeśli jesteś początkujący, jest dużo trudniej uczynić programy bezpiecznymi niż może ci się zdawać.

I nie pozwól swoim adminom być wściekłymi na mnie. ;-)
Poprzedni dokument Następny dokument
Więcej informacji Wołanie o pomoc