7 34 Programowanie dla Internetu (2)


Rozdział 34.
Programowanie dla Internetu


W tym rozdziale:

Klasa CInternetSession
Przetwarzanie URL-i
Klasa CFtpConnection
Wysyłanie i pobieranie plików FTP
Demonstracyjny program FTP
Gniazda i klasa csocket
Program Sockets
Wysyłanie poczty e-mail poprzez gniazda
Pobieranie pliku HTTP
Automatyczne wybieranie numeru w celu uzyskania połączenia z Internetem


Obecnie chyba nie ma nikogo, kto nie słyszałby o Internecie, więc programowanie aplikacji dla Internetu powinno być umiejętnością posiadaną przez każdego programistę. Wiele osób podróżujących służbowo każdego dnia przesyła informacje do swoich firm. Niektóre organizacje edukacyjne wykorzystują Internet do publikowania podręczników i innych materiałów informacyjnych. Poprzez Internet coraz częściej oferuje się także towary i usługi i niczym niezwykłym jest zawieranie elektronicznych transakcji finansowych, na przykład przy okazji obrotu papierami wartościowymi.
Światowa komunikacja i wymiana danych dopiero się zaczęła. Możliwości są prawie nieograniczone. Programiści aplikacji muszą być gotowi na podjęcie dzisiejszych i przyszłych wyzwań. Opanowanie MFC w celu uzyskania dostępu do Internetu jest pierwszym krokiem, więc w tym rozdziale spróbujemy stworzyć ku temu solidne podstawy. Następny krok polega na staniu się kreatywnym i znalezieniu nowych zastosowań dla nowo opanowanych technik wymiany danych.
Głównymi obszarami, jakimi zajmiemy się w tym rozdziale, będą protokoły internetowe specyficzne dla HTTP i FTP. Oprócz tego omówimy bezpośrednie połączenia poprzez gniazda WinSock. Wszystkie te techniki stanowią serce większości metod przekazywania danych poprzez Internet.
Klasa ClnternetSession
Microsoft, w celu ułatwienia pracy programistom, stworzył kilka internetowych klas wysokiego poziomu. Istnieją specjalne klasy dla transmisji FTP, HTTP oraz Gopher. Dostępne są także specjalne funkcje do pobierania URL-i jako plików. Wszystkie one wymagaj ą jednak utworzenia obiektu klasy ClnternetSession. Ta klasa jest używana do tworzenia i manipulowania różnymi klasami protokołów, takich jak CFtpConnection. Poniższy kod ilustruje tworzenie klasy ClnternetSession:
// Proste tworzenie obiektu ClnternetSession ClnternetSession *p!nternetSession; plnternetSession = new ClnternetSession;
Przy tworzeniu klasy ClnternetSession można przekazać sześć opcjonalnych parametrów. Prototyp konstruktora tej klasy jest następujący:
ClnternetSession( LPCSTR pstrAgent = NULL, DWORD dwContext = 1,
DWORD dwAccessType = INTERNET_OPEN_TYPE_PRECONFIG, LPCTSTR pstrProxyName = NULL, LPCTSTR pstrProxyBypass = NULL, DWORD cłwFlags = O ) ;
Pierwszy parametr, pstrAgent, jest wskaźnikiem do łańcucha określającego nazwę aplikacji lub jednostki wywołującej funkcje Internetu. Jeśli pstrAgent ma domyślną wartość NULL, MFC wywołuje globalną funkcję Af xGetAppName (}, zwracającą nazwę aplikacji. Niektóre protokoły wykorzystuj ą ten łańcuch do identyfikacji aplikacji dla serwera.
Drugi parametr, dwContext, jest identyfikatorem kontekstu dla operacji. Ten parametr określa informacje o stanie operacji zwracane przez funkcję ClnternetSession: :OnStatusCaiiback(). Domyślną wartością jest l, jednak można jawnie przypisać konkretny identyfikator kontekstu dla operacji. Obiekt i wszelkie podejmowane przez niego działania są związane z tym identyfikatorem kontekstu. Jeśli parametr dwFiags zawiera znacznik INTERNET_FLAG_ASYNC, obiekty tworzone przez ten obiekt działają asynchronicznie, jeśli tylko zostanie zarejestrowana funkcja zwrotna.
Trzeci parametr, dwAccessType, określa rodzaj żądanego dostępu. W tym przypadku znaczników nie można łączyć za pomocą operatorów OR; można użyć tylko jednego z nich. Listę dozwolonych znaczników zawiera tabela 34.1.
Tabela 34.1. Wartości znaczników dostępu
Typ dostępu
Opis
INTERNET_OPEN_TYPE_PRECONFIG
Prekonfigurowany (w Rejestrze).Ten typ dostępu jest wybierany jako domyślny.


INTERNET_OPEN_TYPE_DIRECT
Bezpośrednio do Internatu.
INTERNET OPEN_TYPE_PROXY
Poprzez CERN proxy.


Czwarty parametr, pstrProxyName, jest nazwą preferowanego serwera CERN proxy, gdy typ dostępu w parametrze dwAccessType jest ustawiony na INTERNET_OPEN_TYPE_PROXY. Domyślną wartościąjest NULL.
Piąty parametr, pstrProxyBypass, jest wskaźnikiem do łańcucha zawierającego opcjonalną listę adresów serwerów. Przy dostępie poprzez serwer proxy MFC może wykorzystać adresy z tej listy. Gdy zostanie przekazana wartość NULL, lista przejścia jest odczytywana z Rejestru. Ten parametr ma znaczenie, tylko gdy parametr dwAccessType ma wartość
INTERNET__OPEN_TYPE_PROXY.
Ostatni parametr, dwFiags, określa różne opcje, takie jak buforowanie danych czy działanie asynchroniczne. Domyślną wartościąjest zero; inne dozwolone znaczniki zostały zebrane w tabeli 34.2.
Tabela 34.2. Znaczniki opcji sesji internetowej
Znacznik
Opis
INTERNET_FLAG_DONT_CACHE
Dane nie są buforowane ani lokalnie, ani na serwerach bram.
INTERNET_FLAG_ ASYNC
Przyszłe operacje na tym obiekcie mogą zwrócić kod błędu ERROR_IO_PENDING. Po zakończeniu operacji będzie wywoływana funkcja zwrotna stanu z kodem INTERNET_SESSION_REQUEST_COMPLETE. Ta funkcja zwrotna będzie wykonywana w wątku innym niż wątek oryginalnego żądania. W celu zarejestrowania funkcji zwrotnej stanu powinieneś wywołać funkcję EnableStatusCallback (); w przeciwnym razie funkcje będą wykonywane całkowicie synchronicznie.
INTERNET_FLAG_ OFFLINE
Operacje ładowania będą odbywał}' się tylko poprzez trwały bufor. Jeśli żądanego elementu nie ma w buforze, zwracany jest odpowiedni kod błędu. Ten znacznik może być łączony z innym za pomocą bitowego operatora OR (|).



Klasa CFtpConnection
Przed pobraniem plików z serwera FTP musisz się najpierw z nim połączyć. Aby nawiązać takie połączenie, musisz wykonać dwa proste kroki. Po pierwsze, musisz stworzyć obiekt CInternetSession. Po drugie, za pomocą funkcji CInternetSession: : GetFtpConnection () musisz otrzymać obiekt połączenia FTP.
Przetwarzanie łańcuchów URL
Gdy przyjmujesz adresy URL od użytkowników, może się zdarzyć, że wpisany adres nie będzie wystarczająco zrozumiały dla funkcji takich jak openURLO . Jeśli aplikacja polega na adresie URL wpisanym przez użytkownika, dla zabezpieczenia się użyj kodu podanego poniżej. Kod przetwarza podany przez użytkownika adres URL na format zrozumiały dla funkcji internetowych. Aby kod działał, wraz z samym adresem musisz przekazać typ usługi. Do usług obsługiwanych w tym kodzie
należą ftp://, http:// oraz gopher://.

BOOL CClass::ConnectionFunction( CString &strURL,
CString &strService ); {
DWORD dwServiceType;
CString strServer, strObject;
int nPort;
// Początkowe przetwarzanie URL-a. Jeśli otrzymamy // wartość FALSE, oznacza to, że standardowe // przetwarzanie się nie powiodło. if( !AfxParseURL( strURL, dwServiceType, strServer, strObject, nPort ) ){
// Tworzymy nowy URL z typem usługi jako
// przedrostkiem.
// Do usług należą ftp://, http:// oraz gopher://
CString strNewURL = strService;
strNewURL += strURL;
// Jeszcze raz próbujemy przetworzyć URL. if( !AfxParseURL( strNewURL, dwServiceType,
strServer, strObject, nPort ) )
return FALSE;
strURL = strNewURL;
}
return TRUE;
}
Klasa MFC CFtpConnection zarządza połączeniem FTP z serwerem internetowym oraz pozwala na bezpośrednią manipulację plikami i kartotekami na tym serwerze. FTP jest jedną z trzech usług internetowych rozpoznawanych przez klasy MFC Winlnet. (Winlnet to API stanowiące podstawę klas internetowych MFC). Z serwerem FTP możesz komunikować się tylko po utworzeniu egzemplarza obiektu cinternetsession i utworzeniu na jego podstawie obiektu CFtpConnection. Obiektów CFtpConnection nigdy nie będziesz tworzył bezpośrednio; zamiast tego musisz wywołać funkcję cinternetsession: :GetFtpConnection(), tworzącą obiekt CFtpConnection i zwracającą do niego wskaźnik. Przejdźmy do prostego przykładu. Poniższy kod nawiązuje połączenie zftp.microsoft. com:
// Sesja internetowa jest tworzona // w momencie zadeklarowania obiektu // CInternetSession CInternetSession InternetSession; CFtpConnection *m pFtpConnection;
try{
// Pobieramy wskaźnik do połączenia FTP // z serwerem ftp.microsoft.com m_pFtpConnection =
InternetSession.GetFtpConnection("ftp.microsoft.com");
}
// Wychwytujemy wszelkie wyjątki zgłoszone jako wynik // próby nawiązania połączenia FTP. catch( CInternetException *pEx ){
TCHAR szError[1024];
if (pEx->GetErrorMessage( szError, 1024 ) ) AfxMessageBox( szError ) ;
else
AfxMessageBox( "Wystąpił wyjątek." );
pEx->Delete ();
m_pFtpConnection = NULL;
}
// Tutaj wykonujemy operacje FTP...
// Zamykamy połączenie FTP i usuwamy obiekt. if( m_pFtpConnection != NULL ){
m_pFtpConnection->Close ( ) ;
delete m_pFtpConnection;
}
// Destruktor klasy CInternetSession zamknie // sesję internetowa.

Łączenie z serwerem FTP
Poświęć teraz nieco czasu na spróbowanie nawiązania połączenia z serwerem FTP. Stwórzmy prosty program łączący się z serwerem FTP:
1. Stwórz j ednodokumentową aplikację o nazwie FTPConnect.
2. Do pliku FTPConnectYiew.h dołącz plik nagłówkowy afainet.h.
3. W p\\kuFTPConnectVie-w.h zadeklaruj poniższe zmienne:
CInternetSession *m_pInternetSession; CFtpConnection *m_pFtpConnection;
4. Dopisz poniższą linię do konstruktora w pliku FTPConnectYiew.cpp:
m_p!nternetSession = new CInternetSession;
5. Do destruktora w pliku FTPConnectYiew.cpp dopisz poniższy kod:
if ( m_p!nternetSession != NULL ) delete m_p!nternetSession;
6. Do klasy CFTPConnect dopisz funkcje obsługi komunikatów WM_CREATE oraz
WM_TIMER.
7. Funkcję onCreate () wypełnij poniższym kodem:
SetTimerf 1,1000, NULL };
8. Funkcję OnTimer () wypełnij poniższym kodem:
KillTimer(1) ;
try{
// Pobieramy wskaźnik do połączenia FTP // z serwerem ftp.microsoft.com m_pFtpConnection =
m_pInternetSession->GetFtpConnection( "ftp.microsoft.com");
}
// Wychwytujemy wszelkie wyjątki zgłoszone jako wynik // próby nawiązania połączenia FTP. catch( CInternetException *pEx ){
pEx->Delete();
m_pFtpConnection = NULL; }
9. Ustaw punkt wstrzymania w funkcji OnTimer (), na wywołaniu funkcji Kill-
Timer() .
10. Uruchom program w trybie do debuggowania i wykonuj kolejno każdą linię. Powinieneś połączyć się z serweremftp.microsoft.com. Pamiętaj o ustawieniu punktu wstrzymania wewnątrz bloku catch, tak abyś mógł zobaczyć ewentualny zgłoszony wyjątek.
Pobieranie pliku z serwera FTP
Po ustanowieniu połączenia FTP pobranie pliku z serwera jest już dość proste. Klasa CFtpConnection posiada wysokopoziomową funkcję GetFile(), zajmującą się wszystkimi szczegółami. Do pobrania pliku z serwera FTP wystarczy pojedyncza linia kodu (oczywiście gdy jesteś już połączony). Poniższy kod, korzystając z utworzonego wcześniej obiektu klasy CFtpConnection, pobiera z serwera FTP Microsoftu (ftp.microsoft.com) plik o nazwie disclaimer.txt i zapisuje go w bieżącej kartotece w pliku o nazwie copy.txt:
// m_pFtpConnection zostało przygotowane wcześniej
// w kodzie programu
m_pFtpConnection->GetFile( "disclaimer.txt", "copy.txt" );
Funkcja GetFile()
Funkcja Getriie o służy do pobierania pliku z serwera FTP i zapisywania go w lokalnym komputerze. Ta funkcja jest funkcją wysokiego poziomu, biorącą na siebie cały ciężar pobrania pliku z serwera i zapisania go w lokalnej kartotece. Jeśli parametr dwFiags zawiera znacznik FILE_ TRANSFER_TYPE_ASCII, przy transmisji pliku następuje konwersja znaków sterujących i formatujących na ich odpowiedniki w Windows. Domyślnym trybem transmisji jest tryb binarny, w którym plik jest ściągany w tym samym formacie, w jakim jest przechowywany na serwerze.
Prototyp funkcji jest następujący:
BOOL GetFile( LPCTSTR pstrRemoteFile, LPCTSTR pstrLocalFile,
BOOL bFail!fExists, DWORD dwAttributes, DWORD dwFiags,
DWORD dwContext );
Parametry pstrRemoteFile i pstrLocalFile mogą być w pełni kwalifikowanymi nazwami lub częściowymi nazwami określającymi położenie pliku względem bieżącej kartoteki. W obu nazwach jako separator kartotek może zostać użyty zarówno znak ukośnika (/), jak i odwrotnego ukośnika (\). Odpowiednią konwersją separatorów kartotek zajmuje się funkcja GetFileo .
Aby określić własną wartość identyfikatora kontekstu, możesz przekazać ją w parametrze dwcontext. Identyfikator kontekstu jest przypisywany operacjom wykonywanym przez obiekt CFtpconnection utworzony poprzez obiekt cinternetsession. Ta wartość jest przekazywana funkcji
CInternetSession: : OnStatusCallback () przy Określaniu Stanu wy-
konywanej operacji.
W przypadku powodzenia funkcja zwraca wartość różną od zera; w przeciwnym razie zwraca zero. Jeśli wywołanie funkcji się nie powiedzie, do wyznaczenia przyczyny błędu można wywołać funkcję Win32
GetLastError() .
Parametry
Pierwszy parametr, pstrRemoteFile, jest wskaźnikiem do zakończonego zerem łańcucha zawierającego nazwę pliku przeznaczonego do pobrania z serwera FTP.
Następny parametr, pstrLocalFile, jest wskaźnikiem do zakończonego zerem łańcucha zawierającego nazwę pliku, jaki ma zostać zapisany w lokalnym systemie.
Trzeci parametr, bFailIfExists, wskazuje, czy lokalna nazwa pliku może być użyta dla już istniejącego pliku. Jeśli lokalnie istnieje już plik o podanej nazwie, zaś parametr bFailIfExists ma wartość TRUE, wywołanie funkcji się nie powiedzie. Jeśli ten parametr ma wartość FALSE, istniejący plik jest zastępowany przez plik pobrany z serwera FTP.
Następny parametr, dwAttributes, określa atrybuty pliku. Atrybutami może być dowolna kombinacja poniższych znaczników:
FILE_ATTRIBUTE_ARCHIVE - Wskazuje, że plik jest plikiem archiwalnym. Aplikacje korzystają z tego znacznika przy tworzeniu i odtwarzaniu kopii zapasowych.
FILE_ATTRIBUTE_COMPRESSED - Wskazuje, że plik lub kartoteka jest skompresowana. W przypadku pliku, kompresja oznacza, że wszystkie dane w pliku są skompresowane. W przypadku kartoteki oznacza, że domyślnie są kompresowane nowe pliki i kartoteki.
FILE_ATTRIBUTE_DIRECTORY - Wskazuje, że plik jest kartoteką.
FILE_ATTRIBUTE_NORMAL - Wskazuje, że plik nie ma ustawionych innych atrybutów. Ten atrybut może występować tylko samodzielnie; wszystkie inne atrybuty powodują jego usunięcie.
FILE_ATTRIBUTE_HIDDEN - Wskazuje, że plik jest ukryty i nie jest wyświetlany na zwykłych listach plików w kartotece.
FILE_ATTRIBUTE_READONLY - Wskazuje, że plik jest przeznaczony tylko do odczytu. Aplikacje mogą odczytywać zawartość pliku, ale nie mogą dokonywać zapisu do pliku ani go usuwać.
FILE_ATTRIBUTE_SYSTEM - Wskazuje, że plik jest częścią systemu operacyjnego lub jest używany włącznie przez system.
FILE_ATTRIBUTE_TEMPORARY - Wskazuje, że plik jest używany jako tymczasowe miejsce przechowywania danych. Aplikacje powinny dokonywać zapisu do tego pliku tylko wtedy, gdy jest to absolutnie konieczne. Większość danych pliku pozostaje w pamięci bez zrzucania ich na dysk, gdyż plik i tak wkrótce zostanie usunięty.
Piąty parametr, dwFiags, określa warunki transmisji pliku. Może być jednym ze znaczników typu transmisji:
FTP_TRANSFER_TYPE_ASCII - plik będzie transmitowany w trybie FTP ASCII (typie A). Znaki sterujące i formatujące w pliku będą zamieniane na odpowiedniki w Windows.
FTP_TRANSFER_TYPE_BINARY - plik będzie transmitowany w trybie obrazu pliku FTP (typie I). Plik zostanie przekazany dokładnie tak, jak jest zapisany na serwerze. Ta metoda transmisji jest domyślna.
Ostatni parametr, dwcontext, jest kontekstem identyfikującym pobieranie pliku.
Funkcja PutFile()
Funkcja Putriieo służy do kopiowania pliku z lokalnego systemu do kartoteki na serwerze FTP. Ta funkcja jest funkcją wysokiego poziomu, biorącą na siebie cały ciężar pobrania pliku z lokalnego systemu i zapisania go w kartotece serwera FTP. Jeśli parametr dwFiags zawiera znacznik FILE_TRANSFER_TYPE_ASCII, przy transmisji pliku następuje konwersja znaków sterujących i formatujących Windows na ich odpowiedniki ASCII. Domyślnym trybem transmisji jest tryb binarny, w którym plik jest ładowany w tym samym formacie, w jakim jest przechowywany na lokalnym dysku.
Prototyp funkcji jest następujący:
BOOL PutFilef LPCTSTR pstrRemoteFile, LPCTSTR pstrLocalFile,
BOOL bFail!fExists, DWORD dwAttributes, DWORD dwFiags,
DWORD dwContext );
Parametry pstrRemoteFile i pstrLocalFile mogą być w pełni kwalifikowanymi nazwami lub częściowymi nazwami określającymi położenie pliku względem bieżącej kartoteki. W obu nazwach jako separator kartotek może zostać użyty zarówno znak ukośnika (/), jak i odwrotnego ukośnika (\). Odpowiednią konwersją separatorów kartotek zajmuje się funkcja putFiieo.
Aby określić własną wartość identyfikatora kontekstu, możesz przekazać ją w parametrze dwcontext. Identyfikator kontekstu jest przypisywany operacjom wykonywanym przez obiekt cFtpConnection utworzony poprzez obiekt cinternetsession. Ta wartość jest przekazywana funkcji
CInternetSession: : OnStatusCallback () przy Określaniu Stanu Wy-
konywanej operacji.
W przypadku powodzenia funkcja zwraca wartość różną od zera; w przeciwnym razie zwraca zero. Jeśli wywołanie funkcji się nie powiedzie, do wyznaczenia przyczyny błędu można wywołać funkcję
Win32 GetLastError () .
Parametry
Pierwszy parametr, pstrRemoteFile, jest wskaźnikiem do zakończonego zerem łańcucha zawierającego nazwę pliku do zapisania na serwerze FTP.
Następny parametr, pstrLocalFile, jest wskaźnikiem do zakończonego zerem łańcucha zawierającego nazwę pliku, jaki ma zostać skopiowany z lokalnego systemu.
Trzeci parametr, bFaiiifExists, wskazuje, czy można zastąpić plik o podanej nazwie już istniejący na serwerze. Jeśli na serwerze istnieje taki plik, zaś parametr bFaiiifExists ma wartość TRUE, wywołanie funkcji się nie powiedzie. Jeśli ten parametr ma wartość FALSE, plik istniejący na serwerze jest zastępowany przez plik kopiowany z lokalnego systemu.
Następny parametr, dwAttributes, określa atrybuty pliku. Atrybutami może być dowolna kombinacja tych samych znaczników, które są używane w funkcji Getriie (). (Szczegóły znajdziesz w ramce "Funkcja GetFileO" w poprzedniej sekcji).
Piąty parametr, dwFiags, określa warunki transmisji pliku. Służą do tego te same znaczniki co w przypadku funkcji Getriie i). (Szczegóły znajdziesz w ramce "Funkcja GetFileO" w poprzedniej sekcji).
Ostatni parametr, dwcontext, jest kontekstem identyfikującym kopiowanie pliku do serwera.
Wysyłanie pliku do serwera FTP
Tak jak w przypadku pobierania pliku, gdy już zostanie ustanowione połączenie FTP, również wysłanie pliku do serwera nie sprawia żadnego problemu. Klasa crtpConnection posiada wysokopoziomową funkcję PutFile (), zajmującą się wszystkimi szczegółami. Do wysłania pliku do serwera FTP wystarczy pojedyncza linia kodu (oczywiście gdy jesteś już połączony). Poniższy kod, korzystając z utworzonego wcześniej obiektu klasy CFtpConnection, wysyła do serwera FTP Microsoftu (ftp.microsoft.com} plik o nazwie mytext.txt i zapisuje go w bieżącej kartotece w pliku o nazwie text.txt:

// m_pFtpConnection zostało przygotowane wcześniej
// w kodzie programu
m_pFtpConnection->PutFile( "text.txt", "mytext.txt" } ;

Szczegóły wywołania funkcji GetFile f) i PutFile () zostały przedstawione w osobnych ramkach.
Inne funkcje FTP
Poza opisanymi funkcjami, klasa CFtpConnection posiada jeszcze kilka innych funkcji, które mogą okazać się bardzo przydatne. Opiszemy je w tej sekcji.
Zwykle przy łączeniu się z serwerem FTP będziesz chciał przejść do innej kartoteki. W tym celu powinieneś użyć funkcji SetCurrentDirectory (). Wymaga ona podania pojedynczego argumentu - łańcucha znaków określającego nową kartotekę. Poniższy przykład ustawia bieżącą kartotekę na/pub/files:

if( SetCurrentDirectory( "/pub/files" } )
AfxMessageBox( "Wywołanie funkcji SetCurrentDirectory()"
" się powiodło." ); else
AfxMessageBox( "Wywołanie funkcji SetCurrentDirectory()" " się nie powiodło." );
Podczas poruszania się po kartotekach serwera FTP często występuje potrzeba pobrania nazwy bieżącej kartoteki. Służy do tego funkcja GetCurrentDirectory {). Funkcja wymaga podania dwóch argumentów. Pierwszym z nich jest wskaźnik do bufora, w którym ma zostać umieszczona nazwa bieżącej kartoteki, zaś drugim jest maksymalny rozmiar tego bufora. Poniższy przykład pobiera nazwę bieżącej kartoteki i wyświetlają w oknie komunikatu:
char szCurDir[500];
if( GetCurrentDirectory( szCurDir, sizeof(szCurDir) )
AfxMessageBox( szCurDir ); else
AfxMessageBox ( "Wywołanie funkcji GetCurrentDirectory ()" " się nie powiodło." );
Gdy chcesz usunąć kartotekę z serwera, musisz użyć funkcji RemoveDirectory (), wymagającej podania pojedynczego argumentu - ciągu znaków zawierającego nazwę kartoteki, którą chcesz usunąć. Poniższy fragment usuwa kartotekę temp:
if( RemoveDirectory( "temp" } )
AfxMessageBox ( "Wywołanie funkcji RemoveDirectory()"
" się powiodło." ); else
AfxMessageBox ( "Wywołanie funkcji RemoveDirectory()" " się nie powiodło." );
Przeciwieństwem funkcji RemoveDirectory () jest funkcja CreateDirectory () . Ta funkcja tworzy kartotekę na serwerze FTP. Wymaga podania pojedynczego parametru -łańcucha znaków zawierającego nazwę nowej kartoteki. Poniższy fragment tworzy nową kartotekę temp I:
if( CreateDirectory( "templ" ) )
AfxMessageBox( "Wywołanie funkcji CreateDirectory()"
" się powiodło." ); else
AfxMessageBox( "Wywołanie funkcji CreateDirectory()" " się nie powiodło." );
Do zmiany nazwy pliku służy funkcja Rename (}. Wymaga podania dwóch argumentów - pierwszym jest łańcuch znaków zawierający oryginalną nazwę pliku, zaś drugim jest łańcuch znaków zawierający nową nazwę pliku. Poniższy fragment zamienia plik Disclaim.txt na plik Datclaim.txt.
if ( Rename( "Disclaim.txt", "Datclaim.txt" ) ) AfxMessageBox( "Wywołanie funkcji Rename(}" " się powiodło." );
else
AfxMessageBox('Wywołanie funkcji Rename ' się nie powiodło." );

Aby usunąć plik, użyj funkcji Remove (), wymagającej podania pojedynczego argumentu - ciągu znaków zawierającego nazwę pliku, który chcesz usunąć. Poniższy fragment usuwa plik o nazwie Extra.doc:
if( Remove ( "Extra.doc" ) )
AfxMessageBox( "Wywołanie funkcji Remove()"
" się powiodło." ); else
AfxMessageBox( "Wywołanie funkcji Remove()" " się nie powiodło." );
Klasa CInternetFile
Jedną z funkcji klasy CFtpConnection, której jeszcze nie omawialiśmy, jest funkcja OpenFile O . W wyniku uwieńczonego powodzeniem wywołania tej funkcji otrzymujemy wskaźnik do klasy CInternetFile. Otwarcie pliku wymaga podania od jednego do czterech parametrów. Pierwszy parametr jest wymagany i stanowi łańcuch znaków zawierających nazwę pliku, który chcemy otworzyć. Drugi parametr jest opcjonalny i zawiera znaczniki typu dostępu (domyślnie GENERIC_READ). Listę znaczników dostępu zawiera tabela 34.3. Trzeci parametr określa sposób transmisji (domyślnie FTP_ TRANSFER_TYPE_BINARY). Znaczniki rodzaju transmisji zebrano w tabeli 34.4. Ostatni, opcjonalny parametr określa kontekst operacji.
Tabela 34.3. Znaczniki dostępu do pliku
Znacznik
Opis
GENERIC_READ
Otwiera plik do odczytu. GENERIC_WRITE
Otwiera plik do zapisu.


Tabela 34.4. Znaczniki rodzaju transmisji
Znacznik
Opis
FTP TRANSFER TYPE ASCII
Plik jest transmitowany z użyciem metody FTP ASCII (typ A). Znaki kontrolne i formatujące są konwertowane na lokalne odpowiedniki.

FTP TRANSFER TYPE BINARY
Plik jest transmitowany z użyciem metody FTP Image (typ I). Plik jest transmitowany bez dokonywania żadnych konwersji. Ten tryb transmisji jest trybem domyślnym.

Operując na plikach, będziesz najczęściej korzystał z trzech funkcji: Read (), write () oraz ciose (). Funkcje Read () i Write () otrzymują po dwa argumenty - pierwszym jest wskaźnik do bufora danych, zaś drugim jest ilość bajtów przeznaczonych do zapisu w funkcji write () lub maksymalny rozmiar bufora w funkcji Read (). Funkcja ciose () nie wymaga argumentów. Poniższy przykład otwiera plik na serwerze FTP, odczytuje 100 bajtów, a następnie zamyka plik:
CInternetFile *p!nternetFile;
// pFtpConnection został ustawiony wcześniej w kodzie p!nternetFile= pFtpConnection->OpenFile( "Text.txt" ); if( plnternetFile != NULL ){
char chBuffer[200];
p!nternetFile->Read( cbBuffer, 100 );
p!nternetFile->Close();
}
Użycie klasy cinternetFile do kopiowania plików z i do serwera FTP nie jest tak wygodne jak użycie funkcji GetFileO i PutFiieO. Oferuje jednak dużo większą kontrolę nad samą operacją kopiowania plików. Możesz wyświetlić użytkownikowi przycisk Anuluj, zaś podczas kopiowania wyświetlać pasek postępu.
Klasa CFtpFileFind
Czasem występuje potrzeba przeszukania zawartości kartoteki FTP. Służy do tego klasa MFC CFtpFileFind. Poniższy przykład tworzy obiekt tej klasy; jej konstruktor wymaga podania wskaźnika do obiektu CFtpConnection:
CFtpFileFind *pFtpFind =
new CFtpFileFind( pFtpConnection );
Następnie w celu pobrania wszystkich nazw plików w kartotece należy użyć dwóch funkcji, FindFileO oraz FindNextFile (). Poniższy przykład odczytuje zawartość bieżącej kartoteki:
CFtpFileFind *pFtpFind =
new CFtpFileFind( pFtpConnection );
BOOL bContinue = pFtpFind->FindFile( "*" ); while ( bContinue )
bContinue = pFtpFind->FindNextFile();
Program FTP
Program FTP przesyła pliki do i z serwerów FTP, a także pobiera zawartości kartotek tych serwerów. Do wyboru działań programu służą trzy pozycje menu. Polecenie Pobierz plik FTP powoduje pobranie pliku z serwera FTP i zapisanie go na lokalnym dysku. Polecenie Wyślij plik FTP powoduje wysłanie pliku z lokalnego dysku na serwer, zaś polecenie Pobierz zawartość kartoteki FTP powoduje pobranie listy zawartości kartoteki serwera.
Ten demonstracyjny program znajdziesz na dołączonej do książki płytce CD-ROM, w kartotece Rozdz34\Ftp. Program został zbudowany w oparciu o klasę CFtpConnect. Możesz zbudować projekt Ftp.dsw lub od razu uruchomić program otwierając plik Ftp.exe.
Zanim zaczniesz pobierać i wysyłać pliki oraz odczytywać zawartości kartotek, pojawi się okno dialogowe przedstawione na rysunku 34.1, w którym musisz podać konieczne informacje. Musisz podać nazwę serwera, nazwę użytkownika, hasło oraz nazwę lokalnego i zdalnego pliku. Powinieneś podać także kartotekę FTP, w której chcesz wykonywać operacje.
Gdy program FTP uzyska potrzebne informacje, zainicjuje pobieranie pliku, wysyłanie pliku oraz odczyt zawartości kartoteki. Program korzysta z dwóch funkcji wątków. Pierwsza z nich, FileThread (), odpowiada za operacje pobierania i wysłania pliku. Druga funkcja, DirectoryThread (), pobiera listę zawartości kartoteki FTP. Po zakończeniu działania na plikach lub w kartotece pojawia się okno komunikatu informujące o zakończeniu operacji. Rysunek 34.2 przedstawia pełną listę zawartości kartoteki.
Program FTP jest prostą implementacją klasy CFtpConnection. Nie jest skomplikowany, dzięki czemu możesz łatwo zrozumieć zastosowane w nim techniki. Funkcje Fiie-Thread ( ) i DirectoryThread ( ) mogłyby zostać połączone w jedną, w której korzystalibyśmy ze znacznika określającego, czy operujemy na plikach czy też pobieramy zawartość kartoteki. Jednak dzięki zastosowaniu dwóch osobnych funkcji program jest łatwiejszy w zrozumieniu. Fragmenty kodu źródłowego programu FTP zostały przedstawione na listingu 34.1.
FTP
Położenie na płytce CD-ROM: Rozdz34\Ftp
Plik wykonywalny: Ftp.exe
Moduły kodu źródłowego w tekście: FtpView.cpp
Listing 34.1. Fragmenty kodu źródłowego pliku FtpView.cpp programu FTP

// FtpView.cpp : implementation of the // CFtpView class
//
#include "stdafx.h"
#include"Ftp.h"
#include "FtpDoc.h"
#include "FtpView.h"
#include "FtpFileDlg .h"
//////////////////////
//CFtpView
//////////
// CFtpView message handlers void CFtpYiew::OnFileGetftpfile()
{
FTP_INFO *lplnfo = GetParams( FALSE, TRUE);
if ( Iplnfo != NULL ){
// Uruchamiamy wątek
AfxBeginThread( CFtpView::FileThread, (LPYOID) Iplnfo );
}
}
void CFtpView::OnFileSendftpfile()
{
FTP_INFO *Iplnfo = GetParams( TRUE, TRUE);
if( Iplnfo != NULL ){
// Uruchamiamy wątek
AfxBeginThread( CFtpYiew::FileThread, (LPYOID) IpInfo );
}
}
void CFtpView::OnFileFtpdirectory()
{
FTP_INFO *lplnfo = GetParams( FALSE, FALSE ); if( Iplnfo != NULL ){
// Alokujemy pamięć na zawartość kartoteki lp!nfo->hGlobal =
GlobalAlloc( GMEM_MOVEABLE, 20000 );
// Uruchamiamy wątek
AfxBeginThread( CFtpView::DirectoryThread, (LPYOID) Iplnfo );
}
FTP_INFO *CFtpView::GetParams( BOOL bSendFlag,
BOOL bFileFlag ) {
FTP_INFO *1pInfo=NULL;
CFtpFileDlg FileDlg;
// Ustawiamy znacznik okna dialogowego służący
// do wyłączenia pól plików, gdyż w przypadku
// pobierania zawartości kartoteki nie są potrzebne.
FileDlg.mJoGrayOutFileltems = IbFileFlag;
// Wywołujemy okno dialogowe w celu // pobrania informacji o serwerze FTP. if( FileDlg.DoModal() == IDOK ){
// Alokujemy strukturę. 1pInfo = new FTP_INFO;
// Kopiujemy nazwę pliku FTP do // struktury.
stropy( lpInfo->szFtpFile, FileDlg.m_strFtpFile );
// Kopiujemy nazwę kartoteki do struktury. strcpy( lpInfo->szFtpDirectory, FileDlg.m_strFtpDir );
// Kopiujemy nazwę pliku loalnego do // struktury.
stropy( 1pInfo->szLocalFile, FileDlg.m_strLocalFile );
// Kopiujemy hasło do struktury. stropy( 1pInfo->szPassword, FileDlg.m_strPassword );
// Kopiujemy nazwę hosta do struktury. stropy( lpInfo->szHost,
FileDlg.m_strServer );
// Kopiujemy nazwę użytkownika do struktury. stropy( lpInfo->szUser, FileDlg.m_strUser );
// Kopiujemy nazwę aplikacji do struktury. stropy( lpInfo->szAppName, AfxGetAppName() );
// Ustawiamy znacznik wskazujący pobieranie. lpInfo->bSendFlag = FALSE;
// Ustawiamy znacznik wskazujący,że będziemy // pobierać lub wysyłać. lpInfo->bSendFlag = bSendFlag;
}

return ( 1pInfo ); } UINT CFtpYiew::FileThread( LPVOID 1pInf )
FTP_INFO *1pInfo = (FTP_INFO *) 1pInf;
CInternetSession *1pInetSession; CFtpConnection *lpFtpConnection;
// Alokujemy obiekt CInternetSession.
IpInetSession = new CInternetSession( lpInfo->szAppName, l, PRĘ CONFIG INTERNET ACCESS );

// Wychodzimy z wątku, gdy nie powiodło się // stworzenie obiektu sesji. if( 1pInetSession == NULL ){
delete 1pInfo;
return ( O ) ;
}
// Próbujemy nawiązać połączenie FTP. try{
// Użyjemy wartości NULL jako nazwy użytkownika, // tak aby GetFtpConniection ( ) użyła domyślnej wartości // Jeśli użytkownik podał swoją nazwę, // przekażemy wskaźnik do tego łańcucha. char *lpUser = NULL; if ( lpInfo->szUser [0] != O ) lpUser = 1pInfo->szUser;
// To samo zrobimy z hasłem. Gdy nie otrzymamy // hasła od użytkownika, użyjemy wartości NULL. char *lpPassword = NULL; if( lpInfo->szPassword[0] != O )
IpPassword = lp!nfo->szPassword;
IpFtpConnection =
lpInetSession->GetFtpConnection (
1plnf o->szHost , lpUser, lpPassword );
}
// W przypadku zgłoszenia wyjątku wychwytujemy go, // porządkujemy obiekty i wychodzimy z wątku. catch( CInternetException *lpEx ){
lpEx->Delete();
delete
1pInetSession;
delete 1pinfo;
return ( O ) ;
}
// Jeśli użytkownik określił kartotekę FTP, wybieramy
// ją.
if( lp!nfo->szFtpDirectory [0] != O )
lpFtpConnection->SetCurrentDirectory (
lpInf o->szFtpDirectory ) ;
if( lpInfo->bSendFlag ){
// Próbujemy przesłać plik z lokalnego komputera
// do serwera.
if( lpFtpConnection->PutFile ( lp!nfo->szLocalFile,
lpInfo->szFtpFile ) }
AfxMessageBox ( "Plik został wysłany." ) ; else
AfxMessageBox ( "Plik nie został wysłany." ) ;
}
else{
// Próbujemy pobrać plik z serwera do
// lokalnego komputera.
if( lpFtpConnection->GetFile ( lp!nfo->szFtpFile,
lpInfo->szLocalFile ) )
AfxMessageBox ( "Plik został pobrany." ); else
AfxMessageBox ( "Plik nie został pobrany." ) ;
}
// Zamykamy połączenie FTP i usuwamy obiekty // połączenia i sesji. lpFtpConnection->Close ( ) ; delete lpFtpConnection; delete lpInetSession; delete lpInfo;
return ( O ) ;
}
UINT CFtpYiew: : DirectoryThread ( LPYOID 1pInf ) {
FTP_INFO *lpInfo = (FTP_INFO *) lpInf;
int nFileCount = 0;
CInternetSession *lpInetSession; CFtpConnection * lpFtpConnection,
// Alokujemy obiekt CInternetSession.
IpInetSession = new CInternetSession ( 1pInfo->szAppName, l, PRE_CONFIG_INTERNET_ACCESS ) ;
// Wychodzimy z wątku, gdy nie powiodło się // stworzenie obiektu sesji. if( lpInetSession == NULL ){
delete 1pInfo;
return ( O ) ;
}
// Próbujemy nawiązać połączenie FTP. try{
// Użyjemy wartości NULL jako nazwy użytkownika, // tak aby GetFtpConnection ( ) użyła domyślnej wartości // Jeśli użytkownik podał swoją nazwę, // przekażemy wskaźnik do tego łańcucha. char *lpUser = NULL; if( lpInfo->szUser [0] != O ) IpUser = 1pInf o->szUser;
// To samo zrobimy z hasłem. Gdy nie otrzymamy // hasła od użytkownika, użyjemy wartości NULL. char *lpPassword = NULL; if( lpInfo->szPassword[0] != O )
1pPassword = lp!nfo->szPassword;
1pFtpConnection =
lpInetSession->GetFtpConnection(
1pInfo->szHost, lpUser, lpPassword );
// W przypadku zgłoszenia wyjątku wychwytujemy go, // porządkujemy obiekty i wychodzimy z wątku. catch( CInternetException *lpEx ){
lpEx->Delete();
delete 1pInetSession;
delete 1pInfo;
return( O );
// Jeśli użytkownik określił kartotekę FTP, wybieramy
// ją-
if( lpInfo->szFtpDirectory[0] != O )
lpFtpConnection->SetCurrentDirectory(
lpInfo->szFtpDirectory );
// Tworzymy nowy obiekt CFtpFileFind. CFtpFileFind *lpFtpFind
new CFtpFileFind( IpFtpConnection ) ; if ( lpFtpFind == NULL )
goto GetDirBailout;
// Pobieramy pierwszy plik.
BOOL bContinue;
bContinue = lpFtpFind->FindFile( "*" ) ;
// Jeśli nie ma więcej plików, wychodzimy z wątku. if( IbContinue )
goto GetDirBailout;
// W pętli pobieramy dalsze pliki, while( bContinue ){
// Zwiększamy licznik plików. nFileCount++;
// Przechodzimy do następnego pliku. bContinue = lpFtpFind->FindNextFile();
// Pobieramy nazwę pliku.
CString strFilename = lpFtpFind->GetFileName();
// Dodajemy nawiasy kwadratowe do nazw kartotek. if( lpFtpFind-MsDirectory () )
strFilename = "[" + strFilename + "]"; // Dodajemy znak nowej linii. strFilename += "\15";
// Blokujemy pamięć i dopisujemy nazwę pliku // do listy, char *pDest =
(char *) GlobalLock( lp!nfo->hGlobal ); if( pDest != NULL ){
strcat( pDest, strFilename );
GlobalUnlockf lpInfo->hGlobal );
}
}
if( nFileCount < 10 ){ char *pDest =
(char *) GlobalLock( 1pInfo->hGlobal ); if( pDest != NULL ){
AfxMessageBox( pDest ); GlobalUnlock( lp!nfo->hGlobal );
}
}
else
AfxMessageBox( "Zbyt dużo pozycji." ) ;
// Zamykamy obiekt wyszukiwania FTP i usuwamy go. lpFtpFind->Close(); delete lpFtpFind;
GetDirBailout:
// Zamykamy połączenie FTP i usuwamy obiekty // połączenia i sesji. lpFtpConnection->Close(); delete lpFtpConnection; delete lpInetSession; delete lpInfo;
return( O );
}
Gniazda i klasa CSocket
Gniazdo jest punktem końcowym w komunikacji sieciowej. Innymi słowy, komunikacja sieciowa polega na połączeniu dwóch komputerów lub dwóch procesów w celu wzajemnej wymiany danych. Profesjonaliści zajmujący się siecią każdy z tych końców sieciowej konwersacji nazywaj ą punktem końcowym. Gdy programy korzystają w komunikacji sieciowej z interfejsu gniazd, gniazdo jest abstrakcyjną reprezentacją punktu końcowego w procesie komunikacji poprzez sieć.
Aby poprzez interfejs gniazd mogło dojść do komunikacji sieciowej, program wymaga powiązania gniazda z każdym punktem końcowym. Połączenie pomiędzy gniazdami może być zorientowane na połączenie (połączenie typu point-to-point) lub bezpołączeniowe.
i
T
Rozdziat 34. Programowanie dla Internetu
999
Interfejs gniazd działa podobnie do systemu plików. Gdy potrzebujesz gniazda dla komunikacji sieciowej, definiujesz jego charakterystykę i wywołując funkcję API, prosisz oprogramowanie sieciowe o przekazanie uchwytu identyfikującego wskazane gniazdo. Jednak w odróżnieniu od uchwytu pliku uchwyt gniazda nie reprezentuje konkretnego punktu końcowego czy adresu docelowego. Właśnie tym uchwyty gniazd różnią się od uchwytów plików w większości systemów operacyjnych. Programy korzystające z gniazd najpierw uzyskują uchwyt gniazda, a następnie, w oddzielnym kroku, łączą gniazdo z docelowym punktem końcowym.
Gdy tworzysz programy TCP/IP, potrzebujesz możliwości korzystania z protokołów bezpołączeniowych oraz protokołów zorientowanych na połączenie. Interfejs gniazd umożliwia programom korzystanie z obu typów protokołów. Niskopoziomowe funkcje gniazd stanowią część Winlnet API, lecz w tym rozdziale będziemy korzystać z klasy MFC csocket, stanowiącej warstwę pośrednią dla tych funkcji. W rzeczywistości klasa csocket jest wyprowadzona z klasy CAsyncSocket i zapewnia wyższy poziom abstrakcji dostępu do gniazd.
Tworzenie gniazd
Gniazda tworzy się w dwóch krokach. Po pierwsze, musisz stworzyć obiekt csocket. Po drugie, musisz użyć funkcji createo w celu stworzenia odpowiedniego uchwytu gniazda. Oto przykład tworzenia gniazda mogącego komunikować się poprzez port 4500:

CSocket GenericSocket;
GenericSocket.Create(4500);

Numer portu jest parametrem opcjonalnym. Jeśli go nie podasz, MFC samo wybierze port, co zwykle nie jest pożądane. Za każdym razem gdy tworzysz gniazdo, musisz mieć określony port. Drugi opcjonalny parametr pozwala na określenie typu gniazda. Dwa dostępne typy gniazd zostały opisane w tabeli 34.5. Trzeci opcjonalny parametr jest łańcuchem zawierającym adres IP gniazda, z którym się łączysz.
Tabela 34.5. Dostępne typy gniazd
Typ gniazda
Opis
SOCK__STREAM
Tworzy sekwencyjne, pewne, dwukierunkowe, oparte na połączeniach strumienie bajtów. Dla rodziny adresów internatowych używa protokołu TCP (Transfer Control Protocol).
SOCK_DGRAM
Tworzy datagramy, którymi sąbezpołączeniowe, niepewne bufory o stałej (zwykle niewielkiej) maksymalnej długości. Dla rodziny adresów internetowych używa protokołu UDP (User Datagram Protocol). Aby użyć tej opcji, musisz używać gniazda łącznie z obiektem klasy
CArchive.
Łączenie się z gniazdem
Gdy już stworzysz gniazdo, powinieneś nawiązać łączność z innym punktem końcowym, czyli gniazdem. W tym celu musisz użyć funkcji Connect ( ) . Dwoma argumentami tej funkcji są wskaźnik do struktury SOCKADDR_IN oraz rozmiar tej struktury w bajtach. Struktura SOCKADDR_IN ma następującą postać:
struct sockaddr_in { short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8];
};
W rodzinie adresów internetowych struktura SOCKADDR_IN jest używana przez Windows Sockets do określenia lokalnego lub zdalnego adresu punktu końcowego, z którym ma zostać nawiązane połączenie. Jest ona formą struktury SOCKADDR specyficznej dla rodziny adresów internetowych i może być rzutowana na typ SOCKADDR. Tabela 34.6 zawiera opis pól struktury SOCKADDR_IN.
Tabela 34.6. Składowe struktury SOCKADDRJN
Składowa
Opis
sin_f amily
Rodzina adresów (musi nią być AF_INET)
sin_port
Port IP.
sin_addr
Adres IP.
sin_zero
Wypełnienie wyrównujące rozmiar struktury do rozmiaru struktury SOCKADDR.

Komponent adresu IP struktury jest typu IN_ADDR. Struktura IN_ADDR jest zdefiniowana w pliku nagłówkowym Windows Sockets WINSOCK.h:
struct in_addr { union {
struct {
unsigned char s_b1,
S_b2,
s_b3,
s_b4 ;
} S_un_b;
struct {
unsigned short s_w1, s_w2 ; } S_un_w;
struct {
unsigned long S_addr; } S un;
}
};
Hoslftp.microsoft.com ma adres IP 198.105.232.1. Jeśli masz zamiar połączyć się z nim poprzez gniazdo, oto jak powinieneś wypełnić strukturę SOCKADDR_IN (pamiętaj, że połączenia FTP odbywają się poprzez port 21):

SOCKADDR_IN Addr;
memset( &Addr, O, sizeof( SOCKADDR_IN } ) ;
Addr.sin_family = AF_INET;
Addr.sin_port = htons( 21 );
Addr.sin_addr.s_addr = inet_addr( "192.105.232.1" );

Ostatnim krokiem wymaganym do nawiązania połączenia jest wywołanie funkcji connect (}. Poniższe linie kodu podejmują próbę połączenia się z serwQremftp.microsoft.com. Kod korzysta ze struktury SOCKADDR_IN przygotowanej w poprzednim przykładzie:

if( Socket.Connect( (SOCKADDR) SAddr, sizeof( SOCKADDR_IN ) ) )
AfxMessageBox( "Nawiązano połączenie." ); else
AfxMessągeBox( "Nie nawiązano połączenia." ) ;
Przed wywołaniem innego gniazda możesz zrobić jeszcze jedną rzecz. Często zdarza się, że znasz nazwę hosta, lecz nie znasz jego adresu IP. Aby otrzymać adres IP, musisz użyć funkcji WinSock gethosrbyname () oraz inet_ntoa (). Poniższy kod pobiera adres IP Yiostaftp.microsoft.com:
HOSTENT *lpHostEnt;
IpHostEnt = gethostbyname( "ftp.microsoft.com" ) ;
char *lpAddr = (char *)
inet_ntoa(*(LPIN_ADDR)*(lpHostEnt->h_addr_list) }; // W tym momencie IpAddr wskazuje na adres IP (198.105.232.1)

Należy pamiętać, że funkcja Connect () może wymagać pewnego czasu, zanim wykona swoje zadanie. To oznacza, że aplikacja może się zawiesić podczas próby nawiązania połączenia. Wywołania gniazd oczekujące na wykonanie zadania i związane z ryzykiem zawieszenia programu są nazywane wywołaniami blokującymi. Ponieważ wiele wywołań funkcji csocket, takich jak Read () czy Listen (), jest wywołaniami blokującymi, próby połączenia warto dokonywać w osobnym wątku.
Nasłuchiwanie połączenia
W poprzedniej sekcji dowiedziałeś się, jak w celu nawiązania połączenia można wywołać gniazdo oczekujące na połączenie. Jednak aby samemu nasłuchiwać połączenia, musimy działać nieco inaczej. Zaczynamy tak samo, od utworzenia gniazda. Najpierw tworzymy obiekt csocket, a następnie za pomocą funkcji Create() uzyskujemy uchwyt gniazda.
Po wywołaniu funkcji Create () musimy wywołać funkcję Listen () w celu nasłuchiwania połączeń nadchodzących poprzez wskazany port. Funkcja Listen () nie zwraca sterowania aż do momentu pojawienia się połączenia lub wystąpienia błędu gniazda. Funkcja posiada opcjonalny parametr określający, ile oczekujących połączeń może być przechowywanych w kolejce oczekiwania. Dozwolona wartość należy do przedziału od l do 5. Poniższy kod nasłuchuje połączenia:
if( Socket .ListenO )
AfxMessageBox( "Odpowiedziano na połączenie." ); else
AfxMessageBox( "Błąd gniazda." );
Ostatnią rzeczą, jaką musisz zrobić przed skomunikowaniem się z wywołującym komputerem po nawiązaniu połączenia, jest stworzenie drugiego obiektu csocket. Do komunikacji będziesz używał tego drugiego obiektu, a nie poprzedniego gniazda. Aby stworzyć drugie gniazdo, użyj funkcji Accept (). Jej pierwszym argumentem jest obiekt csocket dostępny dla połączenia. Argumenty drugi i trzeci są opcjonalne. Drugim argumentem, jeśli zdecydujesz się na jego podanie, jest adres struktury SOCKADDR. Ta struktura zostanie wypełniona informacjami o połączeniu. Trzecim argumentem jest rozmiar struktury przekazanej w drugim argumencie, czyli w tym przypadku rozmiar struktury SOCKADDR w bajtach. Poniższy kod przyjmuje połączenie:
CSocket SecondSocket;
if( Socket.Accept( SecondSocket } )
AfxMessageBox( "Wywołanie Accept() się powiodło." ); else
AfxMessageBox( "Wywołanie Accept() się nie powiodło." );
Ważne jest, by zdawać sobie sprawę, że do przesyłu danych używamy drugiego gniazda. Dzięki temu pierwsze gniazdo może być użyte do dalszego nasłuchiwania połączenia.
Odczyt i zapis danych
Po nawiązaniu połączenia odczyt i zapis danych jest już względnie łatwy. Funkcja Receive () odczytuje dane ze odległego punktu końcowego, zaś funkcja send () wysyła dane do tego punktu końcowego. W obu funkcjach pierwszym argumentem jest bufor danych, zaś drugim argumentem jest rozmiar tego bufora. Obie funkcje posiadają trzeci, opcjonalny argument, który opiszemy w dalszej części sekcji. Oto przykłady odczytu i zapisu danych:
Odczyt danych:
int nBytesRead; char cbBuffer[2000]; nBytesRead =
Socket.Receive( cbBuffer,sizeof( cbBuffer

Zapis danych:
int nBytesWritten;
static char szMessage[] = "Hello socket world!";
nBytesWritten =
Socket.Receive( szMessage, sizeof ( szMessage ) );
Funkcja Receive() posiada trzeci, opcjonalny parametr. Określa on sposób działania funkcji. Wartość może być kombinacją znaczników z tabeli 34.7.
Tabela 34.7. Opcjonalne znaczniki funkcji Receive()
Znacznik
Opis
MSG_PEEK
Kopiuje nadchodzące dane. Dane są kopiowane do bufora, lecz nie są usuwane z kolejki wejściowej.
MSG_OOB
Przetwarza dane " spoza pasma". Takie dane mogą być dostarczane niezależnie od standardowych danych.

Funkcja Send (} także posiada trzeci, opcjonalny parametr. Określa on sposób działania funkcji. Wartość może być kombinacją znaczników z tabeli 34.8.
Tabela 34.8. Opcjonalne znaczniki funkcji Send()
Znacznik
Opis
MSG_DONTROUTE

Określa, że dane nie powinny podlegać routowaniu.
Dostawca Windows Socket może zignorować ten znacznik.
MSG_OOB Przetwarza dane "spoza pasma". Takie dane mogą być dostarczane niezależnie od normalnych danych.
Program Sockets
Program Sockets może nasłuchiwać połączenia lub może łączyć się z nasłuchującym punktem końcowym. Aby uruchomić program, możesz zbudować projekt Sockets.dsw lub po prostu w Eksploratorze Windows otworzyć plik Sockets.exe.
Ten program demonstracyjny znajduje się na dołączonej do książki płytce CD-ROM, w kartotece Rozdz34\Sockets. Program jest zbudowany w oparciu o klasę csocket i korzysta z jednego z dwóch wątków, w zależności od wybranej opcji menu.
Jeśli zdecydujesz się na zainicjowanie połączenia przez wybranie w menu Sockets polecenia Wywołaj, pojawi się okno dialogowe. W tym oknie możesz określić docelowy adres IP oraz port, poprzez który chcesz się komunikować. Rysunek 34.3 przedstawia okno dialogowe z wypełnionymi informacjami o połączeniu. Fragmenty kodu źródłowego programu zostały przedstawione na listingu 34.2.
końcowym podaj adres IP oraz numer portu
Sockets
Położenie na płytce: Rozdz34\Sockets
Plik wykonywalny: Sockets.exe
Moduły kodu źródłowego w tekście: SocketsVie\v.cpp
Listing 34.2. Fragmenty pliku SocketsVie\v.cpp programu Sockets.exe
// SocketsView. cpp : implementation of the // CSocketsYiew class
#include "stdafx.h" #include "Sockets.h"
#include "SocketsDoc.h"
#include "SocketsYiew.h"
#include "HostAddr.h"
#include "Port. h"
#include "Message.h"
////////////////////////////////////////////// // CSocketsView
//////////////////////////////////////////////
// CSocketsView construction/destruction
CSocketsView: : CSocketsYiew () {
// Zerujemy uchwyt wątku.
m_hCallAnswerThread = NULL;
// Zerujemy zawartość struktury SOCKET_INFO. memset ( &m Socketlnfo, O, sizeoff SOCKET INFO ));
}
CSocketsView::-CSockets
View
}
// Ustawiwamy znacznik przerwania. m_Socket!nfo.bAbort = TRUE;
// czekamy na zakończenie wątku. Dajemy mu
// 10 sekund.
if( m_hCallAnswerThread != NULL )
WaitForSingleObject( m_hCallAnswerThread,10000);
}
//////////////////////////////////
// CSocketsView message handlers
void CSocketsView::OnSocketAnswer() {
CPort Port;
// W oknie dialogowym pobieramy numer portu. if( Port.DoModal(} == IDOK ){
// Wypełniamy pola struktury SOCKET_INFO // dla wątku.
m_Socket!nfo.bAbort = FALSE; m_Socket!nfo.bListening = TRUE; m_Socket!nfo.nPort = Port.m_nPort;
// Uruchamiamy wątek. CWinThread *lpThread =
AfxBeginThread( CSocketsView::AnswerThread, (LPYOID) &m_Socket!nfo );
// Zapamiętujemy uchwyt wątku. m_hCallAnswerThread = lpThread->m_hThread;
}
void CSocketsYiew: :OnUpdateSocketAnswer (CCmdUI* pCmdUI)
{
// Włączamy tylko wtedy, gdy nie wywołujemy,
// nie nasłuchujemy ani nie jesteśmy akurat połączeni
pCmdUI->Enąble ( !m_SocketInfo.bConnected &&
!m_Socket!nfo.bCalling &&
!m_Socket!nfo .bListening );
}
void CSocketsView::OnSocketCall() {
CHostAddr HostAddr;
// W oknie dialogowym pobieramy adres hosta. if( HostAddr.DoModal() == IDOK ){
// Wypełniamy pola struktury SOCKET_INFO
// dla wątku.
m_SocketInfo.bAbort = FALSE;
m_SocketInfo.bCalling = TRUE;
m_SocketInfo.nPort = HostAddr.m_nPort;
strcpyf m_Socket!nfo.szHost, HostAddr.m_strHostAddr
// Uruchamiamy wątek. CWinThread *lpThread =
AfxBeginThread( CSocketsView::CallThread, (LPYOID) &m_Socket!nfo );
// Zapamiętujemy uchwyt wątku. m_hCallAnswerThread = lpThread->m_hThread;
}
}
void CSocketsView: :OnUpdateSocketCall (CCmdUI* pCmdUI)
{
// Włączamy tylko wtedy, gdy nie wywołujemy,
// nie nasłuchujemy ani nie jesteśmy akurat połączeni
pCmdUI->Enable ( !m_Socket!nfo.bConnected &&
!m_Socket!nfo .bCalling &&
!m_Socketlnfo .bListening ) ;
}
void CSocketsView::OnSocketDisconnect()
{
// Ustawiamy znacznik przerwania, m_Socketlnfo.bAbort = TRUE;
}
void CSocketsYiew: :OnUpdateSocketDisconnect (CCmdUI* pCmdUI)
{
// Włączamy tylko wtedy, gdy nie wywołujemy,
// nie nasłuchujemy ani nie jesteśmy akurat połączeni.
pCmdUI->Enable ( ( !m_SocketInfo .bConnected &&
!m_Socket!nfo. bCalling &&
!m Socketlnfo. bListening ) ) ;
}
void CSocketsView::OnSocketSendmessage() {
CMessage Message;
// W oknie dialogowym pobieramy treść komunikatu. if( Message.DoModal() }
// Kopiujejemy treść komunikatu do struktury // SOCKET_INFO.
strcpy( m_SocketInfo.szOutgoingMessage, Message.m_strMessage );
}
void CSocketsView::OnUpdateSocketSendmessage(CCmdUI* pCmdUI)
{
pCmdUI->Enable( m_SocketInfo.bConnected && m_SocketInfo.bCalling );
}
UINT CSocketsYiew::CallThread( LPYOID Iplnf ) {
SOCKET_INFO *lplnfo = (SOCKET_INFO *) Iplnf;
int nRetYalue = 0;
CSocket CallSocket;
BOOL bDone = FALSE;
.
// Tworzymy gniazdo
if( !CallSocket .Create ( lpInfo->nPort )
// Rejestrujemy błąd.
Iplnf o->dwLastError = GetLastError ()
// Ustawiamy zwracaną wartość.
nRetValue = l ;
// Wychodzimy.
goto CallEnd;
}
// Przygotowujemy strukturę SOCKADDR_IN.
SOCKADDR_IN Addr;
memset ( &Addr, O, sizeof( SOCKADDR_IN ) );
Addr .sin_family = AF_INET;
Addr . sin_port = htons ( Iplnf o->nPort );
Addr. sin addr.s addr = inet addr ( Iplnf o->szHost);

// Łączymy się ze zdalnym gniazdem. if( ICallSocket.Connect( (LPSOCKADDR)
sizeof( SOCKADDR_IN ) ) ){
// Rejestrujemy błąd.
SAddr,
1pInfo->dwLastError = GetLastError() // Ustawiamy zwracaną wartość. nRetValue = 2; // Wychodzimy, goto CallEnd;
}
// Ustawiamy znacznik połączenia. lpInfo->bConnected = TRUE; // Sygnalizujemy połączenie dźwiękiem. MessageBeept -l );
while ( !bDone ) {
// Odczekujemy, aby oszczędzić czas procesora. Sleep( 200 );
// Sprawdzamy znacznik przerwania. if( lp!nfo->bAbort ) bDone = TRUE;
// Sprawdzamy, czy mamy komunikat do wysłania. else if( lp!nfo->szOutgoingMessage [0] != O ){
// Wysyłamy dane. int nBytes =
CallSocket.Send( Iplnfo->szOutgoingMessage, strlen( lp!nfo->szOutgoingMessage ) );
// Sprawdzamy, czy wystąpił jakiś problem. Jeśli // tak, wychodzimy.
if( nBytes == O || nBytes == SOCKET_ERROR ) bDone = TRUE;
// Zerujemy pierwszy znak w buforze komunikatu. 1pInfo->szOutgoingMessage[0] = 0;
}
}
CallEnd:
// Zerujemy znaczniki.
lpInfo->bCalling =
lpInfo->bListening = lpInfo->bConnected = FALSE;
// Wracamy do funkcji wywołującej return ( nRetValue );
}
UINT CSocketsView::AnswerThread( LPYOID Iplnf )
SOCKET_INFO *1pInfo = (SOCKET_INFO *) Iplnf; int nRetValue = 0; CSocket ServerSocket; BOOL bDone = FALSE; CSocket ClientSocket;
if( !ServerSocket.Create( lpInfo->nPort ) )' // Rejestrujemy błąd.
1pInfo->dwLastError = GetLastError(); // Ustawiamy zwracaną wartość. nRetIalue = 1; // Wychodzimy, goto AnswerEnd;
}
if(!ServerSocket.Listen(1) ){
// Rejestrujemy błąd.
1pInf o->dwLastError = GetLastError ();
// Ustawiamy zwracaną wartość.
nRetValue = 2;
// Wychodzimy.
goto AnswerEnd;
}
if(! ServerSocket .Accept ( ClientSocket)){
// Rejestrujemy błąd.
1pInf o->dwLastError = GetLastError();
// Ustawiamy zwracaną wartość.
nRetValue = 3;
// Wychodzimy.
goto AnswerEnd;
}

// Ustawiamy znacznik połączenia. 1pInf o->bConnected = TRUE; // Sygnalizujemy połączenie dźwiękiem. MessageBeep( -l ) ;
while ( ! bDone ) {
// Deklarujemy bufor znaków i czyścimy go
char cbBuffer[1000] ;
memset ( cbBuffer, O, sizeof( cbBuffer ) },
// Odczytujemy nadchodzące dane. Ta funkcja jest
// blokująca i nie zwraca sterowania aż do odczytania
// danych lub wystąpienia błędu gniazda.
int nBytes =
ClientSocket . Receive ( cbBuffer, sizeof( cbBuffer ) ) ;
// Sprawdzamy, czy wystąpił jakiś problem. Jeśli
// tak, wychodzimy.
if ( nBytes == O | | nBytes == SOCKET_ERROR ||
lpInfo->bAbort )
bDone = TRUE;
// W przeciwnym razie wyświetlamy komunikat. else
AfxMessageBox ( cbBuffer ) ;
}
AnswerEnd:
// Zerujemy znaczniki.
lpInfo->bCalling =
lpInfo->bListening = lpInfo->bConnected = FALSE;
// Wracamy do funkcji wywołującej return ( nRetValue ) ;
}
Program SendEmail
Zobaczmy, jak można stworzyć program wysyłający pocztę elektroniczną.
Na dołączonej do książki płytce CD-ROM, w kartotece Rozdz34\SendEmail, znajdziesz program demonstracyjny. Program wysyła komunikat e-mail używając SMTP. Możesz zbudować projekt SendEmail.dsw lub po prostu uruchomić program, otwierając w Eksploratorze Windows plik SendEmail.exe.
Jak działa ten program? Nawiązuje połączenie z serwerem poczty poprzez port 25, zarezerwowany dla SMTP. Następnie rozpoczyna się konwersacja, w której serwer poczty uzyskuje informacje wystarczające do wysłania poczty e-mail. Konwersację kończy specjalna sekwencja tekstu, po czym gniazdo jest zamykane. Fragmenty kodu źródłowego programu zostały przedstawione na listingu 34.3.
Konwersacja pomiędzy klientem a serwerem poczty składa się z kilku elementów. W poniższej konwersacji wysyłamy komunikat "To jest test" do odbiorcy na serwerze mail.myserver.com, o adresie e-mail you@yourserver.com. W dzisiejszych czasach musisz posiadać zwrotny adres e-mail, aby serwer nie odrzucił Twojej wiadomości jako "spamu". Adres zwrotny zawarty w naszym komunikacie to me@myserver.com.
Sender Text Sent (includes CR/LF at end of text)
Maił Server 220 MAIL.MYSERVER.COM SMTP Service at 9 Apr 98
05:17:18 EDT
Client HELO MAIŁ.MYSERVER.COM
Maił Server 250 MAIL.MYSERVER.COM - Hello, MAIŁ.MYSERVER.COM
Client Maił From:
Maił Server 250 MAIŁ accepted
Client RCPT to:
Maił Server 250 Recipient accepted
Client DATA
Maił Server 354 Start maił input, end with .
Client From: me@myserver.com
Client To: you@yourserver.com
Client Subject: Test
Client
Client To jest test.
Client
MailServer
250 OK Client QUIT Mail Server 221 MAIŁ.MYSERVER.COM Service closing
transmission channel

SendEmail
Położenie na płytce: Rozdz34\SendEmail
Plik wykonywalny: SendEmaiI.exe
Moduły kodu źródłowego w tekście: Email.cpp
Listing 34.3. Fragmenty pliku źródłowego Email.cpp programu SendEmail.exe
// Email.cpp
ftinclude "stdafx.h" ttinclude "Email.h"
CEmail::CEmail()
{
// Ustawiamy wskaźnik wątku i wątek // na NULL.
m_pSendEmailThread = NULL; m hThread = NULL;
}
CEmail::-CEmail()
{
// Ustawiamy znacznik przerwania. m_Info.bAbort = TRUE;
// Sprawdzamy, czy wskaźnik i uchwyt wątku
// maja wartość NULL.
if( m_pSendEmailThread && m_hThread != NULL}
// Oczekujemy na zakończenie wątku.
::WaitForSingleObject( m hThread, 15000);
}
void CEmail::Send( const char *lpszEmailAddress, const char *lpszMessage, const char *lpszFrom, const char *lpszHost, const char *lpszSubject )
{
// Jeśli wskaźnik wątku nie zawiera NULL, // wątek został już utworzony, if( m_pSendEmailThread != NULL ) return;
// Wypełniamy strukturę EMAIL_INFO informacjami // takimi jak adres email i treść komunikatu. m_Info.pSendEmailThread = &m_pSendEmailThread; strcpy( m_Info.szEmailAddress, IpszEmailAddress ); strcpy( m_Info.szMessage, IpszMessage ); strcpy( m_Info.szFrom, IpszFrom ); strcpyt m_Info.szHost, IpszHost ); strcpy( m_Info.szSubject, IpszSubject );
// Zerujemy znaczniki przerwania i zakończenia
// wykonania oraz ustawiamy gniazdo na INVALID_SOCKET,
// dzięki czemu wiemy, że jeszcze nie stworzyliśmy
// gniazda.
m_Info.bAbort = FALSE; m_Info.bCompleted = FALSE; m_Info.hSocket = INVALID_SOCKET;
// Uruchamiamy wątek. m_pSendEmailThread =
AfxBeginThread( CEmail::SendThread,(LPVOID)&m_Info;
// Zapamiętujemy uchwyt wątku.
m hThread = m pSendEmailThread->m hThread;
}
BOOL CEmail::SendSocketData( EMAIL_INFO *lplnfo, char *cbBuffer, CSocketk MailSocket, const char *lpszToken )
{
// Wstrzymujemy działanie, aby bufor się oczyścił
Sleep( 100 };
// Wysyłamy dane.
MailSocket.Send( cbBuffer, strlen( cbBuffer ) };
// Sprawdzamy ostatni błąd i zapamiętujemy go.
// Jeśli wystąpił błąd inny niż WSAEWOULDBLOCK,
// wychodzimy.
Iplnfo->dwLastError = GetLastError();
if( lpInfo->dwLastError != O &&
1pInfo->dwLastError != WSAEWOULDBLOCK )
goto SendSocketDataError; lp!nfo->dwLastError = 0;
// Wstrzymujemy działanie, aby bufor się oczyścił.
Sleep( 100 ) ;
// Zerumemy bufor nadchodzących danych.
memset( cbBuffer, O, BUFFERSIZE ) ;
// Czekamy na nadchodzące danych. MailSocket .Receive ( cbBuffer, BUFFERSIZE );
// Sprawdzamy ostatni błąd i zapamiętujemy go.
// Jeśli wystąpił błąd inny niż WSAEWOULDBLOCK,
// wychodzimy.
lpInfo->dwLastError = GetLastError ();
if( lp!nfo->dwLastError != O &&
lp!nfo->dwLastError != WSAEWOULDBLOCK )
goto SendSocketDataError; lp!nfo->dwLastError = 0;
// Zdarzają się przypadki (gdy lpszToken != NULL) , że
// chcemy porównać IpszToken z buforem nadchodzących danych.
if ( IpszToken != NULL && strnicmp( cbBuffer, IpszToken,
strlen( IpszToken ) ) ){
strcpy( lp!nfo->szErrorMessage, cbBuffer ) ;
lp!nfo->dwLastError = -10000;
goto SendSocketDataError;
}
return ( TRUE ) ;
SendSocketDataError: return ( FALSE ) ;
{
UINT CEmail::SendThread( LPYOID lpInf {
EMAIL INFO *lpInfo = (EMAIL_INFO*)1pInf;

// Deklarujemy bufor dla transmisji danych, char cbBuffer[BUFFERSIZE];
CSocket MailSocket; int nBytesRead; BOOL bMessageTypeOK;
// Tworzymy gniazdo dla transmisji.
MailSocket.Create( 25 );
// Łączymy się z hostem.
MailSocket.Connect( lpInfo->szHost, 25 );
// Sprawdzamy ostatni błąd i zapamiętujemy go.
// Jeśli wystąpił błąd inny niż WSAEWOULDBLOCK,
// wychodzimy.
1pInfo->dwLastError = GetLastError();
if( lp!nfo->dwLastError == O ||
lp!nfo->dwLastError == WSAEWOULDBLOCK ){

// Ustawiamy ostatni błąd na O i przechowujemy // gniazdo w strukturze. lpInfo->dwLastError = 0; lpInfo->hSocket = MailSocket.m_hSocket;
// Zanim odbierzemy dane, pozwolimy // na ustabilizowanie się gniazda. Sleep( 100 );
// Czekamy na nadchodzące dane. nBytesRead =
MailSocket.Receive( cbBuffer, sizeof( cbBuffer ) );
// Sprawdzamy, czy na początku danych otrzymaliśmy 220, bMessageTypeOK =
!( strnicmpf cbBuffer, "220 ", 4 ) );
// Jeśli nie otrzymaliśmy 220, podejmujemy // odpowiednie działania. if( !bMessageTypeOK ){
strcpy( lpInfo->szErrorMessage, cbBuffer );
lpInfo->dwLastError = -10000;
goto EndSendThread;
}
// Sprawdzamy ostatni błąd i zapamiętujemy go.
// Jeśli wystąpił błąd inny niż WSAEWOULDBLOCK,
// wychodzimy.
1pInfo->dwLastError = GetLastError();
if( lpInfo->dwLastError != O &&
lpInfo->dwLastError != WSAEWOULDBLOCK )
goto EndSendThread;
// Jeśli otrzymaliśmy dane... if( nBytesRead > O ){
// Formatujemy łańcuch HELO w celu wysłania do hosta, wsprintf( cbBuffer, "HELO %s\r\n", Iplnfo->szHost); if( !SendSocketData( 1pInfo, cbBuffer, MailSocket,
"250 " ) )
goto EndSendThread;
// Formatujemy łańcuch FROM w celu wysłania do hosta, wsprintf( cbBuffer, "MAIŁ FROM: <%s>\r\n",
lpInfo->szFrom ); if ( !SendSocketData( 1pInfo, cbBuffer, MailSocket,
NULL ) )
goto EndSendThread;
// Formatujemy łańcuch RCPT w celu wysłania do hosta, wsprintf( cbBuffer, "RCPT to: <%s>\r\n",
lp!nfo->szEmailAddress ); if( !SendSocketData( 1pInfo, cbBuffer, MailSocket,
NULL ) )
goto EndSendThread;
// Formatujemy łańcuch DATA w celu wysłania do hosta
strcpy( cbBuffer, "DATA\r\n" );
if( ISendSocketData( Iplnfo, cbBuffer, MailSocket,
NULL ) )
goto EndSendThread;
// Formatujemy komunikat i wysyłamy do hosta. wsprintf( cbBuffer,
"Subject: %s\r\nTo: %s\r\n%s\r\n\r\n.\r\n",
lpInfo->szSubject, 1pInfo->szEmailAddress,
Iplnfo->szMessage ); if( !SendSocketData( 1pInfo, cbBuffer, MailSocket,
NULL ) )
goto EndSendThread;
}
}
EndSendThread:
// Robimy porządki. lpInfo->hSocket = INVALID_SOCKET; lpInfo->pSendEmailThread[0] = NULL; lpInfo->bCompleted = TRUE;
return( O );
}
Pobieranie pliku HTTP
Z pobieraniem pliku HTTP z serwera WWW mamy do czynienia dość często. Szczególnie wtedy, gdy piszemy sieciowego "pająka". Taki program systematycznie pobiera pliki HTTP, wyszukuje w nich odwołania do innych plików HTTP, po czym pobiera następne pliki.
Najprostszym sposobem pobierania pliku HTTP jest użycie funkcji OpenURL (}, stanowiącej funkcję składową klasy cinternetsession. Poniższy kod tworzy obiekt cinternet-Session, a następnie pobiera plik http://www.microsoft.com/file.txt:
II Tworzymy obiekt CInternetSession CInternetSession InternetSession; try {
// Otwieramy plik Http CHttpFile *pHttpFile =
InternetSession.OpenURL(
"http://www.microsoft.com/file.txt" ) ;
// Tworzymy obiekt CFile i otwieramy plik
// do tworzenia.
CFile File;
if( File.0pen( "file.txt",
CFile::modeWrite | CFile:rmodeCreate ) ){
// Pobieramy długość pliku Http, alokujemy
// bufor, odczytujemy dane, zapisujemy dane
// i zwalniamy bufor.
int nLength = pHttpFile->GetLength();
char *pBuffer = new char [pHttpFile->GetLength
pHttpFile->Read( pBuffer, nLength );
File.Write( pBuffer, nLength ) ;
delete [] pBuffer; }
pHttpFile->Close(); } catch( CInternetException *e )
e->Delete();
AfxMessageBox( "Zgłoszono wyjątek." );
}
Nie musisz zamykać pliku reprezentowanego przez obiekt CFile, gdyż jego destruktor sam się tym zajmie. Gdy usuwasz obiekt CHttpFile, konstruktor sam zajmuje się porządkami, więc nie trzeba wywoływać funkcji ciose ().
Program AutoDialer
Przyjrzyjmy się aplikacji automatycznie wybierającej numer telefonu.
Na dołączonej do książki płytce CD-ROM, w kartotece Rozdz34\AutoDialer, znajduje się program demonstracyjny. Program sprawdza, czy w komputerze użytkownika jest nawiązane połączenie z Internetem. Jeśli nie, program automatycznie wybiera numer dostawcy Internetu. Możesz zbudować projekt AutoDialer.dsw lub po prostu uruchomić program AutoDialer.exe w Eksploratorze Windows.
Program wykorzystuje funkcje stanowiące część RAS (Remote Access Service). Klasę CAutoDial możesz wykorzystać we własnym programie, kopiując jej plik do kartoteki swoich plików źródłowych i dołączając go do projektu. Sposób użycia tej klasy pokazuje kod na listingu 34.4.
Aby zainicjować wybieranie, w menu Dial wybierz polecenie Wybierz numer. Pojawi się okno dialogowe, w którym powinieneś podać nazwę połączenia, nazwę użytkownika oraz hasło, tak jak pokazano na rysunku 34.4.
Po wpisaniu informacji program przechodzi do wykonywania kodu klasy CAutoDiai. Przeprowadzany jest cały proces nawiązywania połączenia Dial-up aż do uzyskania połączenia lub wystąpienia błędu. Na rysunku 34.5 przedstawiono klasę CAutoDial w działaniu, podczas nawiązywania połączenia.
AutoDialer
Położenie na płytce: Rozdz34\AutoDialer
Plik wykonywalny: AutoDialer.exe
Moduły kodu źródłowego w tekście: AutoDialerYiew. cpp
Listing 34.4. Fragmenty pliku AutoDialerVie\v.cpp programu AutoDialer.exe
/ / AutoDialerView. cpp : implementation of the // CAutoDialerView class
#include "stdafx.h" #include "AutoDialer .h"
#include "AutoDialerDoc.h" #include "AutoDialerView. h"
#include "DialupPararas .h"
////////////////////////////////////////////// // CAutoDialerView construction/destruction
CAutoDialerView::CAutoDialerView()
CAutoDialerView::-CAutoDialerView()
{
// Mąkę surę we're disconnected. m_AutoDial.Disconnect();
}
//////////////////////
// CAutoDialerView message handlers
void CAutoDialerYiew::OnDialerDial() {
CDialupParams Params;
// W oknie dialogowym pobieramy nazwę połączenia, // nazwę użytkownika oraz hasło. if( Params.DoModal() == IDOK ){
// Wywołujemy funkcję połączenia. Po uruchomieniu // działa w tle poprzez funkcję zwrotną. m_AutoDial.Connect( Params.m_strUserName,
Params.m_strPassword,
Params.m_strDialupName );
// W oknie dialogowym wyświetlamy // stan nawiązywania połączenia. mAutoDial.DoModal();
}
}
void CAutoDialerView::OnDialerHangup()
{
// Rozłączamy się. Możemy to bezpiecznie // zrobić nawet bez wcześniej nawiązanego // połączenia, m_AutoDial.Disconnect();
}
Podsumowanie
W tym rozdziale poznałeś wiele narzędzi, w które możesz wyposażyć swoje programy. Możesz teraz wysyłać i pobierać pliki protokołem FTP. Wiesz także, jak nawiązać połączenie z użyciem klasy CSocket oraz jak przekazywać dane. Wiesz nawet, jak wysyłać pocztę elektroniczną. Potraktuj ten rozdział jako podstawę, rozbuduj przykładowe programy, a przy odrobinie wysiłku sam się zdziwisz otrzymanymi rezultatami.

Wyszukiwarka

Podobne podstrony:
Zestaw poleceń Spikit dla Internet Explorer 11 Windows 7 8 1
m f(angielski dla internautow)
nowa podstawa programowa dla umierkowanych, nacznych i ze sprzężeniami rozporzadzenie 081223 zal 7
Google Toolbar 3 0 128 1 dla Internet Explorer Google Toolbar 3 0 128 1 dla Internet Explorer opis
filologia polska minimum programowe dla studentów MISH 2011 2012
Excel Programowanie dla profesjonalistów
program dla zandarmerii wojskowej 1
Najlepsze programy partnerskie w internecie
UKIE Programy dla przedsiębiorców
NA ZDROWIE Program dla szkół podstawowych

więcej podobnych podstron