[ Pobierz całość w formacie PDF ]
.2 CArchive input(&file,CArchive::load); Wiąże gniazdo z odczytywanym archiwum.3 CArchive output(&file,CArchive::store); Wiąże gniazdo z zapisywanym archiwum.Pamiętaj, obiekty CArchive mogą odczytywać lub zapisywać obiekty, ale nigdy w tym samym momencie.Zupełnie akceptowalne jest jednak stworzenie dwóch obiektów CArchive odwołujących się do tego samego gniazda.Możesz dzięki temu odczytywać obiekty z jednego archiwum i zapisywać je do drugiego.I jeszcze jedno: w przypadku używania CArchive nie możesz korzystać z niepewnych połączeń.Obiekty CArchive traktują gniazda jak pliki, a pliki są pewne.Przejdźmy głębiej: CAsyncSocketJeśli potrafisz oprogramować gniazdo, być może nie odpowiada ci, że MFC wykonuje za ciebie tak dużo pracy.Możesz wtedy użyć klasy CAsyncSocket (szczegóły znajdziesz w plikach pomocy MFC).Ta klasa (stanowiąca zresztą klasę podstawową klasy CSocket) umożliwia bezpośredni dostęp do gniazd.Jeśli jednak zdecydujesz się na skorzystanie z CAsyncSocket, będziesz musiał wykonać dużo więcej pracy.Na przykład, użycie CAsyncSocket wymaga ułożenia danych w sieciowej kolejności.Nie możesz także użyć takich gniazd z obiektami CArchive.W praktycznie każdym przypadku, zamiast CAsyncSocket użyjesz CSocket.Znajdą się jednak przypadki, w których lepiej jest użyć CAsyncSocket.W szczególności, CSocket może czasem się zablokować i spowodować, że podczas oczekiwania na połączenie Twój program sprawia wrażenie jakby się zawiesił.Blokujące wywołaniaJedna z największych różnic między odczytem danych z pliku a odczytem z gniazda polega na potencjalnym opóźnieniu.Gdy czytasz obiekt z pliku, to albo w nim jest, albo go nie ma.Jednak w przypadku gniazda nie możesz być pewien czy dane są dostępne.Jeśli spróbujesz odczytać obiekt, Twój program może wstrzymać działanie do momentu, w którym obiekt stanie się dostępny.Jak temu zaradzić? Blokujące wywołania możesz umieścić w osobnym wątku.Możesz także, w niektórych przypadkach, użyć specjalnych wirtualnych funkcji.Na przykład, gdy w gnieździe pojawią się dostępne dane, jest wywoływana funkcja OnReceive.Oczywiście, aby przesłonić tę funkcję, musisz wyprowadzić własną klasę z klasy CSocket.CAsyncSocket także zawiera przesłanialne funkcje dla połączeń, ale nie możesz użyć ich z CSocket.Połączenie CSocket albo się powiedzie, albo nie.Jeśli połączenie się nie powiedzie, do ciebie należy podjęcie kolejnej próby.PrzykładTeraz, gdy znasz już klasę CSocket i potrafisz połączyć ją z CArchive, zastanówmy się jak można wykorzystać je w prawdziwym programie.Zdecydowałem się na przetestowanie swoich umiejętności pisząc prostą grę (rys.9.1).W tej grze staje na przeciw siebie dwóch dowódców.W pierwszej fazie gry umieszcza się na planszy jednostki (powiedzmy czołgi bądź wyrzutnie rakiet).Gdy plansza zostanie wypełniona, komputer jednego z graczy zaczyna pełnić rolę serwera, z którym łączy się drugi gracz.(Aby to przetestować, możesz uruchomić obie strony na tym samym komputerze.) Następnie gracze kolejno bombarduj ą planszę używając myszki.Komputer zgłasza wszystkie pudła i trafienia.Stworzenie klasy zawierającej informacje przesyłane przez sieć jest proste (patrz tabela 9.4).Dla tego prostego pakietu możesz łatwo napisać funkcje serializacji.Choć w tym przypadku nie jest to wymagane, klasy przeznaczone do serializacji możesz wyprowadzić z klasy CObject.Tabela 9.4.Klas CPacketcmd Liczba całkowita identyfikująca komendę (CMD_CLICK, CMD_REPLY, CMD_RESIGN)x Współrzędna X (nie używana z CMD_RESIGN)y Współrzędna Y (nie używana z CMD_RESIGN)data Znak reprezentujący stan komórki o wsp.(x,y) (nie używane z CMD_CLICK ani CMD_RESIGN)Serialize Funkcja zapisująca pakiety do CarchiveW przypadku naszej gry komunikacja przebiega zgodnie z określonym protokołem.Host (serwer) wysyła pakiet CMD_CLICK.Klient odpowiada pakietem CMD_REPLY.Następnie klient przesyła swój pakiet CMD_CLICK i odbiera pakiet CMD_REPLY.Zamiast polecenia CMD_CLICK program może wysłać polecenie CMD_RESIGN aby zakończyć grę.Posiadanie pakietów oczywiście nie wystarczy, aby zagrać w grę.Potrzebny jest także interfejs użytkownika.Nie powinno to być specjalnym problemem, ale blokowane wywołania sieciowe mogą wymagać specjalnej uwagi.'Podstawowy szkielet programuProgram sam w sobie to zwykły program MFC typu dokument/widok, korzystający z klasy CScrollView.Nie ma w nim niczego specjalnego.Większość pracy wykonuje obiekt dokumentu (patrz listing 9.1).Zawiera trójwymiarową tablicę (grid), która w rzeczywistości składa się z dwóch dwuwymiarowych tablic 10 x 10.Pierwsza z nich zawiera planszę lokalnego komputera.Druga tablica przechowuje to, co wiadomo o planszy przeciwnika.Listing 9.1.Obiekt dokumentu gry Battlefield.// Doc.cpp : implementation of the CBfieldDoc class// Battlefield document -- w tym pliku dzieje się większość rzezcy#include "stdafx.h"#include "bfield.h"#include "insert.h"#include "bsocket.h"#include "Doc.h"#include "ConnDlg.h"#include "HostDlg.h"#include "packet.h"#include "mainfrm.h"#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE[] = __FILE__;#endif///////////////////////// CBfieldDocIMPLEMENT_DYNCREATE(CBfieldDoc, CDocument)BEGIN_MESSAGE_MAP(CBfieldDoc, CDocument)//{{AFX_MSG_MAP(CBfieldDoc)ON_COMMAND(IDM_CONNECT, OnConnect)ON_COMMAND(IDM_HOST, OnHost)ON_UPDATE_COMMAND_UI(IDM_CONNECT, OnUpdateConnect)ON_UPDATE_COMMAND_UI(ID_TURN, OnUpdateTurn)ON_UPDATE_COMMAND_UI(IDM_HOST, OnUpdateHost)ON_COMMAND(IDM_RESIGN, OnResign)ON_UPDATE_COMMAND_UI(IDM_RESIGN, OnUpdateResign)//}}AFX_MSG_MAPEND_MESSAGE_MAP()///////////////////////// CBfieldDoc construction/destructionCBfieldDoc::CBfieldDoc(){sockfile=NULL;xmit=NULL;rcv=NULL;MyTurn=FALSE;}CBfieldDoc::~CBfieldDoc(){NetClose();}BOOL CBfieldDoc::OnNewDocument(){if (!CDocument::OnNewDocument())return FALSE;NetClose();// opróżnij planszęfor (int i=0;iFlush();// sprawdź stratyif (HitCt[1]==17){AfxMessageBox("Przegrałeś!");NetClose();OnNewDocument();UpdateAllViews(NULL);}MyTurn=TRUE;CString msg;if (outgoing.data!='X')msg.Format("Przeciwnik trafiony: %c.",outgoing.data);msg=msg+"Twój ruch.";AfxMessageBox(msg);}BOOL CBfieldDoc::GetTurn(){return MyTurn;}// Pomocnicza funkcja do aktualizacji paska stanuvoid CBfieldDoc::UpdateStatus(void){CMainFrame *fw=(CMainFrame *)AfxGetMainWnd();fw->UpdateStatus();}// Będziemy gotowi gdy wszystkie jednostki będą rozstawione (0x1F)BOOL CBfieldDoc::GetReady(void){return placed==0x1F;}// Aktualizacja panelu paska stanuvoid CBfieldDoc::OnUpdateTurn(CCmdUI* pCmdUI){int mode=GetMode();BOOL turn=GetTurn();CString s;if (!mode){if (placed==0x1F)s="Gotów do połączenia";elses="Przygotowywanie.";}else{if (mode==-1) s="Łączenie.";if (turn)s="Twój ruch";elses="Oczekiwanie.";}CString counts;counts.Format("(%d/%d)",HitCt[0],HitCt[1]);s+=counts;pCmdUI->SetText(s);pCmdUI->Enable(TRUE);}Jedyne funkcje w obiekcie widoku służą do wyrysowania planszy i obsługi kliknięć myszką.Funkcja OnDraw (patrz listing 9
[ Pobierz całość w formacie PDF ]