WinHTTP를 쓰려다가 좌절하고… boost::asio로 작성.
#include "HttpClient.h"
void main()
{
    std::wcout.imbue( std::locale("") );
    AsioHttp::Client client;
    client.Init( "leafbird.net", CP_UTF8 );
    std::wcout << client.Request( "/index.php" ).strData << std::endl;
}

개인 프로젝트에 쓰려고 심플하게 만들었다. 이왕에 asio 사용한 김에 format같은 다른 모듈도 내부에서 사용했음. asio를 한 번도 써 본적도 없고 공부한 적도 없는데, 돌아다니다 보니 http client로 사용하는 예제 코드가 있길래 그냥 그것 기반으로 인터페이스만 정리했다.

http client를 알아볼 때 내가 단골 손님으로 들던 예제가 Naver OpenAPI인데, 이 물건으로 네이버 검색을 하려면 아래처럼 한다.

#include "HttpClient.h"
void main()
{
    std::wcout.imbue( std::locale("") );
    AsioHttp::Client client;
    // call Naver OpenAPI
    client.Init( "openapi.naver.com", CP_UTF8 );
    boost::format fmt( "/search?key=%1%&query=%2%&display..." );
    fmt % "개인 개발자 등록키 값"
        % AsioHttp::ToUrlString( "안철수" );
    std::wcout << client.Request( fmt.str().c_str() ).strData;
}

검색어로 한글을 쓰려고 보니 유의해야 할 점이 하나 있던데, UTF8 인코딩 문자만 받는다는 것. 근데 URL에서의 인코딩 문자 데이터 표기방식이 일반적으로 C++에서 쓰는 바이트 정보랑 다르다. ‘안철수’라는 문자를 UTF8로 인코딩한 후  std::string에 담은 값을 바이트 단위로 다시 ‘%’를 붙여서 문자열화 해야 됨. 바이트 데이터가 0xaa 0xbb 0xcc 0xdd 0xee 0xff라면 ‘%aa%bb%cc%dd%ff’처럼 바꿔야 한다는 말이다. 그래서 ToUrlString(...) 이라는 유틸리티 함수도 같이 만들어 두었다. 궁금한 사람은 직접 코드 열어보시오.

만약에 이 물건으로 C++ 프로젝트에서 AJAX 통신을 하겠다고 한다면, response 데이터의 처리가 문제다. 데이터 형식이 json 아니면 xml이 될 텐데.. 이전 포스팅에서도 설명했듯이 C++에서는 두 포맷 모두 심플하게 다루는 게 쉽지가 않다. 취미로 만드는 게임 엔진에 랭크 기능을 웹서버로 만들어 붙이고 통신할 목적으로 만들었는데… response를 어떻게 처리할지는 좀 더 생각해 봐야지.

See Also:


Posted by leafbird 트랙백 0 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply Favicon of https://devnote.tistory.com BlogIcon leafbird 2015.01.21 18:37 신고

    gist에 넣었어요. https://gist.github.com/leafbird/db5d0c2826355cc4487c

작년에 아꿈사에서 TAOCP 공부할 때 처음으로 코루틴에 대해 관심이 있었지만, 별로 와 닿질 않아서 그냥 넘어 갔는데, 그 뒤로 잊혀질 만하면 저 녀석을 접하게 되었다. NDC나 deview같은 컨퍼런스를 통해서도 그렇고, 스터디 모임이나 여기저기 개발자 모임에서..

그럴 때마다 코루틴을 사용하면 비동기 처리가 간편해 진다는데 왜 그렇게 되는지 솔직히 잘 이해가 되질 않았는데, 오늘에서야 드디어 조금 감을 잡았다. 오늘 GPG6권에 있는 설명이랑 월간마소 2012년 1월호를 봤는데 갑자기 해탈했음. (…) 그러고 나서 deview 2011 자료를 다시 보니 이제 PT가 무슨 소리를 하는지 이해가 된다.

같은 프로세스 안에서 비동기 처리를 한다면 멀티스레드 방식이 더 낫다. 게임서버 짜면서 늘 처리해오던 방식이니 그게 더 익숙하기도 하고, 이젠 람다가 있어서 콜백 코드의 정의도 쉽고 보기 편하게 작성할 수 있다. 게다가 코루틴은 별도의 스택을 사용하니까… 무턱대고 많이 만들어대기에도 부담이 있어..

그나마 그럴법한 용도로는 deview에서 말한 것 처럼 RPC 쪽인데… 이 부분도 람다를 이용한 콜백 처리를 할 수 있으니, 코루틴을 꼭 써야 하나 하는 문제는 아직 애매하다. 게임서버끼리 RPC라면 굳이 코루틴이 없어도 될 것 같음. vs2010을 사용할 수 없는 환경이라면 코루틴을 고려해봄 직 하다.

C++11에서 람다 문법이 들어간 것이 아주 많은 것을 가능하게 한 덕에, 코루틴을 적용해서 얻을 수 있는 이점이 많이 희석된 느낌. 어디에 써먹을 수 있을지는 좀 더 생각해 봐야겠다. GPG에 있는 AI 적용 예제 같은 것도 꼼꼼히 읽어봐야지.

 

ps. 내 블로그 카테고리 설정이 영 엉망인듯. 마땅한 카테고리가 없어서 일단 Network로 해둠. 이 글은 무슨 카테고리일까… ‘언어’ 내지는 ‘설계’ 쯤 되려나…

Posted by leafbird 트랙백 0 : 댓글 2

댓글을 달아 주세요

  1. addr | edit/del | reply Favicon of https://catch-22.tistory.com BlogIcon -22 2012.02.11 13:10 신고

    저도 데뷰 영상 봤을땐 도무지 이해가 안갔는데, 오늘 들으면서 아아!!!!!!!!! 했어요^_^!
    너무 감사합니다ㅎㅎ

출처 : http://kin.naver.com/qna/detail.nhn?d1id=1&dirId=1040101&docId=68634535&qb=U09DS0FERFI

sockaddr(2바이트+14바이트)와 sockaddr_in(2바이트+2바이트+4바이트+8바이트)은 정확하게 16바이트로 사이즈가 똑같습니다...^^;
그렇기 때문에 바로 캐스팅이 되는거죠.

sockaddr_in은 rfc표준으로 정해진 주소sockaddr(sockaddr은 TCP/IP만을 위한 것이 아니거든요)를 우리가 주로 사용하는 Internet address familly구조(프로토콜들 종류가 많죠?;)에 사용하기 쉽게 하려고 세분화한것이라고 생각하면 쉽습니다. 고로 케스팅 그냥 해주시면 됩니다.. 

sockaddr_in은 다음과 같구요. 

struct sockaddr_in{
  short sin_family;  // 2바이트
  unsigned short sin_port;  // 2바이트
  IN_ADDR sin_addr;  // 4바이트
  char sin_zero[8];  // 8바이트(실제 사용되지는 않구요. sockaddr과 사이즈를 맞추기 위함)
}; 

sockaddr_in중의 IN_ADDR은 다음과 같습니다. 

struct in_addr {
  union {
    struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; // 4바이트
    struct { u_short s_w1,s_w2; } S_un_w;  // 4바이트
  u_long S_addr;  // 4바이트
  } S_un;
}; 

단, union으로 정의되었기때문에 총 4바이트가 되는 거죠.
sockaddr은 다음과 같습니다. 

struct sockaddr {
  u_short sa_family// 2바이트
  char sa_data[14];  // 14바이트
}; 

sockaddr을 사용하지 않고 sockaddr_in이라는 것을 정의하여 사용하는 이유는 사용의 편의를 위해서입니다.
한번 생각해보세요;  14바이트의 배열에다가 포트와 아이피를 그냥 갖다넣기는 좀 번거롭죠?

Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

저는 2002년 여름 정도부터 소켓 프로그래밍을 시작했습니다.
그때부터 줄곧 네크워크 프로그래밍을 해왔지만, 항상 윈도우 플랫폼 기반이었고 처음 유지보수를 맡았던 코드도 IOCP 기반이었기 때문에 굳이 소켓의 다른 IO모델은 공부할 필요성을 못 느끼고 지냈지요.
어느새 제법 연차가 쌓인 서버 프로그래머가 되었는데, IOCP 말고는 다른 IO모델은 코드파악조차 못하고 있다는게 참 부끄럽습니다.
오늘 인터넷을 뒤지다가 select 모델에 대한 간략한 설명이 있어서 긁어왔습니다.

출처 : http://blog.daum.net/sdr1982/7027363

12-1. I/O 멀티플렉싱 기반의 서버

하나의 전송로를 여러 사용자가 동시에 사용해서 효율성을 극대화 하는 것. 전송로를 공유.

전송로를 공유하기 위해서는 서로 약속이 필요하다.

TDMA : 자료 전송 시간을 일정한 시간 폭으로 나누어서 함께 공유하는 방식

FDMA : 주파수 대역 폭을 나누어서 함께 공유하는 방식


I/O 멀티플렉싱

클라이언트와의 입/출력을 담당하는 프로세스를 하나로 묶어버리는 것.

프로세스 = 고속의 전송로.


multitasking(멀티태스킹) 예시


multiplexing(멀티플렉싱) 예시

※ 데이터 입출력 및 접속 모두를 하나로 묶어서 처리한다.

※ 멀티플렉싱 서버가 멀티태스킹 서버보다 성능이 좋다. -> context switching이 없음.


멀티 프로세스 vs 멀티 플렉싱

멀티 프로세스

클라이언트와 서버간의 송수신 데이터 용량이 큰 경우.

송수신이 연속적으로 발생하는 경우에 적합.

멀티 플렉싱

클라이언트와 서버간의 송수신 데이터 용량이 작은 경우.

송수신이 연속적이지 않은 경우에 적합. (예: 채팅)

멀티 프로세스 기반의 서버에 비해 많은 수의 클라이언트 처리에 접함.


12-2. Select 함수 사용하기

파일 디스크립터의 설정

지정된 파일 디스크립터의 변화를 확인한다.

파일 디스크립터 변화 : 파일 디스크립터를 통해 데이터 송수신이 가능한 상태

예시)


1. 파일 디스크립터 설정

관찰하고자 하는 파일 디스크립터를 모아놓는다. (총 3묶음)

2. 파일 디스크립터의 변화(3가지 종류 존재)

입력 버퍼의 변화 : 수신할 데이터가 존재하는가? (입력 버퍼에 데이터 존재)

출력 버퍼의 변화 : 데이터 전송이 가능한 상태인가? (출력 버퍼에 충분한 여유공간 존재)

예외(OOB)상황 관련 버퍼의 변화 : 소켓에서 예외상황이 발생 하였는가?

3. fd_set 자료형

파일 디스크립터를 구분 지어 모아두기 위한 자료형(비트단위 배열)


파일 디스크립터 검사 범위 설정
매크로 함수

FD_ISSET(int fd, fd_set *fdset);
fdset에서 해당 fd(번호)의 비트가 1인지 확인한다. 1이면 TRUE, 0이면 FALSE.

검사해야 할 파일 디스크립터의 개수(범위)를 지정해 준다.

(select 마지막에 리턴된 디스크립터 값에) 가장 큰 파일 디스크립터 값에 1을 더해서 인자를 전달.


Timeout(타임아웃) 설정

Timeout : 정해 놓은 시간이 지나면 (select)함수가 리턴하게 한다.
struct timeval!
{
    long tv_sec;    // 초단위
    long tv_usec;  // 마이크로초단위 1/10^6
};

select함수
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>

int select(int n, fd_set * readfds, fd_set *writefds, fd_set * exceptfds,struct timeval! * timeout);
리턴 : 오류 발생이면 -1, 타임아웃이면 0, 변화가 발생하면 0이 상에 상태가 바뀐 파일 디스크립터 수.
n : 파일 디스크립터 개수(범위) 지정.
readfds : 입력버퍼(수신)할 데이터가 있는지 검사.
writefds : 출력버퍼가 충분히 여유가 있는지 검사.
exceptfds : OOB에 대한 검사. ※ 추후설명
timeout : 타임아웃 설정관련 인수.


select함수를 호출하기 전에는 fd_set에 검사하기 원하는 index의 비트1로 세팅해 놓는다.
예시) FD_SET(0, &read_s);    // standard input에 대한 디스크립터를 1로 세팅.
select함수가 리턴이 되면 fd_set에 검사한 결과에 대한 index의 비트만이 1로 세팅되어 있을 것이다.
※ select함수 호출 전과 호출 후에 값이 바뀌기 때문에 select함수를 호출할 때마다 초기화할 수 있도록 원본 fd_set을 복사하여 사용한다.
※ timeout도 select함수 호출 전과 호출 후에 값이 바뀌기 때문에 루프 시작할 때 값을 새로 초기화 주는 것이 좋다.

12-3. 멀티플렉싱 서버의 구현
FD_SET(serv_sock, &reads);
※ 서버소켓으로 오는 입력버퍼(데이터)의 내용이 중요하다. 그 내용은 클라이언트로부터 들어오는 연결요청(일종의 데이터이기 때문)이다.
fd_max : 검사할 디스크립터 개수(범위)를 저장하기 위한 값. 0~fd_max+1의 범위로 설정한다.

12-4. 윈도우즈 기반으로 구현하기
select 함수와 timeval! 구조체는 선언된 형태와 기능이 리눅스와 같다.
int select(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval! FAR *timeout);

※ fd_set 자료형은 기능이 리눅스와 같지만 선언형태는 다르다.
typedef struct fd_set{
    u_int fd_count;                                 // 얼마나 많이 설정되어 있는가?
    SOCKET fd_array[FD_SETSIZE];      // SOCKET의 배열
}fd_set;
※ 소켓의 핸들 값이 일정하지 않고 윈도우즈에는 리턴된 핸들 값을 기억하는 것이 편하다.
그러므로 소켓 핸들의 개수를 fd_set 내부적으로 보관하고 있다.


※ 다음 매크로는 리눅스와 사용 방법은 거의 같다.
FD_CLR(s, *set)
:set 포인터가 가리키는 배열에서 핸들 s를 삭제한다.
FD_ISSET(s, *set)
:핸들 s가 set포인터가 가리키는 배열의 멤버라면 TRUE를 아니면 FALSE를 리턴한다.
FD_SET(s, *set)
:핸들 s를 set포인터가 가리키는 배열의 멤버에 포함시킨다.
FD_ZERO(*set)
:set 포인터가 가리키는 배열을 0으로 초기화한다. 

select(0, &temps, 0, 0, &timeout)
※ 윈도우즈 기반에서는 nfds인수가 필요없다.

 
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

1. 관련 작업 전 꼭 출처 따라가서 글 읽어보기. http://gpgstudy.com/forum/viewtopic.php?t=6273

10.0.0.0 - 10.255.255.255
172.16.0.0 - 172.31.255.255
192.168.0.0 - 192.168.255.255
는 수동 사설 IP,

169.254.0.0 -169.254.255.255
는 자동 사설 IP입니다.

(Windows에서 DHCP응답 없으면 169.254.0.0/16으로 아무 IP나 잡는거 보셨겠죠.)

위의 4개의 영역이 사설 IP라고 지정되어있으므로 클라이언트가 보고 하는 IP가 위의 4개 영역 안에 있다면 사설 IP라고 인식해주면 되죠.
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

1. 제한수 늘릴 때 꼭 링크 따라가서 읽기. http://gpgstudy.com/forum/viewtopic.php?t=3128
2. gpg에서 MaxUserPort로 검색하기.
3. 빗자루님 개인 홈페이지 관련글 http://www.myevan.net/phpBB/viewtopic.php?t=148

안녕하세요. 매크로 없는 메비~랍니다.

WinNT 계열의 TCP/IP 설정에 보면 Accept 할 수 있는 소켓 개수를 제한해 놓은 부분이 있습니다. 리눅스에서 소켓 제한이 컴파일시에 걸리듯이 Windows 2000/XP의 경우(2003도 같다고 알고 있지만 확인은 안해봤네요.) 이 셋팅을 바꿔주지 않으면 Listen->Accept 할 수 있는 소켓의 개수는 1024 ~ 5000 번 이내에서만 할당이 되고 대략 3977 개 이상은 받을 수 없게 됩니다.

그렇지만 Windows 에서 이 사이에 쓰는 접속도 있고 여러가지 클라이언트 접속도 이 사이에 들어오는 경우가 많기 때문에 실제 접속은 3977 보다 적어지게 됩니다. 다음 레지값을 적용해주고 재시작(이 꼭 필요한지는 모르겠습니다만 저는 매번 재시작을 해줬네요.)을 해주시면 됩니다. 아래 레지는 TCP/IP 레이어에 작용하기 때문에 IIS나 기타 서비스등을 통한 대량의 접속을 받고 싶을때도 적용을 해주셔야 많은 동접을 받을 수 있습니다.


Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]
"MaxUserPort"=dword:0000fffe
"TcpWindowSize"=dword:00004470

p.s.테스트 한번 더 부탁합니다. ^____^

p.s.2.물론.. 소켓이 한계이상 올라가면 그것도 곤란합니다. 1만개만 해도 10k 이고 기본 소켓당 버퍼를 32KB 씩만 할당해 주더라도 320MB가 순수하게 소켓 버퍼로 사용되게 됩니다. 하드하죠..? (라지만 요즘 서버들 1~2G는 기본이고 좀 넉넉히 4G씩 주기도 하죠.)
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

NOTE : 현재 작업중인 서버 엔진에 IP를 입맛대로 골라올 수 있는 함수가 제작되어 있음. 참고할 것.



출처 : 데브피아 Q&A 게시판

WSADATA wsaData;
WORD wVersionRequested = MAKEWORD( 2, 0 );
char* spIPAddress;
char sBuf[MAX_PATH]={0};
char szPath[MAX_PATH]={0};
CString strPath;

if( WSAStartup(wVersionRequested, &wsaData) == 0 )
{
    if( gethostname(CONENVINFO_szHostName, sizeof(CONENVINFO_szHostName)) == 0 )
    {
        PHOSTENT hostinfo;
        if( (hostinfo = gethostbyname(CONENVINFO_szHostName)) != NULL )
        {
            // 클라이언트 Ip 개수만큼 돈다.
            for(int i=0; hostinfo->h_addr_list[i] != 0 ; i++)
            {
                spIPAddress = inet_ntoa( *(struct in_addr*)hostinfo->h_addr_list[i] );
                // 경로를 얻어온다.
                GetCurrentDirectory(MAX_PATH,szPath);
                strPath.Format("%s%s",szPath,"\\AAA.ini");
               
                // AAA.ini 파일의 ServerLint를 읽어온다.
                GetPrivateProfileString("ServerList","consolemngd.server","",sBuf,MAX_PATH,strPath);

                // IP Address xxx.yyy.yyy.yyy 중 xxx의 값을 서버랑 클라이언트랑 비교.
                if( spIPAddress[0] == sBuf[0] && spIPAddress[1] == sBuf[1] && spIPAddress[2] == sBuf[2])
                {
                    if( spIPAddress && *spIPAddress )
                    {
                        // 서버로 데이터를 넘기기위해 CONENVINFO_szIPAddress에 ip주소 복사.
                        strcpy( CONENVINFO_szIPAddress, spIPAddress );
                        return;
                    }       
                }
            }
        }
    }           
    WSACleanup();
}

대충 설명을 드리자면 클라이언트에 존재하는 ip를  h_addr_list[i]로 검색하여 서버ip주소를 가진 AAA.ini파일의 내용과 xxx.yyy.yyy.yyy중 xxx를 비교하여 값을 서버의 ip랑 xxx가 동일한 값을 넘기는 로직입니다.
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

http://alumnus.caltech.edu/~dank/peer-nat.html
UDP Hall Punching 에 대해 간략히 소개되어 있습니다.
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

온라인 게임의 기술 동향

                                                                                            2002.10.21
                                                                                            김주남/Nexon
최신 온라인 게임의 전체적인 기술 동향을 살펴보면, 단순한 Server/Client 구조의 한계를 벗어나 P2P를 적극 이용함으로써 네트웍 bandwidth를 분산시키고, 좀 더 빠른 게임 환경을 만드는 방향으로 진행중이다.

대표적인 예로 CrazyArcade의 게임들은 아이템 취득 등의 중요한 정보는 서버/클라이언트 간의 TCP 통신을 이용하는 반면, 캐릭터 이동 등에 관한 정보는 UDP를 통한 P2P 통신으로 처리되고 있다.

이러한 P2P 기술을 적용함에 있어서 가장 걸림돌이 되는 부분이 바로 VPN (Virtual Private Network) 상의 클라이언트들을 지원하는 것이다.  요즘은 많은 가정에서 IP 공유기 등을 통한 VPN 사용이 보편화 되고 있는 추세이므로 이들 서로 간의 통신을 지원해야 한다.

VPN에서 가장 많이 사용되는 기술은 NAPT(Network Address Port Translation) 이다. 즉 가상으로 할당된 IP 주소와 port를 NAT가 실제 IP address와 port로 실시간 매핑/변화 시켜 주는 방식을 사용한다. 일반적인 매핑 방법으로는 Full Cone NAT 방식과 Symmetric NAT 방식이 있다. Full Cone NAT는 ( Real IP Address : port ), ( Virtual IP Address : port ) 2가지 정보로 매핑 테이블을 유지한다. 반면 Symmetric NAT는 앞의 정보에 (Destination IP Address : port) 정보를 추가함으로써 처음 패킷을 보낸 host에게로 binding 되도록 되어 있다.

이렇게 VPN에서는 IP와 Port가 동적으로 바뀌므로 예전과 같은 static port 할당과 같은 방식을 지양해야 한다.

CA에서는 이를 위해 초기 접속 시 서버와 클라이언트 사이에 UDP를 주고 받도록 함으로써 UDP 통과 가능성을 먼저 검증한다. 이 때 항상 클라이언트가 서버로 먼저 UDP 패킷을 보내고 서버는 도착한 패킷에 찍힌 Source IP Address와 port로 UDP 패킷을 보내도록 되어 있다. 물론 UDP가 unreliable이라는 점을 감안하여 여러 번 시도하게 된다. 이러한 과정을 통해 접속된 게임 클라이언트가 Public Host(Login Server)와 UDP 통신이 가능하다는 것을 보장하게 된다.

하지만 실제 게임을 진행하기 위해서는 이보다 더 혹독한(?) 검증이 있어야 한다. 실제로 게임 클라이언트 사이에 UDP 통신이 가능한지를 살펴보아야 한다. 이 때의 상황은 훨씬 더 복잡하다. 각 게임 클라이언트는 Public Host일 수도 있고, VPN 내에 있을 수도 있으며, 서로 같은 VPN에 내의 클라이언트 일 수도 있는 것이다. 따라서 서버와의 통신을 통해 각 클라이언트의 실제 매핑된 IP Address, port와 Virtual IP Address, port를 서로 주고 받아야 한다. 그리고 각각의 IP Address, port로 여러 번의 UDP 패킷 쿼리를 한 후 이에 응답이 온 패킷에 실린 IP Address와 port를 사용해야 한다. 대부분은 이러한 작업을 거치면 UDP 통신이 원활하게 이루어 진다. 하지만 앞에서 언급했던 NAT 종류에 따라 지원이 안되는 클라이언트가 생기기도 한다. 예를 들어 각각 다른 VPN안에 속하는 클라이언트 끼리 UDP 통신을 하기 위해서는 둘 중 하나에 먼저 mapping table이 생성되어야 한다. 이 때 Full Cone 방식의 NAT라면 큰 문제가 되지 않으나 서로 Symmetric NAT방식을 쓸 경우는 상대방 IP Address와 Port 까지도 일치해야 하므로 NAT 수준에서 패킷이 필터링되어 전달되지 않는다. 일반적으로 기업에서 쓰는 NAT는 보안을 어느 정도 중시 하므로 SNAT를 사용하나 가정용 IP 공유기는 Full Cone NAT를 사용하기 때문에 가정에서 게임을 즐기는데는 큰 문제가 발생하지 않는다.

요즘은 게임의 용량이 커짐에 따라 게임 데이터를 다운로드 하는데 있어서도 P2P의 중요성이 높아지고 있다. 수많은 게임 데이터를 초기 배포할 때 모두 포함시길 경우, 다운로드가 느려지고, 서버로의 트래픽도 무시하지 못하게 된다. 때문에 필요한 데이터를 실시간 다운로드 하는 기술이 요구된다. 그리고 이를 P2P를 이용하여 같은 게임을 플레이 하는 서로간의 클라이언트 끼리 주고 받는 다면 훨씬 더 효율적인 네트웍 이용이 가능해 진다 할 수 있다. 특히나 우리 나라처럼 인터넷 PC방에서의 게임이 일반화된 상황에서 게임방 내의 PC끼리 필요한 데이터를 주고 받는다면 훨씬 더 빠르고 안정적으로 데이터를 송수신하게 되는 셈이다.

현재 CA에 이러한 기술이 적용되어 있다. 여러 가지 아이템에 필요한 데이터들을 우선 순위에 따라 서버에서 직접 또는 주위 클라이언트로부터 받을 수 있도록 되어 있다. BNB를 막 설치하고 10분 정도 후면 파일이 하나 둘씩 늘어나는 것을 눈치챈 사람도 있을 것이다. 이는 현재 사용되지는 않지만 나중에 필요한 파일들을 백그라운드로 받고 있기 때문이다. 이러한 파일들은 P2P를 이용하여 주위의 클라이언트들을 통해 게임에 지장을 주지 않을 정도의 속도로 (4kbyte / s) 천천히 다운로드 하도록 되어 있다. 이에 반해 게임에 당장 필요한 데이터는 서버로부터 직접 Full Speed로 다운로드 한다.

이러한 Background P2P Download 기술에서 핵심적인 요소는 주위의 클라이언트를 파악하고, 전송되는 파일이 정확한 것인지를 파악하는 것이다. 이를 위해 별도의 관리 서버가 존재한다. 이 서버는 현재 접속된 클라이언트들의 데이터를 유지하고, 현재 게임에 필요한 데이터 파일들의 리스트 및 파일들의 크기와 CRC 정보를 제공하고, 직접 파일을 서빙하기도 한다. 클라이언트는 이 서버로부터 파일 리스트와 주위 클라이언트 정보를 받아 각 파일을 주위 클라이언트에게 쿼리하고 TCP를 통한 P2P 연결을 통해 실제로 파일을 받도록 되어 있다. 필요에 따라서는 현재 많은 P2P 프로그램이 그러하듯 여러 클라이언트에 동시에 받을 수도 있다. 그리고 이어 받기를 통해 serving해 주는 클라이언트가 게임을 종료할 경우, 다른 클라이언트로부터 계속 받을 수도 있다.

주위 클라이언트를 파악하는 것은 2단계로 이루어 진다. 먼저 같은 subnetwork에 게임을 하는 클라이언트가 있는지를 파악한다. 이는 multicast를 통해 이루어진다. Win 95 이상의 OS에서는 기본으로 multicast ip가 지원이 되므로 특별한 문제 없이 사용할 수 있다. BNB 및 아스가르드를 하는 모든 클라이언트는 현재 multicast ip “225.2.1.8”에 join 하여 서로 필요한 파일을 쿼리하도록 되어 있다. 현재 대부분의 네트웍 router들이 multicast ip를 지원하지 않고 있기 때문에 이 multicast 를 통한 쿼리는 대부분 같은 subnetwork 안에서만 유효하다. 따라서 broadcasting을 통한 쿼리와의 차이점을 느끼지 못할 수도 있지만 multicast ip 자체가 하드웨어 필터링을 사용하므로 subnetwork 내의 다른 컴퓨터에 영향을 주지 않고, 라우터의 지원 여부에 따라 지원할 수 있는 범위가 넓어진다는 점에서 broadcasting을 통한 쿼리보다 더 낫다고 볼 수 있다.

만약 multicast를 통한 쿼리에 아무런 응답이 없을 경우 서버로부터 받은 클라이언트 리스트를 사용한다. 이 리스트는 최근 해당 파일을 전송 받은 클라이언트들로 구성되므로 파일이 존재할 가능성이 높다. 이 단계에서는 udp를 통한 개별 쿼리를 이용한다. 이 경우 클라이언트들이 무작위로 인터넷에 분포되어 있는 상황이므로 쿼리를 보낸 후 가장 빨리 응답한 클라이언트에게 접속을 시도한다. Ping으로 RTT 타임이 가장 적은 클라이언트에게 붙는 셈이다.

이러한 P2P 다운로드 기술은 좀 더 발전하여 Game On Demand에도 적용될 수 있다. Game On Demand는 게임을 인터넷 상에서 바로 다운로드 받아서 플레이 한 시간 만큼 소액 결제 등을 통해 돈을 지불하며 즐기는 형태이다. 하지만 요즘 게임의 용량이 워낙 커졌기 때문에 다운로드에 소요되는 시간도 무시할 수 없다. 따라서 최소한의 필요한 파일만을 먼저 다운로드 받고 나머지는 게임 플레이 도중 P2P를 통해 빠르게 주위에서 다운로드 받는 형식의 스트리밍 서비스가 선호되고 있다. 또한 API Hooking 등을 통한 기술로 기존의 어떤 게임도 Game On Demand가 가능하도록 하는 방법이 시도 되고 있다. 넥슨은 내년 1월 중으로 선보일 게임에 이 기술을 적용할 예정이다.



출처 : http://cosmos.kaist.ac.kr/cs540/seminar/onlinegametrend.htm
TAG 넥슨
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요