Game Programming DirectPlay 를이용한네트워킹 November 21, 2005 배울내용 네트워킹의이해 DirectPlay 인터페이스의사용 네트워크메시지의처리 서버와클라이언트의사용
네트워크의이해 (1) 네트워크모델 서버, 클라이언트, 피어-투-피어 응용프로그램에따라다른모델선택 클라이언트 서버 클라이언트 피어 클라이언트 클라이언트 피어 피어 네트워크주소 (IP address) 4 개의숫자 (0~255) 로구성 피어 네트워크의이해 (2) 대기실 대기실서버 플레이어들을서로연결시킴 지연 (latency) 와랙 (lag) 지연 연산을마치는데걸리는시간 랙 네트워크통신의지체 ( 데이터송신부터수신까지걸리는시간 ) 통신프로토콜 UDP 아주빠르고유연, 데이터정렬이나전달의확실한보장에신경쓰지않음 TCP/IP 확실한메시지전달을보장, 느린속도를감수해야함 ( 정보전송 응답확인 전송검사 )
DirectPlay 의소개 (1) DirectPlay 를사용하여서버, 클라이언트, 피어 - 투 - 피어모델을모두액세스가능 IDriectPlay8Server IDriectPlay8Client IDriectPlay8Peer IDriectPlay8Address 서버객체클라이언트객체피어-투-피어객체네트워크주소를포함하는객체 IDirectPlay8Address 원격네트워크시스템에연결하기위해네트워크주소를구성 플레이어 개별플레이어에게 ID 부여 그룹으로묶을수있음 DirectPlay 의소개 (2) 메시지를이용한네트워킹 메시지가발생할때마다콜백함수호출 전송옵션 보장전송, 보안암호화, 비동기 / 동기 DPN_MSGID_ADD_PLAYER_TO GROUP 기존그룹에플레이어추가 DPN_MSGID_CLIENT_INFO DPN_MSGID_CONNECT_COMPLETE DPN_MSGID_CREATE_GROUP DPN_MSGID_CREATE_PLAYER DPN_MSGID_HOST_MIGRATE DPN_MSGID_SEND_COMPLETE 클라이언트데이터가요청네트워크연결이완료그룹이생성플레이어생성연결이끊겨호스트데이터가다른시스템으로이동데이터가성공적으로전송 DPN_MSGID_TERMINATE_SESSION 네트워크세션이종료되었음......
DirectPlay 의소개 (3) 비동기 vs. 동기 비동기 데이터전송명령을받은후컨트롤반환 동기 모든데이터가성공적으로보내질때까지대기 보안 데이터의암호화 전송시간이더걸림 DirectPlay 는보안메시지를지원 ( 플래그만설정 ) 보장전송 DirectPlay 는특정플래그설정으로메시지의도달을보장할수있음 스로트링 (Throttling) 우선권이낮은메시지를전송차례에서제외시킴 GUID (Globally Unique Identifier, 광역고유식별자 ) 응용프로그램마다부여된고유번호, 다른시스템에연결시이용 생성방법 : Visual C/C++ 디렉터리 \common\tools\guidgen.exe DirectPlay 네트워크객체초기화 (1) HRESULT HRESULT WINAPI WINAPI DirectPlay8Create( GUID* GUID* pciid, pciid, void** void** ppvinterface, ppvinterface, IUnknown* IUnknown* punknown punknown ); ); pciid &IID_IDirectPlay8Server, &IID_IDirectPlay8Client, &IID_IDirectPlay8Peer 예 ) 서버네트워크객체생성 IDirectPlay8Server *pdpserver; if( FAILED(DirectPlay8Create( &IID_IDirectPly8Server, (void**)&pdpserver, NULL ) ) ) // error occurred
DirectPlay 네트워크객체초기화 (2) 네트워크객체에콜백함수지정 네트워크메시지가수신될때마다호출됨 콜백함수원형 (prototype) typedef typedef HRESULT HRESULT (CALLBACK (CALLBACK *PFNDPNMESSAGEHANDLER)( PVOID PVOID pvusercontext, pvusercontext, DWORD DWORD dwmessagetype, dwmessagetype, PVOID PVOID pmessage pmessage ); ); 초기화함수 HRESULT HRESULT IDirectPlay8Server::Initialize( PVOID PVOID const const pvusercontext, pvusercontext, const const PFNDPNMESSAGEHANDLER pfn, pfn, const const DWORD DWORD dwflags dwflags ); ); HRESULT IDirectPlay8Client::Initialize(...); HRESULT IDirectPlay8Peer::Initialize(...); DirectPlay 네트워크객체초기화 (3) 예 ) 서버네트워크객체의콜백함수지정 HRESULT WINAPI MessageHandler( PVOID pvusercontext, DWORD dwmessageid, PVOID pmsgbuffer ); if( FAILED(pDPServer->Initialize( NULL, MessageHandler, 0 ) ) ) // error occurred
주소객체의초기화 (1) IDirect8Address DirectPlay 가 IP 주소를구성하는데사용하는객체 HRESULT HRESULT WINAPI WINAPI DirectPlay8AddressCreate( GUID* GUID* pciid, pciid, void** void** ppvinterface, ppvinterface, IUnknown* IUnknown* punknown punknown ); ); pciid &IID_IDirectPlay8Address 예 ) 주소객체생성 IDirectPlay8Address *pdpaddress; if( FAILED(DirectPlay8AddressCreate( &IID_IDirectPly8Address, (void**)&pdpaddress, NULL ) ) ) // error occurred 주소객체의초기화 (2) IDirect8Address 함수 IDriectPlay8Address::Clear IDriectPlay8Address::SetSP IDriectPlay8Address::AddComponent 주소데이터지움서비스제공자설정구성요소추가 IP Address (String) Provider (GUID) IDrect8 Address (COM Object) Port # (DWORD) Device (GUID)
주소객체의초기화 (3) HRESULT HRESULT AddComponent( AddComponent( const const WCHAR WCHAR *const *const pwszname, pwszname, const const void void *const *const lpvdata, lpvdata, const const DWORD DWORD dwdatasize, dwdatasize, const const DWORD DWORD dwdatatype dwdatatype ); ); pwszname DPNA_KEY_PROVIDER, DPNA_KEY_DEVICE, DPNA_KEY_PORT, DPNA_KEY_HOSTNAME, DPNA_KEY_FLOWCONTROL, DPNA_KEY_PARITY, DPNA_KEY_STOPBITS dwdatatype DPNA_DATATYPE_STRING, DPNA_DATATYPE_STRING_ANSI, DPNA_DATATYPE_DWOARD, DPNA_DATATYPE_GUID, DPNA_DATATYPE_BINARY HRESULT HRESULT SetSP( SetSP( const const GUID GUID *const *const pguidsp pguidsp ); ); pguidsp CLSID_DP8SP_TCPIP, CLSID_DP8SP_IPX, CLSID_DP8SP_MODEM, CLSID_DP8SP_SERIAL 주소객체의초기화 (4) 서비스제공자의설정 포트선택 예약된번호 (1~1024) 외의번호를선택해야안전 장치의할당 Modem TCP/IP 장치를나열한뒤에선택 if( FAILED(pDPAddress->SetSP(&CLSID_DP8SP_TCPIP)) ) // error occurred if( FAILED(pDPAddress->AddComponet(DPNA_KEY_PORT, &dwport, sizeof(dword), DPNA_DATATYPE_DWORD))) // error occurred Service Providers Modem Direct Network Card TCP/IP IPX Serial Port com1 DirectPlay Enumerate Array of SPs 1. TCP/IP Modem 2. TCP/IP Network Card 3. IPX Network Card 4. Modem Modem 5. Serial Serial com1
주소객체의초기화 (5) 나열자 (Enumerator) HRESULT HRESULT IDirectPlay8Server::EnumServiceProviders( const const GUID GUID *const *const pguidserviceprovider, const const GUID GUID *const *const pguidapplication, DPN_SERVICE_PROVIDER_INFO *const *const pspinfobuffer, pspinfobuffer, PDWORD PDWORD const const pcbenumdata, pcbenumdata, PDWORD PDWORD const const pcreturned pcreturned const const DWORD DWORD dwflags dwflags ); ); HRESULT IDirectPlay8Client::EnumServiceProviders (...); HRESULT IDirectPlay8Peer::EnumServiceProviders (...); typedef typedef struct struct _DPN_SERVICE_PROVIDER_INFO DWORD DWORD dwflags; dwflags; GUID GUID Guid; Guid; WCHAR* WCHAR* pwszname; pwszname; PVOID PVOID pvreserved; pvreserved; DWORD DWORD dwreserved; dwreserved; DPN_SERVICE_PROVIDER_INFO, *PDPN_SERVICE_PROVIDER_INFO; 주소객체의초기화 (6) 예 ) TCP/IP 서비스공급자나열 HRESULT ListServiceProviders() HRESULT hr = S_OK; DWORD i 0; DPN_SERVICE_PROVIDER_INFO* pdnspinfo NULL; DPN_SERVICE_PROVIDER_INFO* pdnspinfoenum NULL; DWORD dwitems 0; DWORD dwsize 0; TCHAR strbuf[256] = 0; hr = g_pdp->enumserviceproviders((&clsid_dp8sp_tcpip, NULL, NULL, &dwsize, &dwitems, 0); if( FAILED(hr) && hr!= DPNERR_BUFFERTOOSMALL ) SAFE_DELETE_ARRAY(pdnSPInfo); return hr; pdnspinfo = (DPN_SERVICE_PROVIDER_INFO*) new BYTE[dwSize]; if( FAILED( hr = g_pdp->enumserviceproviders(&clsid_dp8sp_tcpip, NULL, pdnspinfo, &dwsize, &dwitems, 0))) SAFE_DELETE_ARRAY(pdnSPInfo); return hr; pdnspinfoenum = pdnspinfo; for ( i = 0; i < dwitems; i++ ) DXUtil_ConvertWideStringToGenericCch(strBuf, pdnspinfoenum->pwszname, 256); SendMessage( GetDlgItem( g_hdlg, IDC_PROVIDERS ), LB_ADDSTRING, 0, (LPARAM) strbuf ); pdnspinfoenum++; SAFE_DELETE_ARRAY(pdnSPInfo); return hr;
주소객체의초기화 (7) 예 ) 장치설정 서비스공급자의 GUID 를이용하여주소객체완성 if( FAILED(pDPAddress->AddComponent( DPNA_KEY_PROVIDER, &guidsp, sizeof(guid), DPNA_DATATYPE_GUID ))) // error occurred 메시지핸들러의사용 HRESULT WINAPI MessageHandler( PVOID pvusercontext, DWORD dwmessageid, PVOID pmsgbuffer) DPNMSG_CREATE_PLAYER *pcreate; DPNMSG_DESTROY_PLAYER *pdestroy; switch( dwmessageid ) case DPN_MSGID_CREATE_PLAYER: pcreate = (DPNMSG_CREATE_PLAYER *)pmsgbuffer; break; case DPN_MSGID_DESTROY_PLAYER: pdestroy = (DPNMSG_DESTROY_PLAYER *)pmsgbuffer; break; return S_OK;
세션 (1) 각네트워크객체는세션 ( 호스팅하는세션이나참여할대상의세션 ) 에대한정보필요 typedef typedef struct struct _DPN_APPLICATION_DESC DWORD DWORD dwsize; dwsize; DWORD DWORD dwflags; dwflags; GUID GUID guidinstance; guidinstance; GUID GUID guidapplication; DWORD DWORD dwmaxplayers; dwmaxplayers; DWORD DWORD dwcurrentplayers; WCHAR WCHAR *pwszsessionname; WCHAR WCHAR *pwszpassword; *pwszpassword; DPN_APPLICATION_DESC, *PDPN_APPLICATION_DESC; dwflag DPNSESSION_CLIENT_SERVER ( 클라이언트 / 서버구조 ), DPNSESSION_MIGRATE_HOST ( 피어 - 투 - 피어구조에서호스트분실경우, 다른시스템으로이동 ), DPNSESSION_NODPNSVR ( 원격시스템이우리응용프로그램을나열하는것을허락하지않음 ), DPNSESSION_REQUIREPASSWORD ( 로그인암호필요 ) 세션 (2) 예 ) 서버의세션정보설정 세션의이름과암호설정 접속가능한최대플레이어 4 명으로제한 GUID AppGUID = 0xede9493e, 0x6ac8, 0x4f15, 0x8d, 0x1, 0x8b, 0x16, 0x32, 0x0, 0xb9, 0x66 ; DPN_APPLICATION_DESC pdad; ZeroMemory( &dpad, sizeof(dpn_application_desc) ); dpad.dwsize = sizeof( DPN_APPLICATION_DESC ); dpad.pwszsessionname = L MySession ; dpad.pwszpassword = L MyPassword ; dpad.dwmaximumplayers = 4; dpad.guidapplication = AppGUID; dpad.dwflag = DPNSESSION_CLIENT_SERVER DPNSESSION_REQUIREPASSWORD;
세션 (3) 예 ) 클라이언트의세션정보설정 참여할세션의이름과암호설정 서버응용프로그램과동일한 GUID 설정 GUID AppGUID = 0xede9493e, 0x6ac8, 0x4f15, 0x8d, 0x1, 0x8b, 0x16, 0x32, 0x0, 0xb9, 0x66 ; DPN_APPLICATION_DESC pdad; ZeroMemory( &dpad, sizeof(dpn_application_desc) ); dpad.dwsize = sizeof( DPN_APPLICATION_DESC ); dpad.pwszsessionname = L MySession ; dpad.pwszpassword = L MyPassword ; dpad.guidapplication = AppGUID; dpad.dwflag = DPNSESSION_CLIENT_SERVER DPNSESSION_REQUIREPASSWORD; 플레이어 (1) 플레이어의생성 첫번째플레이어 호스트플레이어 ( 세션에남아있음 ) typedef typedef struct struct _DPNMSG_CREATE_PLAYER DWORD DWORD dwsize; dwsize; DPNID DPNID dpnidplayer; dpnidplayer; PVOID PVOID pvplayercontext; DPNMSG_CREATE_PLAYER, *PDPNMSG_CREATE_PLAYER; 예 ) 메시지핸들러의사용 플레이어의정보얻기 HRESULT HRESULT IDirectPlay8Server::GetClientInfo( const const DPNID DPNID dpnid, dpnid, DPN_PLAYER_INFO DPN_PLAYER_INFO *const *const pdpnplayerinfo, pdpnplayerinfo, DWORD DWORD *const *const pdwsize, pdwsize, const const DWORD DWORD dwflags dwflags ); );
플레이어 (2) typedef typedef struct struct _DPN_PLAYER_INFO DWORD DWORD dwsize; dwsize; DWORD DWORD dwinfoflags; dwinfoflags; PWSTR PWSTR pwszname; pwszname; PVOID PVOID pvdata; pvdata; DWORD DWORD dwdatasize; dwdatasize; DWORD DWORD dwplayerflags; dwplayerflags; DPN_PLAYER_INFO, *PDPN_PLAYER_INFO; HRESULT WINAPI ServerMsgeHandler( PVOID pvusercontext, DWORD dwmessageid, PVOID pmsgbuffer) IDirectPlay8Server *pdpserver; DPNMSG_CREATE_PLAYER *pcreateplayer; DPN_PLAYER_INFO *dppi; DWORD dwsize; HRESULT hr; if( (pdpserver = (IDirectPlay8Server*)pvUserContext)) == NULL ) return E_FAIL; 플레이어 (3) switch( dwmessageid ) case DPN_MSGID_CREATE_PLAYER: pcreateplayer = (DPNMSG_CREATE_PLAYER *)pmsgbuffer; dwsize = 0; dppi = NULL; hr = pdpserver->getclientinfo( pcreateplayer->dpnidplayer, dppi, &dwsize, 0); if( FAILED(hr) && hr!=dpnerr_buffer_toosmall ) if( hr==dpnerr_invalidplayer ) break; dppi = (DPN_PLAYER_INFO *)new BYTE[dwSize]; ZeroMemory( dppi, sizeof(dpn_player_info) ); dppi.dwsize = sizeof(dpn_player_info); if( FAILED(pDPServer->GetClientInfo( pcreateplayer->dpnidplayer, dppi, &dwsize, 0 ))) delete [] dppi; break; char szname[32]; wcstombs( szname, dppi->pwszname, 32 ); MessageBox( NULL, szname, Player Joined, MB_OK ); delete [] dppi; return S_OK; return E_FAIL;
플레이어 (4) 플레이어의파괴 typedef typedef struct struct _DPNMSG_DESTROY_PLAYER DWORD DWORD dwsize; dwsize; DPNID DPNID dpnidplayer; dpnidplayer; PVOID PVOID pvplayercontext; DWORD DWORD dwreason; dwreason; DPNMSG_DESTROY_PLAYER, *PDPNMSG_DESTROY_PLAYER; dwreason DPNDESTROYPLAYERREASON_NORMAL, DPNDESTROYPLAYERREASON_CONNECTIONLOST, DPNDESTROYPLAYERREASON_SESSIONTERMINATED DPNDESTROYPLAYERREASON_HOSTDESTROYSERVER HRESULT HRESULT IDirectPlay8Server::DestroyClient( const const DPNID DPNID dpnidclient, dpnidclient, const const VOID VOID *const *const pdestroyinfo, pdestroyinfo, const const DWORD DWORD dwdestroyinfosize, const const DWORD DWORD dwflags dwflags ); ); 서버와클라이언트의사용 서버생성 서버메시지전송 서버세션종료 클라이언트생성 클라이언트메시지전송 클라이언트세션종료
서버생성 (1) GUID AppGUID = 0xede9493e, 0x6ac8, 0x4f15, 0x8d, 0x1, 0x8b, 0x16, 0x32, 0x0, 0xb9, 0x66 ; HRESULT WINAPI = ServerMsgHandler( PVOID pvusercontext, DWORD dwmessageid, PVOID pmsgbuffer ); IDirectPlay8Server *StartNetworkServer( char *szsessionname, char *szpasswrod, DWORD dwport, DWORD dwmaxplayers ) IDirectPlay8Server *pdpserver; IDirectPlay8Address *pdpaddress; DPN_APPLICATION_DESC dpad; WCHAR wszsessionname[256]; WCHAR wszpassword[256]; if( FAILED(DirectPlay8Create(&IID_IDirectPlay8Server, (void**)&pdpserver, NULL))) return NULL; if( FAILED(pDPServer->Initialize(pDPServer, ServerMsgHandler, 0))) pdpserver->release(); return NULL; if( FAILED(DirectPlay8AddressCreate(&IID_IDirectPlay8Address, (void**)&pdpaddress, NULL))) pdpserver->release(); return NULL; 서버생성 (2) pdpaddress->setsp( &CLDIP_DP8SP_TCPIP ); pdpaddress->addcomponent( DPNA_KEY_PORT, &dwport, sizeof(dword), DPNA_DATATYPE_DWORD ); ZeroMemory( &dpad, sizeof(dpn_application_desc) ); dpad.dwsize = sizeof( DPN_APPLICATION_DESC ); dpad.dwflag = DPNSESSION_CLIENT_SERVER; dpad.guidapplication = AppGUID; mbstowcs( wszsessionname, szsessionname, strlen(szsessionname)+1 ); dpad.pwszsessionname = wszsessionname; if( szpassword!= NULL ) mbstowcs( wszpassword, szpassword, strlen(szpassword)+1 ); dpad.pwszpassword = wszpassword; dpad.dwflag = DPNSESSION_REQUIREPASSWORD; dpad.dwmaximumplayers = dwmaxplayer; if( FAILED(pDPServer->Host(&pdad, &pdpaddress, 1, NULL, NULL, NULL, 0))) pdpaddress->release(); pdpserver->release(); return NULL; pdpaddress->release(); return pdpserver;
서버생성 (3) 호스팅함수 서버객체의네트워킹세션을시작함 HRESULT HRESULT IDirectPlay8Server::Host( const const DPN_APPLICATION_DESC *const *const pdnappdesc, pdnappdesc, IDirectPlay8Address **const **const prgpdeviceinfo, prgpdeviceinfo, const const DWORD DWORD cdeviceinfo, cdeviceinfo, const const DPN_SECURITY_DESC *const *const pdpsecurity, pdpsecurity, const const DPN_SECURITY_CREDENTIALS *const *const pdpcredentials, pdpcredentials, VOID VOID *const *const pvplayercontext, const const DWORD DWORD dwflags dwflags ); ); 서버메시지전송 (1)
Tutorials DXSDK\Samples\C++\DirectPlay 1) enumerating the available DirectPlay service providers 2) creation of an address object and hosting a session 3) enumerating the hosts at a given target address 4) connecting 5) sending data 6) host migration 7) lobby launching 8) void support 9) client-server model 10) useing the IDirectPlay8ThreadPool interface Result: EnumSP
Global Variables WinMain ( ) (1)
WinMain ( ) (2) MainDlgProc ( ) (1)
MainDlgProc ( ) (2) DirectPlayMessageHandler ( )
ListServiceProviders ( ) (1) ListServiceProviders ( ) (2)
InitDirectPlay ( ) Other Functions