<?xml version="1.0" encoding="euc-kr" ?>
<rss version='0.92'>
<channel>
<docs></docs>
<title>리카넷</title>
<link>http://ricanet.com</link>
<description></description><item>
<title>구라치다 걸리면</title>
<pubDate>Sat, 03 Apr 2010 21:15:06 +0900</pubDate>
<description><![CDATA[
▷<A href='http://ricanet.com/new/view.php?id=blog/100403' target='_blank'>직접 가서 보기</A>◁<BR>
<BR>
모 사건에서 거대한 구라의 냄새가 난다. 어떤 판에서든 구라를 치려는 놈이 있고 심지어 때때로 성공하는 건 어쩔 수 없다손 쳐도, 구라치다 걸렸다면 손모가지가 날아가야 한다. 그래야 멀쩡한 판이다. 요즘 판이 돌아가는 꼴을 보면 그렇지 않은 것 같아 화가 난다. 결국 나도 언제든지 호구일 수 있는 것이다.<BR>
<!--end-->]]></description>
<link>http://ricanet.com/new/view.php?id=blog/100403</link>
</item>
<item>
<title>책읽記 - 타워</title>
<pubDate>Wed, 24 Mar 2010 12:44:34 +0900</pubDate>
<description><![CDATA[
▷<A href='http://ricanet.com/new/view.php?id=blog/100324' target='_blank'>직접 가서 보기</A>◁<BR>
<BR>
<div class="ttbReview"><table><tbody><tr><td><a href="http://www.aladdin.co.kr/shop/wproduct.aspx?ISBN=8901096439&amp;ttbkey=ttbrickrica0020001&amp;COPYPaper=1"><img src="http://image.aladdin.co.kr/coveretc/book/coversum/8901096439_2.jpg" alt="" border="0"/></a></td><td align="left"  style="vertical-align:top;"><a href="http://www.aladdin.co.kr/shop/wproduct.aspx?ISBN=8901096439&amp;ttbkey=ttbrickrica0020001&amp;COPYPaper=1" class="aladdin_title">타워</a> - <img src="http://image.aladdin.co.kr/img/common/star_s10.gif" border="0" alt="10점" /><br/>배명훈 지음/오멜라스(웅진)</td></tr></tbody></table></div>
<BR>
총 674층, 가로세로 5km 크기의 빌딩이자 독립국가인 빈스토크. 이 있을 법하지 않은 거대한 건물에서 벌어지는 이야기 여섯 편에 담긴 미래 가상의 공간에서 살아가는 사람들의 모습은 독특하고 흥미로우면서도 놀라울 만큼 지금 우리의 모습과 닮아 있다.<BR>
<BR>
<타워>는 초기 조건을 비틀어 놓은 세계에서 어떤 일이 벌어지는지를 상상하고 관찰하는 과정에서 사물과 인간 본질의 편린을 엿보는 SF의 즐거움을 아주 훌륭하게 담고 있다. 그러면서도 책의 재미를 누리는 데 과학 지식이 거의 필요하지 않아 누구나 재미있게 즐길 수 있다. 망설임 없이 별 다섯 개, 강력히 추천한다.<BR>
<!--end-->]]></description>
<link>http://ricanet.com/new/view.php?id=blog/100324</link>
</item>
<item>
<title>데드락 피하기</title>
<pubDate>Thu, 18 Mar 2010 23:43:10 +0900</pubDate>
<description><![CDATA[
▷<A href='http://ricanet.com/new/view.php?id=blog/100318' target='_blank'>직접 가서 보기</A>◁<BR>
<BR>
데드락은 다른 멀티스레드 문제와 마찬가지로 부하가 심하게 걸릴수록 나타나기 쉽다. 온라인 게임이라면 동접이 제일 높은 시간에 가장 섭다되기 쉽다는 얘기다. 안정적으로 서비스하려면 라이브 서비스를 시작하기 전에 데드락 문제를 해결해야 하는데, 평상시에 수행하는 테스트만으로는 데드락을 찾아내기 어려우니 다른 방법을 강구해야 한다. 데드락이 아니라도 터질 문제가 많을 테니 대비할 수 있는 건 미리 해 두자. 그런다고 라이브 시작해서 밤 안 새는 건 아니지만…<BR>
<BR>
학부 OS 과목에서 데드락에 대해 배웠을 것이다. 데드락이 발생하기 위한 4가지 조건, 데드락 감지하기, 데드락 피하기 등 꽤 많은 내용이 있다. 그런데 C++로 멀티스레드 게임 서버를 짤 때는 그런 지식들은 별 쓸모가 없고, 딱 한가지 조건만 철저하게 지키면 된다:<BR>
<BR>
<DIV style='; border:0px; padding-left:12px'> 락을 잡는 순서가 일관성 있어야 한다. 예를 들어, 어디서는 a b 순서로 잡고 딴데서는 b a 순서로 잡으면 안 된다.<BR>
</DIV><BR>
간단하다. 데드락 문제를 다 해결했다! 야 신난다!<BR>
<BR>
아니다. 사실, 간단하지 않다. 프로그램이 좀 커지면 이 조건을 깨뜨리기가 무척 쉬워지고, 깨뜨린 사실을 발견하기는 무척 어려워진다. 예를 들어 보겠다. 어떤 함수 Ouch()가 있는데 그 함수는 안에서 락 a를 잡는다고 치자. 그런데 프로그램 전체에서 락을 a b c 순서로 잡기로 했다. 그러면, 락 b 혹은 c를 잡은 상태에서 Ouch()를 호출하면 데드락이 일어날 수 있다. 만약 Ouch()가 직접 락을 잡는 것이 아니라 콜스택을 5단계쯤 타고 들어가서 호출된 전혀 다른 곳의 함수가 락 a를 잡더라도 마찬가지다.<BR>
<BR>
어떤 함수를 호출하는 코드를 쓸 때, 현재 맥락에서 어떤 락을 잡은 상태인지, 호출할 함수와 그것이 간접적으로 호출할 모든 함수들이 어떤 락을 잡을지를 일일이 확인해야 한다. 게다가 코드에 변경이 일어날 때마다 영향받는 호출 경로를 모두 확인해야 한다. 사람이 직접 하기에는 너무 힘든 일이다. 간단한 줄 알았던 문제가 프로그램의 규모 때문에 어려운 문제가 된다.<BR>
<BR>
우리네 소프트웨어 세계는 모듈의 인터페이스를 알고 그대로 따르면, 세부 구현에 신경쓰지 않아도 행복하게 살 수 있다는 가정에 기반해서 모듈화의 성을 쌓아올렸다. 그러나 락 기반 멀티스레드 환경에서는 함수를 호출할 때 그 호출경로에서 잡힐 락에 대해 신경써야 한다. 곧 함수가 어떤 락을 잡을까 하는 것이 (어떤 예외를 던질까처럼) 함수의 인터페이스의 일부가 된다. 하지만 지금 널리 쓰이고 있는 언어들은 함수의 락잡기를 명시적으로 표기하도록 하지 않는다. 헤더에 기록된 인터페이스만 보고는 프로그래밍할 수 없는 것이다.<BR>
<BR>
그러나 맥주가 미지근하면 얼음을 넣어 마시면 되듯이, 컴파일러가 검사해 주지 않으면 실행시간에 확인하면 된다. 개별 스레드가 잡고 있는 락을 모두 기록하고, 새로이 락을 잡으려 할 때 기존에 잡은 락 목록을 확인해서 순서 규칙을 깨뜨리지 않는지 assert() 하는 것이다. assert()에 걸리면 디버거로 들어가든지 즉시 덤프를 남기도록 해서, 어떤 호출 경로에서 데드락이 생기는지 분석하면 된다.<BR>
<pre name='code' class='python'>
# TLS 타입의 객체는 포인터 한 개 크기의 스레드별 저장 공간을 제공한다.
# TLS 객체를 담고 있는 lockhistory 전역변수가 선언되어 있다.
# 락 객체에는 enter(), leave() 멤버 함수와 type 필드가 있다. type 필드는 락의 순서를 나타내며, type이 작은 것부터 큰 것 순서로 잡도록 규칙을 정했다.
# 전처리기를 적절히 써서 서비스용 빌드에서는 데드락 검사 코드를 빼 버릴 수 있도록 하자.


# 스레드를 시작할 때:
lockhistory := create new set

# 락 lock에 진입하려 할 때:
for( oldlocktype in lockhistory ):
   assert( oldlocktype &lt;= lock.type )
lockhistory.insert( lock.type )
lock.enter()

# 락 lock에서 탈출하려 할 때:
lock.leave()
assert( lockhistory.has( lock.type ) )
lockhistory.erase( lock.type )
</pre>위와 같이 락을 잘못된 순서로 잡는지 각 스레드별로 감시하면 실제로 데드락이 일어나기를 기다리는 것보다 훨씬 결정론적으로 데드락의 여지를 알아낼 수 있다.<BR>
<BR>
이 방법은 프로그램의 호출 경로가 대체로 변하지 않는다는 가정을 전제로 하고 있다. 만약 테스트에서 한번도 수행되지 않았던 경로로 프로그램이 작동할 수 있다면, 테스트 환경에서 한번도 락 순서를 위반하지 않았더라도 서비스에서 데드락이 발생할 수 있으니 백 퍼센트 안심할 수는 없다. 그러나 그런 경우는 단순 실수보다는 설계 문제에 가까우므로 미리 파악할 수 있을 것이다.<BR>
<BR>
<DIV style='width:100%; font-family:돋움;font-size:17pt;border-bottom:1px solid #cccccc;margin-bottom:8px'><B>다른 이야기</B></DIV><DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>그럼에도 불구하고 데드락에 걸린다면, CPU 점유율이 갑자기 낮아진다(스핀락을 썼다면 갑자기 높아진다). 차라리 크래시하면 서버를 바로 다시 띄우고 덤프를 받아와서 분석하면 되는데, 데드락은 서버가 잘 돌다가 잠시 멈칫하는 건지 데드락에 걸린 건지 명확하게 구분하기 어렵다. 스레드의 작동 상태를 감시하는 코드를 짜 두든지, 장애 전화를 받고 달려와서 수동으로 서버를 죽였다 띄워야 한다. 콜스택 없이는 데드락 분석이 불가능하니 프로세스를 죽이기 전에 덤프를 확보해야 하는데, 수동으로 서버를 죽이기 전에 프로세스 외부에서 덤프를 떠야 한다. user mode process dump를 검색해 보라.</li></DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>함수가 어떤 락을 잡을지가 함수의 인터페이스의 일부인 것처럼, 함수에 진입할 때 어떤 락이 잡혀 있는 상태여야 하는지도 함수의 인터페이스의 일부다. 우리는 이것도 명시적으로 표현하고 실행시간에 검사하고 싶어서 need_lock 매크로를 만들어 썼다.</li></DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>프로그램 정적분석기를 써서 데드락을 찾아내는 것도 이론적으로 가능한 일이다. 한번 찾아보라.</li></DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>인텔 패러렐 인스펙터라는 제품이 데드락을 찾아준다고 한다.</li></DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>락 순서를 전역적으로 정의하지 않고, 프로그램이 동작하는 동안 락들의 부분 순서를 수집해서 모순이 생기는지를 검사하는 방법도 가능하다. 순서를 미리 저장해 둔 파일이 없으니 재컴파일 문제를 피할 수 있다. 그러나 락의 순서는 반드시 문서화해 두어야 한다. 다른 프로그래머가 락 순서 위반 assert()에 걸렸을 때 무엇을 어떻게 고쳐야 하는지 헤메지 않게 하기 위해서다.</li></DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>위에서는 간결하게 설명하기 위해 제외했지만, 한 프로세스 내에 여러 개의 인스턴스가 존재하는 클래스를 위해 같은 종류의 락들에 대한 정책도 필요하다. 어떻게든 일관성 있는 순서를 정하고 지키면 되는데, 간단하게 락 객체의 주소를 기준으로 하는 정도면 충분하다. lockhistory에 그 값도 같이 저장해 두고 검사하도록 하자.</li></DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>DB의 데드락도 간단한 문제는 아니다. 다만 내가 잘 모르는 분야라 자세한 설명은 생략한다.</li></DIV><!--end-->]]></description>
<link>http://ricanet.com/new/view.php?id=blog/100318</link>
</item>
<item>
<title>결정을 기록할 때는</title>
<pubDate>Tue, 16 Mar 2010 10:17:31 +0900</pubDate>
<description><![CDATA[
▷<A href='http://ricanet.com/new/view.php?id=blog/100316' target='_blank'>직접 가서 보기</A>◁<BR>
<BR>
왜, 를 반드시 쓰라.<BR>
<BR>
지금 내린 결정의 이유가 나중에 분명히 궁금해질 거다. 어떤 전제에 의해 결정을 내렸는지 기록해두지 않으면 당신이 천재가 아닌 이상 분명히 잊어버릴 거다. 아니, 당신이 천재라도 잊어버린다.<BR>
<BR>
결정의 이유를 기록해 두지 않으면 몇 개월이나 몇 년이 지나서 과거의 결정이 불합리해 보일 때, 그 결정이:<BR>
<BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>지금은 유효하지 않은 어떤 전제 하에 내려진 것인지 (번복하면 됨)</li></DIV><DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>지금은 기억나지 않지만 여전히 문제가 되는 미묘한 사항 때문인지 (아하 그랬구나 하고 넘어가면 됨)</li></DIV><DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>충분한 정보를 모으지 못해서 일단 임시방편으로 내린 결정인지 (이제 결정하거나 다시 미루면 됨)</li></DIV><DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>당시 피할 수 없는 조건 때문에 내려졌던 것인지 (레거시입니다 존중해주시죠?)</li></DIV><DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>지금 생각난 빠꼼한 해결책이 당시에는 떠오르지 않았던 것 뿐인지 (우쭐하면 됨)</li></DIV><BR>
떠올릴 수 없게 된다.<BR>
<BR>
암흑으로 가득찬 광대한 해공간을 탐색하는 당신들, 안 그래도 갈 길이 먼데 자신들이 서있는 위치가 어디인지, 왜 여기 서있는지는 알고 있어야 되지 않겠나.<BR>
<!--end-->]]></description>
<link>http://ricanet.com/new/view.php?id=blog/100316</link>
</item>
<item>
<title>한국어 회화에서</title>
<pubDate>Mon, 15 Mar 2010 20:36:24 +0900</pubDate>
<description><![CDATA[
▷<A href='http://ricanet.com/new/view.php?id=blog/100315' target='_blank'>직접 가서 보기</A>◁<BR>
<BR>
'왜'는 이유나 원인을 묻는 말이다. 그런데 회화에서는 본래 뜻에 더하여 '싫다', '그러지 마라' 는 의미로 자주 쓰인다.<BR>
예)<BR>
<DIV class='Quote'>너 왜 그랬니?<BR>
</DIV>
<BR>
일을 하다 보면, 다른 사람이 내린 결정이 어떤 생각의 흐름에 의한 것인지 알고 싶을 때가 있다. 그럴 때 주의를 기울이지 않고 '왜'를 넣어 이야기하면 듣는 사람은 '그러지 마라' 로 받아들이기 쉽다.<BR>
예)<BR>
<DIV class='Quote'>갑돌이: 이 클래스를 왜 템플릿으로 만들었어요?<BR>
을돌이: 예 고치겠습니다.<BR>
갑돌이: !!??? 이유를 알려달라고 녿ㅌ허내루러니ㅔ냩율<BR>
</DIV>
<BR>
반대로 아랫사람이 '왜'를 묻는 경우 반항하는 태도로 받아들여지기도 한다.<BR>
예)<BR>
<DIV class='Quote'>갑돌이: 그 일은 이렇게 진행하도록 합시다.<BR>
을돌이: 왜요?<BR>
갑돌이: 뭐 이 새끼야<BR>
</DIV>
<BR>
내가 말하고자 하는 의도가 불만을 표하거나 힐난하는 것이 아니라 정말로 이유를 묻는 경우에는 '왜'를 사용하지 않도록 문장의 꼴을 바꾸거나, 말투를 특별히 주의해서 온화하게 할 필요가 있다.<BR>
<BR>
특히 업무상 상하 관계에 있는 경우나, 비 프로그래머 직군과 대화할 때 그렇다.<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
거꾸로, '그 사람은 왜 그렇게 사는걸까요?' 같은 말에 그 사람이 왜 그렇게 사는지 진지하게 생각할 필요는 없다.<BR>
<!--end-->]]></description>
<link>http://ricanet.com/new/view.php?id=blog/100315</link>
</item>
<item>
<title>인하우스 툴 만들기 2</title>
<pubDate>Thu, 04 Mar 2010 00:08:22 +0900</pubDate>
<description><![CDATA[
▷<A href='http://ricanet.com/new/view.php?id=blog/100304' target='_blank'>직접 가서 보기</A>◁<BR>
<BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>구조적이고 자기서술적인 포맷 2</li></DIV><DIV style='; border:0px; padding-left:12px'> 자기서술적이라는 것은 데이터의 의미를 데이터 자체에 포함하고 있다는 뜻이다. 이를테면<BR>
</DIV><DIV style='; border:0px; padding-left:12px'> A:<BR>
</DIV><DIV style='; border:0px; padding-left:24px'>  </DIV><DIV class='Quote'> Kim, 228.56,<BR>
 Asada, 205.50,<BR>
 Rochette, 202.64,<BR>
</DIV>
<DIV style='; border:0px; padding-left:12px'> B:<BR>
</DIV><DIV style='; border:0px; padding-left:24px'>  </DIV><DIV class='Quote'>[<BR>
&nbsp;&nbsp;&nbsp;&nbsp;{<BR>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;이름 = "Kim"<BR>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;점수 = 228.56<BR>
&nbsp;&nbsp;&nbsp;&nbsp;},<BR>
&nbsp;&nbsp;&nbsp;&nbsp;{<BR>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;이름 = "Asada"<BR>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;점수 = 205.50<BR>
&nbsp;&nbsp;&nbsp;&nbsp;},<BR>
&nbsp;&nbsp;&nbsp;&nbsp;{<BR>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;이름 = "Rochette"<BR>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;점수 = 202.64<BR>
&nbsp;&nbsp;&nbsp;&nbsp;}<BR>
]<BR>
</DIV>
<DIV style='; border:0px; padding-left:12px'> A에 비해서 B가 사람이 읽기 쉽고, 실수했을 경우 잘못을 찾아내기 쉽다. 중간에 데이터를 끼워넣기도 쉽다: 읽고자 하는 항목이 없을 경우 디폴트값을 사용하면 된다. 중첩된 구조도 표현하기 쉽다. 좀 까다롭긴 하지만 상호 참조하는 구조도 만들 수 있다.<BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> <A href='http://ricanet.com/new/view.php?id=blog/100223'>이전 글(인하우스 툴 만들기)</A>에서 XML을 추천했었다. 추천 이유는 이미 관련 도구들이 많이 나와 있다는 것이다. 어떤 언어에서든 해석기 라이브러리가 만들어져 있다. 에디터도 많이 있다. 바이너리 표현도 있다고 한다(써보진 않았다). 문법 오류를 확인해주는 도구도 있다. 그러나 단점도 있다: 텍스트 에디터로 쓸/읽을 경우 군더더기가 많다.<BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> 우리는 자체 포맷을 사용했기 때문에 관련 라이브러리와 도구를 많이 만들어야 했다. 그러나 최종 결과물에 불만은 전혀 없다.<BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> JSON의 경우 라이브러리를 구하기 쉽고 가독성이 좋다. XML에 비해 용량도 적게 차지한다. 그러나 필요한 도구는 직접 만들어야 할 거다.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>언두, 리두</li></DIV><DIV style='; border:0px; padding-left:12px'> 맞다. 구현하기도 어렵고 시간도 오래 걸린다. 하지만 비싼 값을 한다. 게임개발의 꽃이 스킬이라면 툴개발의 꽃은 언두라고 생각한다(컴공 학부의 꽃은 컴파일러ㅋㅋ다). 하지만 모든 툴에 필요한 건 아닌 듯하다. 대충 나누어 보자면 감각적인 편집이 필요한 곳에는 언두가 꼭 필요하고(포토샵, 3dmax, 텍스트 편집기), 작은 단위로 편집하고 반영하는 곳에는 그다지 필요하지 않다고 본다(윈도우 제어판, 인쇄 설정).<BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> 언두/리두는 모든 다른 기능을 가로지르는 기능이기 때문에, 설계를 잘 해야 한다. 반면에, 구현하기 영 귀찮고 자주 안 쓰인다 싶은 기능에 대해서는 "이 동작은 되돌릴 수 없습니다." 정도의 메세지를 띄우는 정도로 타협해도 된다.<BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> 여담으로, 클로저를 편하게 쓸 수 있는 언어로 툴을 만들면 적어도 언두/리두는 편하게 구현할 수 있을 것 같다. 요즘 루아로 프로그래밍하다가 C++로 돌아오면 클로저를 너무너무 쓰고 싶-_-다.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>광야에 나가면</li></DIV><DIV style='; border:0px; padding-left:12px'> 서비스를 몇 달 안 하고 게임이 접혔지만, 당시 디렉터가 다년간의 라이브 서비스 경험을 바탕으로 미리 조언해준 덕택에 이것저것 준비할 수 있었다. 시간의 시험을 통과하거나 깊이있는 경험을 쌓은 것은 아니나 일단 써본다. <BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> 실제 유저에게 서비스를 할 때 계속 반복되는 단순 작업들이 있다. 서비스하는 기계에 파일 보내기, 서버 내리기, 서버 띄우기, 죽은 서버 다시 살리기, 윈도업데이트, DB 동기화, 시간 맞추기, ipsec 적용… 서버 대수가 많아짐에 따라 귀찮음이 선형으로 증가하는 일들이다. 우리는 서버 4대까지 전부 손으로 세팅하다가 서버가 12대가 되면서 GG 치고 자동화했다. 서버들에 sshd를 설치하고 원격에서 커맨드라인 명령을 내릴 수 있게 되면 배치파일을 가지고 이것저것 할 수 있게 된다. 특히 외부 툴을 쉽게 붙일 수 있는 점이 좋다.<BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> DB 동기화는 아주 실수하기 쉬운 부분이다. 개발중에 팀내 개발DB에서 SP나 테이블을 바꾸고 그에 맞춰 코드를 수정했는데 서비스 DB에 동일한 변경을 가하지 않았다면, 서버가 잘 돌다가 문제있는 부분을 수행하는 순간 오작동하게 된다. MSSQLServer를 쓸 경우 DB에 변경을 가할 때마다 SSMS가 자동으로 change script를 뽑아주긴 하지만, 저장하거나 적용하는 걸 빼먹으면 서비스에서 실제로 사고가 터지기 전에는 실수를 했는지조차 알 수 없다는 게 굉장히 무섭다. ApexSQL Diff라는 툴이 DB간의 diff를 떠 주던데, 무료도 아닌데다 커맨드라인으로 붙이기가 곤란해서, 그 툴이 제공하는 기능의 일부를 T-SQL 코드로 직접 짜서 패치과정에 넣었다. 서버프로그래머가 꽤 고생했으니 그리 간단한 일은 아니었던 듯하다.<BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> ipsec은 원래 보안이 적용된 IP 프로토콜이라고 학부 네트워크 과정에서 배우지만, 윈도우 서버에서 일종의 방화벽처럼 쓸 수 있다. 시작-실행-secpol.msc를 해보도록 하자. 발신/수신 IP주소/포트의 조합에 대해 차단 여부를 지정할 수 있어서, 관리 포트를 개발망에만 접속 허용한다거나 할 수 있다. 다만 설정하기가 정말……정말 귀찮다. 전부 막고 필요한 포트만 딱 열어주려면 항목이 상당히 많아지게 되는데, 설정 인터페이스가 너무 귀찮아서 뭔가 바뀔 때마다 서버 대수만큼 적용하고 있노라면 내가 게임 서비스를 하는 것인지 도 닦고 있는 것인지 헷갈린다. 잘 찾아보면 커맨드라인에서 ipsec 설정하는 방법이 있다. 이것도 당시 담당했던 프로그래머가 꽤 시행착오를 거쳐서 알아냈다. ipsec 설정을 배치파일로 만들어두고 모든 서버에 일괄 반영하면 패치 담당자의 삶이 편해진다.<BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> 윈도업데이트도 커맨드라인에서 하는 스크립트를 찾아내긴 했는데, 업데이트 후 재부팅이 필요한 경우 어떻게 동작하는지 확인하지 못했고, 선택적 업데이트를 안 물어보고 바로 설치해버려서, 잠깐 쓰다 말았다. 윈도 업데이트는 자주 있는 일이 아니므로 패치 당번 프로그래머가 몇 대씩 맡아서 손으로 업데이트해도 별로 귀찮지 않았다.<BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> 서버들의 시간을 미리 맞춰두지 않으면 사고 터지고 나서 로그 분석할 때 정말 신경질나게 된다. 시간을 계속 동기화하는 데몬을 돌려두든지, 점검할 때 자동으로 시간을 맞춰주도록 하자.<BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> 동접 모니터링과 기록은 기본이다. 서버가 잘 동작하고 있는지 아닌지도 언제나 확인할 수 있어야 한다.<BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> 윈도우 서버들을 도메인으로 묶으면 좋다......는 얘기를 들은 적이 있다. 위에 적은 많은 것들이 도메인을 적용하면 편해질지도 모른다. 해보신 분들의 경험담을 기대한다.<BR>
</DIV><BR>
<DIV style='; border:0px; padding-left:12px'> 뭔가 많이 썼지만 서버 관리자를 한 명 뽑으면 해결되는 것들인 듯하기도 하다.<BR>
</DIV><!--end-->]]></description>
<link>http://ricanet.com/new/view.php?id=blog/100304</link>
</item>
<item>
<title>인하우스 툴 만들기</title>
<pubDate>Tue, 23 Feb 2010 23:32:25 +0900</pubDate>
<description><![CDATA[
▷<A href='http://ricanet.com/new/view.php?id=blog/100223' target='_blank'>직접 가서 보기</A>◁<BR>
<BR>
게임을 개발하는 과정에서 인하우스 툴을 많이 만들게 된다. 개발팀의 문화나 개발하는 게임의 특성에 따라 차이는 있겠지만, MMORPG처럼 컨텐츠 분량이 매우 많은 게임은 손으로 모든 컨텐츠를 만들어 넣기 어려우므로 툴에 의존하게 된다. 그런데, 한 번 게임을 만들고 비슷비슷한 후속작을 계속 내거나 만들고자 하는 장르에 딱 맞는 엔진을 구매하지 않는 한, 개발팀에서 적절한 툴을 만들어 사용해야 한다.<BR>
<BR>
툴의 품질은 컨텐츠의 품질과 툴 사용자의 작업 효율에 큰 영향을 미친다. 직접 유저에게 서비스되는 코드가 아니라고 가볍게 생각하면 안 된다. 디버거 없이 메모장으로만 코딩한다고 상상해 보자… 회사 다니기 싫어지지 않는가?<BR>
<BR>
좋은 툴을 만들기 위해 고려해봄직한 주제등을 정리해 보았다. 쓰다 보니 데이터를 만들어내는 데 전용 툴을 쓰는 경우와 텍스트를 직접 편집하는 경우를 구분하지 않게 되었다. 적절히 가려 읽어주기 바란다.<BR>
<BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>실수에 대해 적절한 피드백을 주어라.</li></DIV><DIV style='; border:0px; padding-left:12px'> 툴의 사용자는 사람이다. 사람은 실수한다. 어디서 실수했는지, 왜 잘못된 건지 알려주자. 데이터를 읽어들이는 코드를 짜다 보면 데이터를 해석하고 적재하는 코드보다 에러 검사하는 코드가 더 길어지기도 하지만, 당연한 것으로 받아들이자. 어차피 버그를 잡는 건 프로그래머의 몫이니, 데이터의 에러 검사에 미리 시간을 투자하자.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>인터페이스에 신경써라.</li></DIV><DIV style='; border:0px; padding-left:12px'> 잘 알려진 툴과 인터페이스가 비슷하면 좋다. 3dmax나 포토샵이 제공하는 모든 기능을 만들 순 없겠지만 이왕 만드는 기능이면 단축키를 똑같이 하자. 배우기 쉬우면 좋지만, 그보다 더 중요한 건 익숙해졌을 때의 작업 효율이다. 실수를 하기 어렵거나, 했더라도 쉽게 알아볼 수 있도록 해라.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>가급적 안 죽게 만들어라 (게임이 죽는 것보다야 덜 심각한 문제지만, 그래도).</li></DIV><DIV style='; border:0px; padding-left:12px'> 크래시 덤프 수집 기능을 넣어서 툴의 버그를 잡아라. 아마 대부분의 게임개발자들이 열악한 현실에 익숙해진 나머지 툴이 죽더라도 일단 담배를 한 대 태운 후 한숨을 쉬며 툴을 다시 띄울 것이다. 툴의 버그를 적극적으로 리포트하도록 독려해라. 그리고, 리포트한 사람에게 친절해라.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>데이터 날려먹지 마라.</li></DIV><DIV style='; border:0px; padding-left:12px'> 툴이 안 죽어야 되는 것은 기본이다. 죽더라도 오토세이브가 있으면 좋지 않을까 싶다(적용해보진 않았다). 여러 사람이 동시에 편집할 가능성을 고려해서 여러 파일로 분산해라. 모든 몬스터의 스킬 이펙트를 한 파일로 몰아넣는다거나 하면 두 사람이 동시에 몬스터의 스킬 이펙트를 편집할 수 없게 된다.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>테스트 주기를 짧게 할 수 있게 해라.</li></DIV><DIV style='; border:0px; padding-left:12px'> 미리보기 가능한 것은 미리보기할 수 있게 만들어라. 하지만 미리보기가 실제 게임에 들어갔을 때와 다르게 보이면 절대로 안된다. 가급적 서버를 작업자의 개인 컴퓨터에서 띄워볼 수 있거나, 데이터를 커밋했을 때 팀 공용 테스트 서버가 그 데이터를 다시 읽을 수 있게 만들자(솔직히, 하기 힘든 일이긴 하다). 일찍 에러내라. 데이터가 실제로 사용될 시점에 에러내기보다는 서버나 클라이언트가 뜰 때 모든 데이터를 검사하고 에러를 알려줘라. 뜰 때 검사하기 어렵다면, 그런 에러에 대해서는 게임을 죽이지 말고 가벼운 경고를 띄우고 넘어가라.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>테스트를 위한 치트를 제공해라.</li></DIV><DIV style='; border:0px; padding-left:12px'> 캐릭터 레벨 세팅은 기본이고, 캐릭터를 무적 상태로 만들거나 퀘스트 상태를 변경하는 등 다양한 치트가 필요하다.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>엑셀을 잘 활용해라.</li></DIV><DIV style='; border:0px; padding-left:12px'> 표 형태로 나타내기 적절한 데이터는 엑셀에 넣게 하자. 경험상 엑셀 파일에 적절한 패턴으로 데이터를 뽑아낼 범위를 명시하고, 게임이 데이터를 읽을 시점에 엑셀 파일을 직접 읽어서 텍스트를 뽑아내는 게 가장 좋았다. 우리는 C#으로 COM Interop을 이용해서, xls 파일을 읽고 txt 파일을 토해내는 일종의 컴파일러를 짜고, C++ 게임 코드에서 xls 파일과 txt 파일의 날짜를 비교해서 갱신이 필요한 경우 컴파일러를 실행하게 했다.<BR>
</DIV><DIV style='; border:0px; padding-left:12px'> 단, 과도하면 좋지 않다. 엑셀 테이블에 열이 너무 많아지는 경향이 보이면 외부 파일로 빼내거나 포맷을 수정할 것을 고려해 보자.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>변환된 데이터를 직접 수정하지 마라.</li></DIV><DIV style='; border:0px; padding-left:12px'> 변환된 데이터를 직접 수정하게 하면 나중에 원본 데이터가 수정되었을 때 자동으로 최종 결과물에 반영할 수가 없다. 수정은 항상 원본에서만 하게 하자.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>가급적 플레인텍스트를 쓰자.</li></DIV><DIV style='; border:0px; padding-left:12px'> 형상관리 시스템(svn같은)에 친화적이고 데이터 검색도 쉽다. 에디터를 안 만들어도 된다! 대신 에디트플러스 구문강조 문법 파일 정도는 어렵지 않으니 만들어서 보급하도록 하자. 주석도 쓸 수 있으면 좋다. 문제가 생겼을 때 데이터 문제인지 프로그램 문제인지 빠르게 파악할 수 있다. 물론 플레인텍스트는 읽고쓰기가 바이너리보다 느리다. 어디서 쓰고 어디서 쓰지 말아야 할지 적절한 타협점을 찾도록 하자. 속도가 정말 중요한 경우와 확장성이 정말 중요한 경우는 거의 겹치지 않을 거다.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>구조적이고 자기서술적인 데이터 포맷을 만들고 활용해라.</li></DIV><DIV style='; border:0px; padding-left:12px'> 해석기parser를 짜는 부담이 훨씬 줄어든다. XML이 대표적인 예고 루아도 답이 될 수 있다. 우리는 처음에 XML 비슷한 바이너리 포맷을 만들었다가 텍스트로 확장했고 엑셀->텍스트 변환기와 DTD 비슷한 것도 만들었다. 이정도까지 만들 줄 알았으면 그냥 처음에 XML을 도입했을 거다.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>파일 포맷은 확장성 있게 만들어라.</li></DIV><DIV style='; border:0px; padding-left:12px'> 뭐가 됐든 생각도 못했던 기능을 추가하게 된다. 자기서술적인 데이터 포맷을 쓴다면 사소한 기능 추가에 유연하게 대응할 수 있다. 덤으로 디버깅하기도 쉽다. 버전 정보를 남기면 포맷이 업그레이드되었을 때 과거 포맷을 읽을 수 있게 할 수도 있고, 최소한 과거 포맷을 새 프로그램이 읽다가 죽어버리는 건 피할 수 있다.<BR>
</DIV><BR>
<DIV><li style='text-indent:-1em; margin-left:1em; margin-top:5px;' type='disc'>툴의 실제 사용 모습을 관찰해라.</li></DIV><DIV style='; border:0px; padding-left:12px'> 사용자와 이야기를 많이 해라. 단, 사용자의 성격에 주의해라. 분명히 생산성을 개선할 여지가 있는데 그냥 적응해버리거나 얘기를 안 하고 넘어가기도 한다. 단순작업, 반복작업은 기계가 하게 해라. 그리고, 반드시, 직접 사용해 봐라.<BR>
</DIV><DIV style='; border:0px; padding-left:12px'> 초기에 툴을 대충 만들고 방치할 경우, 팀의 병목으로 작용하고 있는데도 모르고 지나칠 수 있으니 주의하자.<BR>
</DIV><BR>
<BR>
사실 인하우스 툴의 생산성을 개선하는 작업은 프로그래머의 시간으로 다른 직군의 시간을 사는 것이기 때문에, 프로그래머들의 시간이 부족하면 좀처럼 하기 어렵다. 출시 날짜가 다가오면서 야근야근 열매를 월화수목금금금 먹고 있는데 툴을 사용하는 사람이 칼퇴근하고 있으면 툴의 문제가 눈에 들어오지 않을 뿐더러 문제를 알아도 고치고 싶지 않을 거다. 이쯤 되면 프로그래머를 비난할 수는 없고, 직군을 초월한 매니저급의 의사결정이 필요한 부분이다. 하지만 프로젝트 초기에는 아무래도 시간이 널럴할 테니 툴을 만들 때 이런저런 고민을 많이 해보기를 권한다. 툴의 품질에 투자하는 만큼 팀 동료의 효율이 올라가서 멋진 컨텐츠를 슴풍슴풍 뽑아내는 것을 보면 꽤 재미나고 보람차다.<BR>
<BR>
여기까지 써놓고 짜식는 소리를 하는 것일 수도 있지만, 너무 일찍 툴을 만드는 것은 안 좋을 수도 있다. 어떤 게임을 만들지 확실히 결정하기 전에 툴부터 만들면 좀더 다양한 게임성을 탐색할 여지를 억제할 수 있다. 기존에 검증된 게임성을 가져다 쓸거면 모르겠으나, 툴보다는 프로토타입이 먼저다.<BR>
<!--end-->]]></description>
<link>http://ricanet.com/new/view.php?id=blog/100223</link>
</item>
<item>
<title>sqlsrv_query 스타일 mysql_query</title>
<pubDate>Sun, 21 Feb 2010 21:16:06 +0900</pubDate>
<description><![CDATA[
▷<A href='http://ricanet.com/new/view.php?id=blog/100221' target='_blank'>직접 가서 보기</A>◁<BR>
<BR>
MSSQLServer의 PHP용 모 드라이버에서 제공하는 쿼리 함수 sqlsrv_query는 호출 단계에서 쿼리 자체와 쿼리의 인자를 문자열로 합치지 않은 채로 넘길 수 있게 되어 있다. 무슨 뜻인고 하니.. sqlsrv_query( "SELECT * FROM auth WHERE id=? and password=?", array($id, $pwd) ); 같은 걸 쓸 수 있다는 뜻이다. 문자열 조작을 하지 않기 때문에 자연스럽게 SQL Injection 공격에 면역이 된다. PHP+MySQL에서 일반적으로 제공되는 mysql_query는 mysql_query( "SELECT * FROM auth WHERE id='$id' and password='$pwd'" ); 와 같이 쓰는데 만약 $pwd에 mysql_escape_string() 따위를 걸어 주는 걸 까먹었다면 SQL Injection 공격에 털릴 수 있게 된다.<BR>
<BR>
PHP5부터는 mysqli라는 게 생기긴 했지만 API가 좀 복잡하고 사용하기 귀찮아 보이는데다가 내가 쓰고 있는 서버에서는 아직 PHP4가 설치되어 있어서, mysql_query를 래핑해서 슥슥 짜 보았다.<BR>
<pre name='code' class='php'>
#Auther: Seungjae Lee ( http://ricanet.com )
#License: Public domain. Feel free to use this for any purpose.

function tostring_for_query( $v )
{
	switch( gettype( $v ) )
	{
	case 'boolean':
		if( $v === true )
			return 'true';
		else
			return 'false';

	case 'integer':
	case 'double':
		return (string)$v;

	case 'string':
		return '"'.mysql_real_escape_string( $v ).'"';

	case 'NULL':
		return 'NULL';

	case 'array':
	case 'object':
	case 'resource':
	default:
		die( 'unknown type or unsupported type: '.gettype( $v ) );
		return '';
	}
}

function mysql_safe_query( $qstr, $args )
{
	$query = '';
	$prevEnd = 0;
	$idx = 0;
	$qlen = strlen( $qstr );
	for( $i = 0; $i &lt; $qlen; ++$i )
	{
		if( $qstr[$i] == '?' )
		{
			if( $idx &gt;= sizeof( $args ) )
			{
				die( 'insufficient parameters' );
			}

			$query .= substr( $qstr, $prevEnd, $i - $prevEnd );
			$query .= tostring_for_query( $args[ $idx ], $linkid );
			$prevEnd = $i + 1;
			++$idx;
		}
	}

	if( $prevEnd &lt; $qlen )
	{
		$query .= substr( $qstr, $prevEnd );
	}

	if( $idx != sizeof( $args ) )
	{
		die( 'too many parameters' );
	}

	return mysql_query( $query );
}

echo '['.mysql_safe_query( 'SELECT * FROM someTable WHERE a=? and b=? and c=?',
		array( 1, "'", '"' ) ).']';
</pre>sqlsrv_query와 비교해 보면 mysql_query는 분명히 실수를 유도하게 만들어져 있다. 저딴 API를 대략 10년 가까이 써 왔다고 생각하면 머리에서 김이 모락모락 올라온다..<BR>
<BR>
웹 프로그래밍은 장난감 규모 이상의 프로젝트를 해 본 적이 없고 다른 코드를 읽어본 적도 별로 없다. 다른 사람들은 PHP에서 injection-safe한 코드를 어떻게 짜는지 궁금하다.<BR>
<BR>
<BR>
오늘의 야크쉐이빙:<BR>
<DIV class='Quote'>metabbs의 zbxe-xml 임포터를 짜던 도중<BR>
-&gt; 악 mysql_query가 짜증나. sqlsrv_query 비슷한 거 없을까... 없네 짜야겠다<BR>
-&gt; 다 짰다. 코드를 블로그에 포스팅하자<BR>
-&gt; 포스팅하는 김에 Syntax Highlighter를 달아야겠다<BR>
</DIV>
임포터 진도는 하나도 못 나갔다.<BR>
<!--end-->]]></description>
<link>http://ricanet.com/new/view.php?id=blog/100221</link>
</item>
<item>
<title>RT considered harmful</title>
<pubDate>Thu, 18 Feb 2010 23:04:32 +0900</pubDate>
<description><![CDATA[
▷<A href='http://ricanet.com/new/view.php?id=blog/100218' target='_blank'>직접 가서 보기</A>◁<BR>
<BR>
구글 버즈를 며칠 써 본 김에, 트위터를 써 오면서 마음에 안 들었던 점을 얘기해 보려 한다.<BR>
<BR>
버즈와 트위터의 차이는 간단하게 요약하면 글타래 개념의 존재 유무다. 버즈는 페이스북과 아주 유사한 글타래 개념을 갖고 있다. 내가 팔로우하는 사람이 만든 글타래가 내 버즈 홈에 나타난다. 그리고 그 글타래에 달린 모든 리플이, 내가 그 리플의 작성자를 팔로우하는지 여부에 상관없이, 나에게 보인다. 반면에 트위터에는 글타래 개념이 없다. 대신 Reply가 있다. Reply는 원래 트윗(트위터에서 글의 단위다)의 작성자와 새 트윗의 작성자 모두를 팔로우해야만 내 홈에 나타난다. &#160;예를 들어 오일러가 쓴 트윗에 대해 가우스가 보낸 Reply는, 오일러와 가우스를 모두 팔로우하는 사람의 홈에만 나타난다.<BR>
<BR>
여기까지는 그럭저럭, 별로 문제가 없다고 느껴진다. 그런데 트위터에는 RT라는 독특한 문화가 있다. Retweet의 약자로, 한국어로 직역하면 다시지저귀기, 의역하면 퍼나르기, 두글자로 줄이면 펌질이다. 다른 사람의 트윗을 출처를 밝히고 짤막한 말을 덧붙인 뒤 그대로 복사&붙여넣는 것과 같다.<BR>
<BR>
트위터를 일종의 방송 매체media로 보면 리트윗은 메세지가 사람들간의 네트워크를 따라 이동하게 하는 운반자carrier다. 메세지가 원래 어디서 발생했든, 아주 많은 사람들에게 전파될 수 있는 잠재력을 갖는 것이다. 다른 사람들이 그 메세지를 전파할 가치가 있다고 평가한다면 말이다.<BR>
<BR>
이렇게 멋있는 도구를 잡담의 도구로 쓰는 경우를 심심찮게 본다. Reply를 써야 할 곳에 RT를 써서 잡담을 하면 대화 참여자 중 한 사람만을 팔로우하는 사람은 진행중인 대화의 한 조각만을 보게 된다. 그리고 그런 대화는 트위터의 특성상 따라잡기가 번거롭다. 맥락을 잃은 트윗은 의미도 잃고, 대신 짜증을 가져다준다.&#160;<BR>
<BR>
RT는 퍼나르는 행위다. 메세지에 퍼나를 가치가 있는지, 당신을 알지만 당신이 잡담하고 있는 상대를 모르는 다른 사람에게도 정말 RT를 읽도록 하고 싶은 것인지 잠깐이라도 고민해 줬으면 좋겠다. 애매하다면 기준을 하나 제시해 보겠다. 복사&붙여넣기를 하지 않고 직접 그 내용을 타이핑해야 하더라도 메세지를 전달하고 싶은가?<BR>
<BR>
아니라면 RT 쓰지 마라.<BR>
<BR>
그래도 꼭 써야겠다면, 미리 알려주겠다. 특별히 당신이 싫어서 언팔로우하는 건 아니다.<BR>
<BR>
<BR>
@ 이 글을 쓸 수 있도록 용기를 내게 해 준 알테양에게 감사한다.<BR>
<BR>
@ 트위터 자체에서 Retweet 기능을 지원한다(안타깝게도 이게 안 되는 클라이언트도 있다). 어차피 퍼나르려면 이쪽을 권장한다. 여럿이 같은 트윗을 리트윗해도 읽는 사람에게는 한번만 나타난다.<BR>
<!--end-->]]></description>
<link>http://ricanet.com/new/view.php?id=blog/100218</link>
</item>
<item>
<title>Flickr 연동</title>
<pubDate>Sat, 06 Feb 2010 19:36:58 +0900</pubDate>
<description><![CDATA[
▷<A href='http://ricanet.com/new/view.php?id=blog/100206' target='_blank'>직접 가서 보기</A>◁<BR>
<BR>
아이를 낳고 나서 한동안 페이스북에 사진을 올리고 있었다. 그런데 페이스북 회원이 아니면 사진을 볼 수 없으므로 뭔가 다른 수단을 강구해야 했다. 트위터에 올리면 사진들이 트윗의 홍수에 밀려 잊혀질 것이 마음에 들지 않았다. 어떻게든 블로그에 모아 놓고 싶었다.<BR>
<BR>
하지만 내 블로그에는 사진을 올리는 간편한 방법이 없다. <s>너도 없다고 말해줘</s> 지금까지는 SFTP 클라이언트로 파일을 올리고 링크를 일일이 걸어 주고 있었다. 얼마 전까진 그게 별로 불편하다고 느끼지 않았는데, 아이폰으로 사진을 찍고 바로 페이스북/트위터에 올리는 일을 몇 번 해 보고 나니 Aㅏ.... 신세계가 열려 버렸다.<BR>
<BR>
지금까지의 방식으로 블로그에 사진을 올리는 것은 지금의 나로선 도저히 귀찮아서 할 수 없는 일이므로, 어떻게든 다른 수단을 만들어내기로 했다. 아이폰으로 찍은 사진을 바로 올릴 수 있어야 한다는 점이 굉장히 중요한 요구사항인데 사파리에서는 사진을 파일로 간주하여 업로드할 수가 없었다. 대신 앱스토어에 플리커 앱이 있었다. 아이폰이나 PC에서 플리커로 사진을 보내고, 블로그에서는 플리커의 RSS 피드를 받아와서 블로그 내용에 동기화하면 되는 것이다.<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
그 과정에서 야크 털도 좀 깎았다...<BR>
<DIV class='Quote'>블로그에 플리커 연동 기능을 넣어야겠어<BR>
-&gt; 하지만 블로그 소스 버전관리가 안되는게 예전부터 불안했는데 (cafe24에서 svn 못 씀)<BR>
-&gt; 버전관리할수있도록 스크립을 짜보쟈<BR>
-&gt; 블로그 버전관리는 내 PC에서 해야겠다.<BR>
-&gt; TortoiseSVN을 써야지...<BR>
-&gt; 맥북에서 터미널로 PC에 들어갔더니 우클릭이 안돼!!<BR>
-&gt; 마우스를 구하자.<BR>
</DIV>
<BR>
리눅스 머신에 있는 파일들을 쭉 긁어오는 것은 pscp로 할 수 있었다. 괜히 파이썬 깔고 삽질했다..<BR>
<!--end-->]]></description>
<link>http://ricanet.com/new/view.php?id=blog/100206</link>
</item>
</channel>
</rss>
