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

type traits

2012. 2. 14. 15:18 from 프로그래밍 팁/C++

traits를 네이버 영한사전으로 검색해보면 특질, 자질 등의 의미를 가진다. EC++ 3판 #47에서는 특성정보라고 번역되어 있고, The C++ 표준 라이브러리 확장 튜토리얼 및 레퍼런스 11장에서는 type traits 전체를 ‘형식 특질’이라고 번역하고 있다.

템플릿 코드를 읽다 보면 traits를 아주 잦은 빈도로 만나게 된다. STL의 컨테이너와 반복자들의 구현에도 많이 사용되었다. 템플릿의 인자로 전달되는 타입을 컴파일 할 때 알아내고 싶을 때 사용하는데, 인자의 타입 뿐 아니라 다른 부가적인 정보들도 더 확인할 수 있다. 간단하게는 포인터형인지(is_pointer) 혹은 배열인지(is_array) 하는 것도 확인할 수 있고, 가상 소멸자를 가지고 있는지 여부(is_virtual_destructor)나 POD인지 여부(is_pod)도 확인할 수 있다.

pod(plain old data)라는 단어 정의를 오늘 처음 접했다. 그리고 컴파일 시점에 pod 여부를 확인하는 것이 가능 하다는 사실에 놀랐다. 이게 가능하다면 네트워크 프로그래밍에서 패킷을 serialize할 때 메모리 복사를 잘못 시행하는 코드를 미리 잡아낼 수 있다. 좀 더 미리 알았더라면 예전에 삽질을 많이 줄일 수 있었을 텐데 아쉽다 ㅠ

아무튼 다시 traits 이야기로 돌아와서..

기본적으로 traits가 무엇이고, 어떤 유익이 있는가 감을 잡기 위해서는 EC++의 챕터 7 템플릿과 일반화 프로그래밍 부분을 읽어보는 것이 좋다. (#41~#48) 여기 설명된 대로 traits는 은근슬쩍(?) 암묵적인 관례들이 하나 둘씩 모이면서 다듬어 졌는데, 그래서 예전부터 traits를 구현한 라이브러리를 여러 곳에서 볼 수 있다. 내가 알고 있는 것만 적어봐도

이중에 boost의 traits는 Technical Report1을 거쳐 현재는 표준 라이브러리에 들어가 있으므로, 이제 vs2008 sp1 이상이라면 #include <type_traits>만 추가하면 별도 라이브러리 없이 바로 사용 가능하다. 컴파일러 자체 제공 traits도 있지만 표준 라이브러리를 쓰는 게 좀 더 마음이 편안하달까 +_+).. 관련된 설명은 msdn에도 있지만 C++ 표준 라이브러리 확장 책에도 잘 설명되어 있다.

템플릿은 역시 만만하지 않다. 지금보다 많이 초보였던 시절 제대로 다 이해 못하고 팽개쳐 두었던 책들을 지금 다시 읽으니 이젠 대충 이해가 가기는 한데, 실제로 코딩할 때 적극적으로 사용해 보려고 하면 많이 헷갈린다. 다음 번 토이 프로젝트에는 메타 프로그래밍을 좀 더 익힐 수 있도록 해야겠다.

예전에 진택이형이 사용한 int2type이라는 템플릿 구조체를 처음 보고 정말 신기해했던 기억이 난다. 공부하다 보니 Modern C++의 저자 알렉산드라쿠스가 소개한 기법이라고 한다. 이제는 동일한 기법이 std::true_type, std::false_type으로 깔끔하게 정리되어 표준 라이브러리에 들어가있다. 이걸 활용해 실행시간의 조건비교를 컴파일 타임 비교로 교체하는 예제 코드를 만들어봤다.

아래 코드는 Windows 2008부터 제공하는 슬림 리더라이터락 (SRW)을 scoped lock 형식으로 사용할 수 있게 해주는 헬퍼 클래스다. 락의 경우가 읽기와 쓰기 두 종류여서, lock을 걸 때 어떤 타입인지를 별도의 인자로 받게 작성되어 있다.

class Locker
{
public:
    Locker( SWRLOCK& lock, bool bExclusive )
        : m_lock( &lock )
        , m_bExclusive( bExclusive )
    {
        if( m_bExclusive )
        {
            while( !::TryAcquireSWRLockExclusive( m_lock ) )
                Sleep( 0 );
        }
        else
        {
            while( !::TryAcquireSRWLockShared( m_lock ) )
                Sleep( 0 );
        }
    }
 
    ~Locker()
    {
        if( m_bExclusive )
            ::ReleaseSRWLockExclusive( m_lock );
        else
            ::ReleaseSRWLockShared( m_lock );
 
        m_lock = NULL;
    }
 
protected:
    PSRWLOCK    m_lock;
    bool        m_bExclusive;
};
 
SRWLOCK g_rwLock;
 
Locker lock( g_rwLock, true );    // 쓰기 락을 걸 때
Locker lock( g_rwLock, false );   // 읽기 락을 걸 때

공유 리소스에 접근할 때 읽기 락이 필요한지 쓰기 락이 필요한지는 코드에서 이미 결정되어 있다. 하지만 위에 작성된 것처럼 코드를 짜면 실행시간에 매번 잠금의 종류를 확인하기 위해 조건문을 수행해야 한다.

type traits를 이용해 간단히 고쳐보면 아래와 같이 된다.

#include <type_traits>
 
template<bool bExclusive> class Locker
{
public:
    Locker( SWRLOCK& lock ) : m_lock( &lock )
    {
        Lock( std::integral_constant<bool,bExclusive>() );
    }
 
    ~Locker()
    {
        Unlock( std::integral_constant<bool,bExclusive>() );
    }
 
protected:
 
    void Lock( std::true_type )
    {
        while( !::TryAcquireSWRLockExclusive( m_lock ) )
            Sleep( 0 );
    }
 
    void Lock( std::false_type )
    {
        while( !::TryAcquireSRWLockShared( m_lock ) )
            Sleep( 0 );
    }
 
    void Unlock( std::true_type )
    {
        ::ReleaseSRWLockExclusive( m_lock );
        m_lock = NULL;
    }
 
    void Unlock( std::false_type )
    {
        ::ReleaseSRWLockShared( m_lock );
        m_lock = NULL;
    }
 
protected:
    PSRWLOCK    m_lock;
};
 
SRWLOCK g_rwLock;
 
Locker lock<true>( g_rwLock );    // 쓰기 락을 걸 때
Locker lock<false>( g_rwLock );   // 읽기 락을 걸 때

막상 다 해놓고 읽어보면 별로 대수로울 거 없는데 직접 작성할 때는 한참 헤맸음. 읽어서 이해되는 거랑 직접 써보는 거랑 차이가 많이 난다.

TMP 코드들이 예전에 봤을 때보단 쉽게 읽히는 걸 보니 그래도 조금씩 성장하고 있는 것 같아 기분이 좋다.  팽개쳐 두었던 템플릿 책들 다시 읽어봐야겠다.

See Also:

'프로그래밍 팁 > C++' 카테고리의 다른 글

x64 Calling Convention  (0) 2013.07.04
Faux variadics  (0) 2013.06.21
type traits  (0) 2012.02.14
Direct2D 레퍼런스 모음  (0) 2011.06.03
[vs2005] __assume 키워드  (0) 2011.05.25
[visual studio camp #1] C++0x와 Windows7  (0) 2011.02.13
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

요즘은 웹 기반의 솔루션들이 오픈 API를 많이 내놓고 있다. 오픈 API를 이용하면 갖가지 인터넷 정보검색은 물론 지도정보를 이용할 수도 있고, 블로그에 글도 올릴 수 있고, 구글 번역을 이용하면 문장 번역도 할 수가 있다. 구글 API는 특히나 방대하다. 아직 자세히 둘러 보지는 않았지만 YouTube도 API가 있고 Picasa, 구글 문서, 구글 캘린더도 다 API를 제공한다. 트위터와 페이스북, 미투데이 같은 SNS들도 오픈 API 형태의 기능제공을 하는 것으로 알고 있다. 앞으로 이런 오픈 API 형태로 접할 수 있는 기능들은 더욱 풍성해질 것이다.

오픈 API의 사용은 간단하다.

  1. http형태로 원하는 데이터를 요청하고,
  2. xml 형태로 요청한 데이터를 받는 것.

이게 전부다. 하지만 문제는 VC++에서는 http로 통신을 하기에도, xml 데이터를 파싱하기에도 자잘한 설정이 너무 많고 복잡하다는 점이다.

C++에서 http 통신을 하려면 WinHttp 계열의 API를 사용하는 것이 가장 기본적이다. 오픈 API 때문이 아니라 예전에 프로젝트에서 다른 작업을 할 때도 잠깐 썼던 적이 있는데, 아 뭐 어찌나 귀찮고 장황한지… 간단하게 사용할 인터페이스는 절대 못 된다.

Functions that create handles

http://msdn.microsoft.com/en-us/library/aa384270(v=VS.85).aspx

 

C++의 xml 파서는 여러 개가 있는데, 그 중에 가장 이름이 많이 알려진 게 MSXML, TinyXml 두 녀석일 듯 하다. 나는 MSXML은 써 본적 없고 TinyXml을 주로 썼는데, 적어도 게임업계 내 주변에서는 다들 TinyXml을 쓰는 거 같다. 하지만 암만 대세인 이 녀석도 아주 마음에 쏙 드는 인터페이스는 아니어서, xml 노드와 속성을 일일이 접근하는 귀찮은 과정이 많이 필요하다. ( http://sourceforge.net/projects/tinyxml/ )

그런데 요즘에 C#이랑 Linq를 공부하다가 OpenAPI 사용하는 코드를 한 번 찾아봤다. (http://chaoskcuf.com/132 )
대략 멍해졌다. C++로 대충 150~200줄 짤 거 같은 API 호출이 열 줄이면 끝난다.

   1: Console.Write("검색할 단어를 입력하세요 : ");
   2: string keyword = Console.ReadLine();
   3:  
   4: string url = "http://openapi.naver.com/search?key={0}&query={1}&target=...";
   5:  
   6: xdoc = XDocument.Load(string.Format(url, _apikey, keyword));
   7:  
   8: var result = from item in xdoc.Descendants("item")
   9:                 select new
  10:                 {
  11:                     Title = item.Element("title").Value,
  12:                     Link = item.Element("link").Value,
  13:                 };
  14:  
  15: int i = 1;
  16: foreach (var searchResult in result)
  17: {
  18:     Console.WriteLine("결과{0} : {1}", i++, searchResult.Title);
  19: }

Linq 문법과 C# 3.0의 Anonymouse Type (익명 타입) 의 엄청난 조화. 대.박.이.다.

Posted by leafbird 트랙백 0 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply Favicon of https://devnote.tistory.com BlogIcon leafbird 2011.05.26 11:57 신고

    정 C++에서 오픈 API를 사용하고자 한다면, 직접 http 요청하고 xml 받아와 파싱하는 클래스를 만드는거보다 C#에서 처리하고 이걸 dll로 C++에 붙이는게 훨씬 쉽고 빠를 것으로 예상 +_+

이번에 회사에서 ODBC를 이용한 DB 연결 인터페이스를 다시 작업했다.
요번에 DB처리를 다시 손대면서 좀 더 확신하게 된 사실이 있는데,
사실 대부분의 게임 개발 프로젝트들은 DB와 통신하는 처리에 그다지 신경을 많이 안쓴다는 점이다.
그냥 DB에 값 잘 쓰고, DB에서 값 잘 받아오면 그정도에서 처리 완료라는 뜻.

2000년 초반에는 업계 전반적으로 볼 때 stored procedure도 사용 안하고, SQL Injection에도 제대로 대응하지 못하던 개발팀들도 많았다. (GPG Study 포럼 게시판 등에 올라오는 글을 보고 미루어 보건대)

지금은 그정도는 아니더라도, 예를 들면 stored procedure의 output parameter 기능을 활용한다던가,  프로시저 자체 반환형(주로 int 형의)을 얻어낸다던가 하는 인터페이스들은 아마 거의 없을거라는 소리.
이런 애들은 모두 다 SELECT 쿼리로 대체할 수 있기 때문에, 대부분의 자체구현 코드들은 SELECT 결과셋을 얻어오는 처리만 구현하고 거기서 스톱이라는 말이다.
적어도 내가 여태껏 보아왔던 프로젝트의 DB 구현 클래스에는 이런 사항까지 디테일하게 처리한 코드가 전무했다.

다들 프로시저 모양이 대부분 요런 식일거다.
CREATE PROCEDURE [dbo].[up_xxxxxxx]
    @param1 int,
    @param2 int,
    ...
AS

 -- 프로시저 실제 작업 처리

  IF @@ERROR <> 0
  BEGIN
    SELECT -1 -- 에러값을 반환형으로 처리하지 못하고 SELECT를 이용해 새로운 결과셋으로 생성.
    RETURN -- 여기에서 return -1 하면 되지만 대부분 귀찮아서 안쓰는 듯.
  END -- 때문에 BEGIN ~ END 블럭을 생략할 수도 없다. 리턴문 하나만 있다면 생략해도 되는데 말이지.

SELECT 0 -- 성공적인 경우의 리턴.

뭐 아무튼... 이번에도 기존 코드의 유지보수를 맡으면서 개선을 좀 했는데
이번에는 반환형 사용에 대한 이야기는 아니고...
예전에 한 번도 사용해보지 않았던 api들을 처음으로 몇 개 이용하게 되어서 정리해 두고자 한다.

메모리 DB를 사용하는 경우 처럼 서버 아키텍쳐 안에서 DB 호출을 일괄적으로 처리하는 공통의 루틴을 만들고, 프로시저 이름과 인자 리스트를 받아서 처리하는 방식이라면 아래 api들을 사용해야 한다. 

SQLNumResultCols ( http://msdn.microsoft.com/en-us/library/ms715393(v=VS.85).aspx )
 - 쿼리를 수행 후 Fetch 해야 할 데이터가 있는지 확인하기 위해, 컬럼의 수를 얻어낸다.

Update, Insert, Delete 쿼리의 경우는 위의 값이 0으로, 반환되는 데이터가 없음을 알 수 있다. general하게 처리하는 경우 쿼리 실행 후 무조건 FETCH를 걸면 커서 상태오류(Invalid Cursor State)에 빠질 수 있다. 이럴 때 SQLNumResultCols 함수를 이용해서 데이터 존재여부를 알아낸다.

컬럼의 개수를 알아낸 다음에는, 각 컬럼의 타입이 무엇인지, 컬럼에 붙여진 이름은 무엇인지 조회할 필요가 있다. 이럴 때는 아래 함수를 이용.

SQLDescribeCol ( http://msdn.microsoft.com/en-us/library/ms716289(VS.85).aspx )
 - 결과셋(resutl set)의 각 컬럼에 대한 세부정보를 조회한다.

이러면 DB의 데이터를 얻어내기 위해 바인딩 해야 할 컬럼의 정보를 조회할 수 있다.

그런데, SP처럼 batch 구문으로 엮여서 여러개의 쿼리가 한 번에 처리되는 경우, 일괄처리 내에서 update, insert, delete, select가 다양하게 섞여서 호출될 수 있다. 이럴 때 SQLNumResultCols 함수를 호출하면 sp 내에서 가장 처음에 실행된 쿼리에 대한 컬럼의 값을 가져올 뿐이다.

그럴 땐 아래의 함수를 이용한다.

SQLMoreResults ( http://msdn.microsoft.com/en-us/library/ms714673(VS.85).aspx )
 - 결과셋(result set)이 여러 개 존재하는 경우, 다음 결과셋으로 cursor를 옮긴다.

이 함수를 이용해 SQL_NO_DATA가 나올 때 까지 루프를 돌아줘야 update / insert / delete 뒤에 호출되는 SELECT의 결과셋에 접근할 수가 있고, 또 유효한 결과셋을 여러 개 반환하는 batch 실행 구문에 대해서도 문제없이 모든 데이터를 얻어낼 수가 있다.

ODBC API에 대한 지식이라고 해봐야, 예전 김상형씨의 API 책에서 소개된 정도가 거의 전부이기 때문에... 이번에 새로 알게 된 함수들 이름이랑 간략한 소개, 링크 정도만이라도 정리해 둔다. 회사 소스코드를 백업할 수는 없으니 이렇게 작업사항 기록이라도 해두어야지 :)

그리고, 예전에 검색할 땐 ODBC api들 설명자료가 많이 부실하다고 느꼈는데, 그사이에 마소가 MSDN 리팩토링(?)을 좀 한듯?
요번에 작업할 때는 자료들이 많아서 예전보다 수월하게 작업할 수 있었다. VC++이 이러니 저러니 말은 많아도... MSDN 하나는 과연 짱인듯 +_+)b
TAG C++, ODBC
Posted by leafbird 트랙백 0 : 댓글 2

댓글을 달아 주세요

  1. addr | edit/del | reply Favicon of http://twitter.com/jun0683 BlogIcon 홍구 2012.02.07 18:05

    오픈소스로 라이브러리 만들어주세요ㅋㅋㅋㅋㅋㅋ

    • addr | edit/del Favicon of https://devnote.tistory.com BlogIcon leafbird 2012.02.08 20:25 신고

      앗 홍구님이다 +_+
      오픈해봐야 다운받을 사람 뭐 얼마나 있으려고요 ㅎㅎ

여태껏 수년 동안 개발하면서도 정규표현식을 실제 프로젝트에 써본 적이 몇 번 없는데

이번에 개인적으로 구현중인 코드에서 오랜만에 다시 사용하게 됐다.

그런데 감격인 것은 이번엔 DB 접속툴에서 쓰던 자바 스크립트도 아니고,

팀원들의 원성을 사며 설치해 사용하던 boost::regex도 아니고

무려 tr1 라이브러리에 들어있는 표준 C++ 라이브러리 regex를 사용했다는 말씀! ㅜㅠ

너무 감동이다 ~

이제 ‘그런 머리아픈걸 왜 써? 부스트 꼭 깔아야 돼? 그냥 내가 짜면 안돼?’ 같은 소리에 답변할 필요가 없다.

표준 라이브러리다.

표준 라이브러리 ~ ♡

tr2도 얼른 나왔음 좋겠다 ㅎ

Posted by leafbird 트랙백 0 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply Favicon of https://devnote.tistory.com BlogIcon leafbird 2010.08.15 15:46 신고

    사용할 땐 #include <regex>. 인클루드부터 감동이다 ㅜㅠ...