결론부터 이야기 하면 아니다.

아래는 내가 서버로 쓰는 Nehemiah M10000 의 spec 이다. 대략 펜티엄 3 800~1000 MHz 정도 성능 밖에 안된다. 메로리도 지금은 고물인 DDR 266 을 사용한다. ㅎㅎ


CPU 1GHz VIA C3/EDEN (Nehemiah core)
Form factor Mini-ITX
Chipset VIA ProSavage CLE266
North bridge VIA VT8623
South bridge VIA VT8235
Interconnect V-Link (266MB/s)
PCI slots 1 32-bit/33MHz
AGP slots None
AMR/CNR slots None
Memory 1 184-pin DIMM sockets
Maximum of 1GB of DDR266/200 SDRAM


하지만 이것으로 http://whria.net , http://sshan.net , http://medicalphoto.org , http://whria.co.kr 의 4개의 도메인을 돌리고 있다. 4 site 합쳐서 하루 방문자는 2000 명 정도이다.

이 녀석은 mldonkey 라는 당나귀(푸르나)도 24 시간 돌리고 있다. 이외에도 FTP, SQUID, SQL 서버 기능도 하고 있지만 쌩썡하게 잘 돌아간다. 한 때는 라그나로크 서버로 동시에 100 명 접속상태인적도 있었다.

꼬진 CPU 에 무리가 가지 않을까 ??

한번 페이지를 클릭할 때 CPU 점유율이 1초에 3% 정도 된다.

즉 펜티엄 3 800~1000 MHz 성능의 컴퓨터로도 1초에 33 번 페이지 클릭을 버틸 정도 능력이라는 이야기다. 하루가 60 * 60 * 24 = 86400 이니까 33 * 86400 이면 2851200 페이지 뷰를 소화할 수 있다. 펜티엄 3 가 이정도니 최신 컴퓨터는 말할 것 없다.

참고로 이놈은 전기를 무지 적게 먹는다. 최고로 일을 많이 할 때 23W 정도이다. 5년전에 나온 녀석 치고는 엄청나게 적게 먹는다. 당시 CPU 들은 대부분 80 ~ 100 W 정도 먹었다. 요즘은 저전력이 유행이어서 atom, eden 처럼 훌륭한 놈이 많다.




서버에 듀얼 cpu 또는 쿼드 cpu 가 필요할까 ? 결론부터 이야기하면 이 역시 아니다.

물론 하루 방문자 몇십만 정도인 site 를 구축하는데 컴퓨터 꼭 "1대" 로 운영한다면 듀얼 또는 쿼드 CPU 가 필요하겠다.

요즘 나오는 컴퓨터는 성능이 좋기 때문에 듀얼 cpu 가 아니어도 서버 운영이 가능하다. 내가 가지고 있는 Nehemiah M10000 도 잘만 돌아가는데...

만약에 windows 서버를 사용한다면 쿼드 cpu 를 써야 할 필요가 있다. 이는 windows 서버가 너무 비싸기 때문이다. 소프트웨어 값이 너무 비싸서 차라리 소프트웨어 1개 깔고 쿼드 cpu 서버 쓰는게 싸기 때문에 이런경우에는 쿼드 cpu 를 쓴다. 하지만 서버는 linux 기반이 훨씬 관리도 편하고 문서도 많다. windows 서버는 무지 비싸다...

듀얼 또는 쿼드 cpu 는 장점이 있다. 서버 관리하는 중에도 서버가 매끈하게 돌아가는 장점이 있다. 서버 관리 하려고 윈도우 작업을 조금만 해도 cpu 가 80% 이상의 부하가 걸린다. 2개의 cpu 라면 관리 작업 하는데 1개의 cpu 가 80% 이상 차지하고 나머지 1개의 cpu 가 서버 일을 열심히 하고 있을 것이다.

따라서 하루에 만명 정도 오지 않으면 꼭 듀얼 cpu 필요없다.


아래는 다나와의 컴퓨터 -> 디지털완제품 -> 서버 를 출력해보았다. 다 100 만원돈 된다. 너무 비싸다.



 Nehemiah M10000 은 단종되었다. 지금 나에게 새로 서버를 구입하라면 아래 상품을 구입하겠다.


아톰(atom) 기반의 메인보드이다. 330 은 cpu 가 atom 듀얼 cpu 이다. 대신 쿨링팬이 붙는다. MSI Wind Board 는 atom 230 cpu (싱글 cpu) 를 사용하고 fanless 이다. 팬이 없어서 먼지도 안 쌓인다. atom 230 의 경우 전력은 4W 밖에 안먹는다.

위의 메인보드를 기반으로 만든 완제품 PC 도 많이 있다. danawa 에서 atom 230 또는 atom 330 으로 검색하면 나올것이다.

cf) 요즘은 위 제품들을 구할 수 없는 경우가 있습니다. 최근 검색해 본 바로는 넷북 중에 fanless 제품이 있습니다. 대표적으로 Dell inspiron mini 10 이 있습니다. inspiron 시리즈가 모두 초저전력 atom 기반이라서 모두 fanless 입니다. ^^

atom CPU 는 대단히 매력적인 cpu 이다.



직접 개인 서버를 운영해 보라. 은행 업무 외에는 USB 메모리 가지고 다닐 일이 없어진다. 대용량 파일 첨부해서 메일 보낼 때도 그냥 자신의 서버에 올리면 편하다. 자신의 홈페이지 블로그도 직접 운영이 가능하다.

 http://sshan.net , http://whria.net , http://medicalphoto.org 를 한번 와 보시라.

전부 1개의 빈약한(?) 서버로 돌아가고 있다. sshan.net 은 하루 페이지 뷰가 5000 이 넘는다. 하지만 쾌적하고 빠르다.


참고로 서버를 운영하려면 linux 로 운영하는 것이 좋다. gentoo, fedora, mandrake 등등 있지만 ubuntu 를 추천한다. 업데이트가 가장 빠르고 인터페이스가 친숙하기 때문이다. http://ubuntu.org

자~~~ 보드 + CPU 가 10 만원이다. 서버는 모니터가 필요 없으니 케이스 + 램 + 발품비 까지 20 만원이면 족치겠다. 서버 하나 운영해 보시라. 새로운 세계가 보인다.
,



SSD 는 solid state disk 의 약자이다.

하드디스크는 지금까지 용량을 늘리는 방향으로 진화해 왔다. 사람들은 하드디스크를 따질 때 가장 먼저 물어보는 것이 용량이다. 따라서 제조사도 용량을 늘리기 위해 노력했었다.

최근에는 1 TB (1000 giga byte) 의 하드디스크도 나왔다.

하지만....

하드디스크는 여전히 느리다. 용량을 늘리려는 개발만 한 나머지 속도는 예전에 비해 크게 향상되지 않았다. 보급형 하드디스크는 5년전 하드디스크에 비해 50% 정도 밖에 빠르지 않다.

속도 문제 말고도 발열, 정숙성도 중요한 문제이다. 최근에는 많이 개선되었지만 하드디스크가 에러가 나지 않는 것은 정말정말 중요한 문제이다.

이런 모든 문제를 말끔히 해결해 줄 수 있는 것이 바로 SSD 이다.

SSD 는 nand flash 메모리를 가지고 만든다. 메모리를 가지고 만들기 때문에 속도는 기존의 하드보다 비교할 수 없이 빠르다.




가장 일반적인 SSD 이다. 이 사진은 1.8 인치 SSD 이지만 2.5 인치, 3.5 인치 크기의 제품도 있다.

여러 다른 형태의 SSD 도 있다.



첫번째는 CF 카드를 raid 로 연결해서 SSD 로 만들어 주는 젠더이다. 일본에는 4개의 CF 카드까지 병렬로 연결해주는 카드도 나왔다.

2번째는 Expresscard 라고 최신 노트북(2008년 이후)에 장착되어 있는 슬롯에 사용가능한 SSD 이다. 일반 하드 1개 + expresscard SSD ... 합쳐서 2개 장착이 가능한 장점이 있다.

3번째는 USB 에 사용하는 SSD 이다. USB 메모리로 생각해도 되겠다.


Mtron 사의 SSD 벤치마킹 자료이다.


대용량 카피시에는 속도가 94.3 MB/s 정도이다. 이건 현재 쓰는 대부분의 일반 하드디스크와 비슷한 수준이다. 하지만 우측에 보면 random access time 이 0.1 ms 로 나온 것을 볼 수 있다. 이곳 저곳 흩어져 있는 자료를 읽을 때 속도인데 이 속도가 일반 하드 디스크보다 훨씬 빠르다. 일반 하드디스크는 10~15 msec 정도 나온다.

대부분의 컴퓨터 작업은 큰 파일을 카피하기 보다는 작은 파일을 random access 하는 경우가 많기 때문에 이 수치가 체감 성능을 좌우한다.


아래는 일반 하드디스크 벤치마킹 자료이다.



빠른 하드의 대명사인 랩터 150 의 자료이다. 읽기가 135.6 MB/s / random access 7.8msec 이다. 대단하다. 사실 일반 하드디스크로 random access time 이 7.8 msec 면 엄청 빠른것이다. 하지만 SSD 의 0.1 msec 와는 비교가 되지 않는다.


SSD 는 두가지 nand flash 메모리로 만들어 진다. SLC 와 MLC 이다.

SLC 는 단가가 비싸지만 속도가 빠르다. MLC 는 단가가 싸지만 속도가 느리다. 최근에는 값싼 MLC 메모리를 병렬로 연결해서 속도를 증가시키는 방식으로 SSD 를 제조해서 원가를 낮추고 있다. 하지만 이러한 병렬 구조상의 문제로 프리징 현상 (freezing) 현상이라고 작은 파일 읽고 쓰는 도중(우리가 컴퓨터 쓰는 중간중간에 무수히 많은 작은 파일 읽고 쓰는 일이 벌어진다.)에 윈도우가 매우 느려지는 문제가 발생한다. 특히 초창기에 나온 JMicron 사의 602 칩셋의 경우 이 문제가 심각하다.

따라서 당분간은 SLC 로 제작된 SSD 를 구입하는 것이 현명하겠다. 추후 컨트롤러가 개선된다면 MLC 메모리로 제작된 SSD 가 유망하겠다.


SSD 는 현재 삼성, Mtron, Sandisk, Intel, transcend 등이 제조한다. 가장 좋은 제품은 역시 삼성 제품이다. Intel 이 멀티 채널 컨트롤러를 바탕으로 맹추격하는 양상이다. Mtron 은 초반에 열심히 했지만 요즘은 freezing 문제, 호환성 문제 등으로 안습이다.




 
삼성 제품은 OCZ 등의 회사에 OEM 으로 공급된다. OCZ 도 삼성제품이라 생각하면 된다.




삼성 SSD PS105D15 의 벤치마킹이다. 최고 속도가 180 MB /s 에 육박한다. random 은 0.1 ms 이다. 가히 꿈의 하드디스크이다.




아쉽게도 현재 국내에 판매중인 삼성 제품( MCBQE32G5MPP-OVA 등등)은 모두 역수라고 해서 해외 제품을 보따리 상이 가져온 것이다. 따라서 정식 A/S 가 아직 안된다. Mtron 제품은 아까도 언급했듯이 호환성, Freezing 문제가 해결되어야겠다. 한성컴퓨터의 ULTRON 은 JMicron 사의 602 칩셋을 사용한 제품은 Freezing 문제가 있다. 만약 내가 지금 구입한다면 해외에서 OCZ 를 직접 구매하거나 삼성 역수 제품을 구입할 것이다. 비록 A/S 문제가 있지만 메모리 제품의 특성상 처음 고장이 나지 않으면 추후에 고장날 가능성은 거의 zero 이다.

'컴퓨터 이야기~ > 하드웨어' 카테고리의 다른 글

서버는 비싸야 한다 ??  (9) 2009.01.28
서버는 성능이 좋아야 한다 ??  (18) 2009.01.27
블루투스 헤드셋  (0) 2009.01.12
Fanless Core2 MoDT  (0) 2009.01.11
Fanless Server  (0) 2009.01.11
,


 스마트 프라이싱이란 노출되는 광고가 효과적인가 아닌가에 따라 구글이 인센트브 또는 징벌을 가하는 것이라고 하겠다. 대부분 구글 애드센스에서 2가지가 문제가 되는데 1) 부정 클릭 2) 스마트 프라이싱 이다.

일단 스마트 프라이싱(smart pricing)의 정의는 아래와 같다.

<스마트 프라이싱의 정의>

스마트 프라이싱이란 쉽게 말하면 광고의 단가 조정이며
상세하게 말하면 계정마다 클릭당 지불을 차별화하여 지급하는 기능입니다.
이 기능은 2004년 4월부터 작동하고 있었다고 합니다.
광고주는 광고를 클릭하는 사람이
자신의 상품을 구매하여 매출을 올리는게 목적입니다.
근데 애드센스는 자신의 사이트에 들어온 사용자가 광고를 클릭만 하여도
사이트 운영자에게 수익을 줍니다.
그래서 광고클릭만 하고 물건을 사지 않는다면 광고주는 손해를 보게 됩니다.
계속적으로 클릭수는 많은데 구매률과 매출연결이 낮아지고 적어진다면
구글에서는 클릭으로 인한 애드센스 수입단가를 광고주를 배려하여
자동으로 다운 시켜버립니다.

 스마트 프라이싱을 어떻게 대응하느냐는 말이 많은데

1) 노출되는 광고를 줄여라 - 노출은 많고 클릭이 낮으면 단가가 낮아진다.
2) 블로그의 질을 올려라

 인데 1) 번에 대해서는 의견이 분분하다.

 스마트 프라이싱의 원리상 return on investment (RIO) 즉... 투자 회수율에 대해 이야기 하는데 ...

 문제는 나의 지식상 웹상에서 투자회수율(RIO) 를 알아내는 것은 불가능에 가깝다. 광고주에게 일일히 거래가 되었는지 물어보기도 힘들고 더구나 어떤 site 에서 온 손님이 구매를 했는지를 알기는 광고주의 web site 까지 관리하면 모를까 불가능하다.

 따라서 분명 다른 방법으로 스마트 프라이싱에 대한 데이터를 얻으리라 생각된다.

 가장 유력한 것은 Google Analytics 를 이용한 감시이다.


http://www.liewcf.com/blog/wp-images/google-analytics.jpg

위의 그림처럼 구글은 analytics 를 통해 웹사이트의 정보를 정밀하게 얻는다.

광고 때리는 우리 싸이트와 광고주의 싸이트 양쪽에 analytics 분석툴이 설치되어 있다면 광고가 효과적으로 돌아가는지 알 수 있다.

예를 들어 우리 싸이트에서 "홍삼" 으로 클릭해서 광고주의 "홍삼원" 싸이트에 들어갔다고 하자.
만약 소비자가 "홍삼원"에 전혀 관심이 없는데 실수로 클릭 했다면 바로 나올 것이다.

이렇게 바로 나오면

1) bounce rate - 이 수치는 싸이트에 들어가서 클릭없이 바로 나오게 되면 올라가는 수치이다. 한마디로 전혀 관심없이 바로 나오는 경우 올라간다.
2) page view - 1 페이지 밖에 안 보았다.
3) average time - "홍삼원" 싸이트에 10 초나 접속했을래나?

이런 수치들이 엉망으로 나오게 된다. 추측컨데 구글은 이러한 데이터를 몇달정도 모아서 광고 단가를 정해주는 것으로 생각된다.

결국 bounce rate 가 낮고 page view 는 많고 site 에 머문 시간이 길어야 효과적으로 광고한 것이기 때문이다. 웹써핑 한 사람이 물건을 샀는지 안 샀는지는 사실 알 길이 없다.


여기까지 스마트 프라이싱에 대한 나의 추측이다. 동의하는가 ??? 동의한다면 추천 클릭 꽝~!! ^^
,


컴퓨터를 좀 안다고 생각했던 저도 rss 라는 것을 어제 알았습니다. 한마디로 각종 블로그나 기사를 골라읽기 위한 통신 규약이라고 생각하시면 됩니다. 비슷한 것으로 "atom" 이라는 것이 있습니다.

예를 들면 아고라의 "미네르바", 다음블로그의 "상승미소", 네이버블로그의 "드루킹" 님의 글을 읽는다고 합시다. 보통은 3개의 site 를 모두 즐겨찾기를 해놓고 한군데씩 들어가서 봅니다.

하지만 rss 를 이용하면 3 저자의 글을 한 화면에서 볼 수 있습니다 !!! 글도 시간 순서대로 정렬되어 있습니다.

rss 를 읽으려면 rss reader 라는 것이 있어야 하는데 쉽게 사용할 수 있는 rss reader 로는 google reader 가 있습니다. 이외에도 hanrss 등이 있습니다. rss 로 검색하면 나와요.

1) 일단 google.com 에 가입하시고.
2) http://reader.google.com 으로 들어갑니다.



왼쪽 위에 보면 구독 추가라고 되어 있습니다. 여기에 구독하고 싶은 rss 주소를 적어주면 됩니다.

3) rss 주소는 어디서 얻을까요 ?? "미네르바" 님의 rss 를 찾아보겠습니다. 아고라의 미네르바 님의 아이디를 클릭하면 아래처럼 나옵니다.

토론 2 옆에 보면 아까 보여드린  이 있습니다. 이걸 클릭하면 주소가 나옵니다.

이렇게 얻는 미네르바님의 rss 주소는
http://agora.media.daum.net/profile/rss.xml?key=yzcyxX5kuoE0&group_id=1

입니다. 이 주소를 2) 번에서 보여드린 google reader 의 구독 추가란에 적어주시면 됩니다.

현재 거의 모든 블로그 및 뉴스 기사 등이 rss 를 지원합니다. 잘 찾아보면 마크를 찾을 수 있습니다.

4) 결과입니다. 글이 잘 정렬되어 있습니다.




rss 로 많은 site 를 등록해서 본다면 시간도 절약하고 아고라의 알밥들도 피해갈 수 있으리라 생각됩니다. 몇몇 까페에서는 사람들이 죽어라 퍼나르고 있는데 이런 수고를 할 필요가 없습니다.

사실 저도 rss 를 모르고 열심히 비슷한 기능이 있는 홈페이지를 만드느라 고생 좀 했습니다. 진작 알았으면 이런 수고를 덜었겠죠. 그래도 많은 글들이 많이 있는 편리한 site 입니다. 많이 놀러와주세요.
http://sshan.net
,


Analytics 로 결과를 보면 검색엔진이 전부 "search" 로 나온다.
이는 네이버 / 다음 과 같은 검색 site 가 등록이 안되어 있어서 발생하는 문제이다.
검색엔진을 등록하면 해결할 수 있다.

google 의 help 를 보면

pageTracker._addOrganic("name_of_searchengine","q_var");

로 등록하라고 한다. name_of_searchengine 은 검색 엔진의 고유한 주소이고 q_var 는 검색 키워드가 저장되는 변수명이다.


예를 들면 엠파스의 경우 "keyword" 로 검색버튼을 누르면 주소창에

http://search.empas.com/search/all.html?z=A&q=keyword&x=0&y=0&qn=&s=&f=&bd=&bw=&tq=

라고 뜨는 것을 볼 수 있다. search.empas.com 이 검색 엔진 주소이고 &q=keyword 에서 q 가 검색값인 "keyword" 가 저장되는 장소이다. 따라서

pageTracker._addOrganic("empas","q");

를 추가하면 된다.

한가지 문제가 더 있는데 구글은 search 가 들어가는 주소를 "search"라는 검색엔진으로 등록하고 있다. 이것을 무시하게 만들어야 한다.

pageTracker._clearOrganic();

를 앞에 추가하자.

cf) 원래는
pageTracker._addIgnoredOrganic("search"); 만 해도 search 가 삭제되야 하는데 버그가 있는 것 같다. pageTracker._clearOrganic(); 을 추가해서 모든 검색엔진 정보를 지웠다. 이경우에는 search 엔진을 전부 등록해 주어야한다. 우리나라에서 쓰는 엔진 위주로 올렸다.


아래는 내가 쓰는 analytics 코드이다. "UA-XXXXXX-X" 부분을 자신의 것으로 바꾸어 쓰면 된다. 아래 코드를 모두 </body> tag 전에 삽입하면 인식이 된다.

ps) 아래에 UA-@@@@@@-@ 는 자신의 것으로 바꾸어야 한다.



<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-@@@@@@-@");
pageTracker._clearOrganic();
pageTracker._addOrganic("naver.com","query");
pageTracker._addOrganic("daum.net","q");
pageTracker._addOrganic("nate.com","q");
pageTracker._addOrganic("empas.com","q");
pageTracker._addOrganic("google.com","q");
pageTracker._addOrganic("paran.com","Query");
pageTracker._addOrganic("yahoo.com","p");
pageTracker._trackPageview();
} catch(err) {}</script>



http://www.antezeta.com/blog/google-analytics-search-engines

http://www.google.com/support/googleanalytics/bin/answer.py?answer=57046

http://www.google.com/support/googleanalytics/bin/answer.py?answer=55479
,


http://mldonkey.sourceforge.net/

donkey(당나귀)는 p2p 프로그램의 대명사입니다. 많이 쓰이는 overnet 도 donkey 에서 힌트를 얻어 나왔지요. overnet 과 donkey 의 차이는 donkey 는 중앙 서버가 필요한 반면에 overnet 의 경우에는 중앙 서버가 필요없는 완벽한 p2p 라고 할 수 있겠습니다.

하여튼 donkey 는 이미 소스가 공개 되어서 emule, aMule, xMule, 푸루나 등의 clone 이 만들어졌습니다. 안의 구조는 같지만 사용하기 편하게 조금씩 바꾼 프로그램들이지요.

그 중에서 가장 특징이 있는 donkey 를 꼽는다면 주저없이 mldonkey 를 꼽겠습니다. mldonkey 는 두가지 중요한 특징이 있습니다.

1) explore 로 원격 제어가 가능하다.

다른 donkey 와 다르게 mldonkey 는 인터넷 explore 를 통해 접근이 가능합니다. 아래는 explore 에서 본 mldonkey 의 제어창입니다.



2) 사용자 계정 설정이 가능하다.

 mldonkey 내에서는 사용자 설정을 할 수 있습니다. 저희 집 같은 경우 신발장에 donkey 서버가 있습니다. 물론 mldonkey 도 설치되어 있지요. mldonkey 안에 3명의 계정이 있답니다. 각각의 사람이 접속해서 자료를 다운을 걸어 놓으면 자신의 계정 폴더에 다운로드됩니다.


 donkey 의 가장 큰 문제는 느린 다운 속도였습니다. 이 때문에 donkey 프로그램을 하루종일 켜놓는 경우가 많죠. 하지만 donkey 하나 쓸려고 컴퓨터를 하루종일 켜 놓는 것은 낭비입니다. mldonkey 가 없다면 형이랑 동생이랑 둘다 donkey 를 사용한다면 컴퓨터를 2대 켜놓아야 합니다. 하지만 mldonkey 를 쓰면 mldonkey 가 설치된 컴퓨터 한대가 계속 일을 하게 됩니다. 형/동생 계정으로 다운로드를 제어하면 되지요.

 저희 집 같은 경우 신발장에 자료 저장용 linux 컴퓨터가 있는에 이 녀석이 mldonkey 서버 역할을 하고 있습니다. 우리집 식구는 모두 mldonkey 계정이 있습니다. 일단 자료만 걸어 놓으면 하루종일 받고 있습니다. 똘똘한 놈이지요.


 


,


제가 아는 인터넷 앨범 프로그램에 가장 많이 쓰이는 것은 2가지 있습니다.

1) Gallery (http://gallery.menalto.com)
Gallery - YOUR PHOTOS ON YOUR WEBSITE
2) album.pl (http://perl.bobbitt.ca/album)



입니다. 두개 중에 우열을 가린다면 Gallery 가 압승입니다. album.pl 은 perl 로 만들어져 있는데 아무래도 php 로 만들어진 gallery 보다 느립니다. 서버에 부하도 많이 줍니다.

제가 사용하는 gallery 는 ubuntu linux 에서 자동 업데이트 되는 버젼으로 사용합니다. 아무래도 apt-get(우분투 프로그램 업데이터) 으로 업데이트 하는 것이 편하니까요.

제 홈페이지(http://whria.net) 와 보시면 photography 부분이 있습니다. 이것은 gallery 로 만들어져 있습니다. 매우 짜임새있고 좋습니다. 단점이라면 댓글 다는 부분이 조금 부족합니다.
,


유니 코드 (UTF-8 or UTF-16LE) 를 local (아스키 또는 각자의 codepage) 로 변환시키는 문제는 유니코드 지원 프로그래밍을 위해서는 매우 복잡한 문제이다.

먼저 보통 말하는 local codepage 랑 아스키랑 같은 것이라는 것을 알자. 나는 처음에 이게 차이가 있을 까봐 정말 머리가 아팠다.

결국...

UTF8 <-> UTF16LE
UTF16LE <-> UTF8
UTF8 <-> local
UTF16LE <-> local

이렇게 4 가지 조합만 바꿀 수 있으면 unicode 를 완벽하게 지원하는 프로그램을 짤 수 있다.

c++ 에서 사용할 수 있는 locale 을 바꿔 주는 library 가 몇가지 있는데...

1) iconv
2) boost
3) qt 의 qstring
4) 그리고 피부미인의 codechanger ㅋㅋㅋ

1 번은 가장 많이 쓰는데 static 으로 compile 할라면 머리아프다. 나는 DLL 을 정말 싫어한다.
2 번은 사람들이 잘 모르는데 boost 안에 일부 function 이 unicode 프로그래밍의 중요한 clue 를 제공한다. 여기서 개발된 것이 4 번의 피부미인의 codechanger 이다.
3 번 qt 는 일단 install 할라면 너무 머리아프다. build 하다가 다른 library 랑 부딛치면 돌아버린다.

4 번 피부미인의 codechanger 는 내가 medicalphoto 라는 프로젝트를 하면서 정말정말 어렵게 만든겁니다.  여기에만 특별히 공개하겠습니다. ^^g 이걸 쓰려면 boost library 를 설치해야합니다. 아니면 utf8-codecvt_facet.hpp 에 있는 boost/config.hpp 나 boost/detail/workaround.hpp 등만 copy 해서 사용해도 됩니다.


사용법은 아래와 같다.

MCodeChanger::_CCL("unicode letters") = "local code letters"
MCodeChanger::_CCU("local code letters") = "unicode letters"



1. codechanger.h

////////////////////////////////////////////////////////////////////////////////
// Copyright : Han Seung Seog
// It was so damn hard to make this library
// http://prettygom.com
// http://sshan.net
// 2008. 8. 1
////////////////////////////////////////////////////////////////////////////////

#pragma once

#include "../boost.h"
#include <string>
#include <boost/format.hpp>
#include "tchar.h"
#include "utf8_codecvt_facet.hpp"
#include "unicode.h"

#ifdef _UNICODE
    #define _CCL U_W
    #define _CCU W_U
    #define _CCW mbs_to_wcs
    #define _CCN wcs_to_mbs
#else
    #define _CCL U_L
    #define _CCU L_U
    #define _CCW LocaltoLocal
    #define _CCN LocaltoLocal
#endif // _UNICODE

class MCodeChanger
{
public:
    static tstring LocaltoLocal(const tstring& str)
    {
        return str;
    }

    static std::string L_U(const std::string& str)
    {
        std::locale local(std::locale(""),new utf8_codecvt_facet);
        return wcs_to_mbs(mbs_to_wcs(str),local);
    }
    static std::string U_L(const std::string& str)
    {
        std::locale local(std::locale(""),new utf8_codecvt_facet);
        return wcs_to_mbs(mbs_to_wcs(str,local));
    }
    static std::string W_U(const std::wstring& str)
    {
        std::locale local(std::locale(""),new utf8_codecvt_facet);
        return wcs_to_mbs(str,local);
    }
    static std::wstring U_W(const std::string& str)
    {
        std::locale local(std::locale(""),new utf8_codecvt_facet);
        return mbs_to_wcs(str,local);
    }

static std::wstring
mbs_to_wcs(std::string const& str, std::locale const& loc = std::locale(""))
{
    typedef std::codecvt<wchar_t, char, std::mbstate_t> codecvt_t;
    codecvt_t const& codecvt = std::use_facet<codecvt_t>(loc);
    std::mbstate_t state = 0;
    std::vector<wchar_t> buf(str.size() + 1);
    char const* in_next = str.c_str();
    wchar_t* out_next = &buf[0];
    codecvt_t::result r = codecvt.in(state,
        str.c_str(), str.c_str() + str.size(), in_next,
        &buf[0], &buf[0] + buf.size(), out_next);
    return std::wstring(&buf[0]);
}
 
static std::string
wcs_to_mbs(std::wstring const& str, std::locale const& loc = std::locale(""))
{
    typedef std::codecvt<wchar_t, char, std::mbstate_t> codecvt_t;
    codecvt_t const& codecvt = std::use_facet<codecvt_t>(loc);
    std::mbstate_t state = 0;
    std::vector<char> buf((str.size() + 1) * codecvt.max_length());
    wchar_t const* in_next = str.c_str();
    char* out_next = &buf[0];
    codecvt_t::result r = codecvt.out(state,
        str.c_str(), str.c_str() + str.size(), in_next,
        &buf[0], &buf[0] + buf.size(), out_next);
    return std::string(&buf[0]);
}
};

2. [ utf8_codesvt_facet.hpp ]

// Copyright ?2001 Ronald Garcia, Indiana University (garcia@osl.iu.edu)
// Andrew Lumsdaine, Indiana University (lums@osl.iu.edu).
// Distributed under the Boost Software License, Version 1.0. (See accompany-
// ing file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#pragma once

// MS compatible compilers support #pragma once
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
# pragma once
#endif

/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// utf8_codecvt_facet.hpp

// This header defines class utf8_codecvt_facet, derived fro
// std::codecvt<wchar_t, char>, which can be used to convert utf8 data in
// files into wchar_t strings in the application.
//
// The header is NOT STANDALONE, and is not to be included by the USER.
// There are at least two libraries which want to use this functionality, and
// we want to avoid code duplication. It would be possible to create utf8
// library, but:
// - this requires review process first
// - in the case, when linking the a library which uses utf8
//   (say 'program_options'), user should also link to the utf8 library.
//   This seems inconvenient, and asking a user to link to an unrevieved
//   library is strange.
// Until the above points are fixed, a library which wants to use utf8 must:
// - include this header from one of it's headers or sources
// - include the corresponding .cpp file from one of the sources
// - before including either file, the library must define
//   - BOOST_UTF8_BEGIN_NAMESPACE to the namespace declaration that must be used
//   - BOOST_UTF8_END_NAMESPACE to the code to close the previous namespace
//   - declaration.
//   -  -- to the code which must be used for all 'exportable'
//     symbols.
//
// For example, program_options library might contain:
//    #define BOOST_UTF8_BEGIN_NAMESPACE <backslash character>
//             namespace boost { namespace program_options {
//    #define BOOST_UTF8_END_NAMESPACE }}
//    #define  BOOST_PROGRAM_OPTIONS_DECL
//    #include "../../detail/utf8/utf8_codecvt.cpp"
//
// Essentially, each library will have its own copy of utf8 code, in
// different namespaces.

// Note:(Robert Ramey).  I have made the following alterations in the original
// code.
// a) Rendered utf8_codecvt<wchar_t, char>  with using templates
// b) Move longer functions outside class definition to prevent inlining
// and make code smaller
// c) added on a derived class to permit translation to/from current
// locale to utf8

//  See http://www.boost.org for updates, documentation, and revision history.

// archives stored as text - note these ar templated on the basic
// stream templates to accommodate wide (and other?) kind of characters
//
// note the fact that on libraries without wide characters, ostream is
// is not a specialization of basic_ostream which in fact is not defined
// in such cases.   So we can't use basic_ostream<OStream::char_type> but rather
// use two template parameters
//
// utf8_codecvt_facet
//   This is an implementation of a std::codecvt facet for translating
//   from UTF-8 externally to UCS-4.  Note that this is not tied to
//   any specific types in order to allow customization on platforms
//   where wchar_t is not big enough.
//
// NOTES:  The current implementation jumps through some unpleasant hoops in
// order to deal with signed character types.  As a std::codecvt_base::result,
// it is necessary  for the ExternType to be convertible to unsigned  char.
// I chose not to tie the extern_type explicitly to char. But if any combination
// of types other than <wchar_t,char_t> is used, then std::codecvt must be
// specialized on those types for this to work.

#include <locale>
// for mbstate_t
#include <wchar.h>
// for std::size_t
#include <cstddef>

#include <boost/config.hpp>
#include <boost/detail/workaround.hpp>

namespace std {
    #if defined(__LIBCOMO__)
        using ::mbstate_t;
    #elif defined(BOOST_DINKUMWARE_STDLIB) && !defined(__BORLANDC__)
        using ::mbstate_t;
    #elif defined(__SGI_STL_PORT)
    #elif defined(BOOST_NO_STDC_NAMESPACE)
        using ::mbstate_t;
        using ::codecvt;
    #endif
} // namespace std

#if !defined(__MSL_CPP__) && !defined(__LIBCOMO__)
    #define BOOST_CODECVT_DO_LENGTH_CONST const
#else
    #define BOOST_CODECVT_DO_LENGTH_CONST
#endif

// maximum lenght of a multibyte string
#define MB_LENGTH_MAX 8

struct  utf8_codecvt_facet :
    public std::codecvt<wchar_t, char, std::mbstate_t> 
{
public:
    explicit utf8_codecvt_facet(std::size_t no_locale_manage=0)
        : std::codecvt<wchar_t, char, std::mbstate_t>(no_locale_manage)
    {}
protected:
    virtual std::codecvt_base::result do_in(
        std::mbstate_t& state,
        const char * from,
        const char * from_end,
        const char * & from_next,
        wchar_t * to,
        wchar_t * to_end,
        wchar_t*& to_next
    ) const;

    virtual std::codecvt_base::result do_out(
        std::mbstate_t & state, const wchar_t * from,
        const wchar_t * from_end, const wchar_t*  & from_next,
        char * to, char * to_end, char * & to_next
    ) const;

    bool invalid_continuing_octet(unsigned char octet_1) const {
        return (octet_1 < 0x80|| 0xbf< octet_1);
    }

    bool invalid_leading_octet(unsigned char octet_1)   const {
        return (0x7f < octet_1 && octet_1 < 0xc0) ||
            (octet_1 > 0xfd);
    }

    // continuing octets = octets except for the leading octet
    static unsigned int get_cont_octet_count(unsigned   char lead_octet) {
        return get_octet_count(lead_octet) - 1;
    }

    static unsigned int get_octet_count(unsigned char   lead_octet);

    // How many "continuing octets" will be needed for this word
    // ==   total octets - 1.
    int get_cont_octet_out_count(wchar_t word) const ;

    virtual bool do_always_noconv() const throw() { return false; }

    // UTF-8 isn't really stateful since we rewind on partial conversions
    virtual std::codecvt_base::result do_unshift(
        std::mbstate_t&,
        char * from,
        char * /*to*/,
        char * & next
    ) const
    {
        next = from;
        return ok;
    }

    virtual int do_encoding() const throw() {
        const int variable_byte_external_encoding=0;
        return variable_byte_external_encoding;
    }

    // How many char objects can I process to get <= max_limit
    // wchar_t objects?
    virtual int do_length(
        BOOST_CODECVT_DO_LENGTH_CONST std::mbstate_t &,
        const char * from,
        const char * from_end,
        std::size_t max_limit
#if BOOST_WORKAROUND(__IBMCPP__, BOOST_TESTED_AT(600))
        ) const throw();
#else
        ) const;
#endif

    // Largest possible value do_length(state,from,from_end,1) could return.
    virtual int do_max_length() const throw () {
        return 6; // largest UTF-8 encoding of a UCS-4 character
    }
};


3. [utf8_codecvt_facet.cpp]

/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// utf8_codecvt_facet.cpp

// Copyright ?2001 Ronald Garcia, Indiana University (garcia@osl.iu.edu)
// Andrew Lumsdaine, Indiana University (lums@osl.iu.edu).
// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// Please see the comments in <boost/detail/utf8_codecvt_facet.hpp> to
// learn how this file should be used.
#include "stdafx.h"
#include "utf8_codecvt_facet.hpp"

#include <cstdlib> // for multi-byte converson routines
#include <cassert>

#include <boost/limits.hpp>
#include <boost/config.hpp>

// If we don't have wstring, then Unicode support
// is not available anyway, so we don't need to even
// compiler this file. This also fixes the problem
// with mingw, which can compile this file, but will
// generate link error when building DLL.
#ifndef BOOST_NO_STD_WSTRING

/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// implementation for wchar_t

// Translate incoming UTF-8 into UCS-4
std::codecvt_base::result utf8_codecvt_facet::do_in(
    std::mbstate_t& /*state*/,
    const char * from,
    const char * from_end,
    const char * & from_next,
    wchar_t * to,
    wchar_t * to_end,
    wchar_t * & to_next
) const {
    // Basic algorithm:  The first octet determines how many
    // octets total make up the UCS-4 character.  The remaining
    // "continuing octets" all begin with "10". To convert, subtract
    // the amount that specifies the number of octets from the first
    // octet.  Subtract 0x80 (1000 0000) from each continuing octet,
    // then mash the whole lot together.  Note that each continuing
    // octet only uses 6 bits as unique values, so only shift by
    // multiples of 6 to combine.
    while (from != from_end && to != to_end) {

        // Error checking   on the first octet
        if (invalid_leading_octet(*from)){
            from_next = from;
            to_next = to;
            return std::codecvt_base::error;
        }

        // The first octet is   adjusted by a value dependent upon
        // the number   of "continuing octets" encoding the character
        const   int cont_octet_count = get_cont_octet_count(*from);
        const   wchar_t octet1_modifier_table[] =   {
            0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc
        };

        // The unsigned char conversion is necessary in case char is
        // signed   (I learned this the hard way)
        wchar_t ucs_result =
            (unsigned char)(*from++) - octet1_modifier_table[cont_octet_count];

        // Invariants   :
        //   1) At the start of the loop,   'i' continuing characters have been
        //    processed
        //   2) *from   points to the next continuing character to be processed.
        int i   = 0;
        while(i != cont_octet_count && from != from_end) {

            // Error checking on continuing characters
            if (invalid_continuing_octet(*from)) {
                from_next   = from;
                to_next =   to;
                return std::codecvt_base::error;
            }

            ucs_result *= (1 << 6);

            // each continuing character has an extra (10xxxxxx)b attached to
            // it that must be removed.
            ucs_result += (unsigned char)(*from++) - 0x80;
            ++i;
        }

        // If   the buffer ends with an incomplete unicode character...
        if (from == from_end && i   != cont_octet_count) {
            // rewind "from" to before the current character translation
            from_next = from - (i+1);
            to_next = to;
            return std::codecvt_base::partial;
        }
        *to++   = ucs_result;
    }
    from_next = from;
    to_next = to;

    // Were we done converting or did we run out of destination space?
    if(from == from_end) return std::codecvt_base::ok;
    else return std::codecvt_base::partial;
}

std::codecvt_base::result utf8_codecvt_facet::do_out(
    std::mbstate_t& /*state*/,
    const wchar_t *   from,
    const wchar_t * from_end,
    const wchar_t * & from_next,
    char * to,
    char * to_end,
    char * & to_next
) const
{
    // RG - consider merging this table with the other one
    const wchar_t octet1_modifier_table[] = {
        0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc
    };

    wchar_t max_wchar = (std::numeric_limits<wchar_t>::max)();
    while (from != from_end && to != to_end) {

        // Check for invalid UCS-4 character
        if (*from  > max_wchar) {
            from_next = from;
            to_next = to;
            return std::codecvt_base::error;
        }

        int cont_octet_count = get_cont_octet_out_count(*from);

        // RG  - comment this formula better
        int shift_exponent = (cont_octet_count) *   6;

        // Process the first character
        *to++ = static_cast<char>(octet1_modifier_table[cont_octet_count] +
            (unsigned char)(*from / (1 << shift_exponent)));

        // Process the continuation characters
        // Invariants: At   the start of the loop:
        //   1) 'i' continuing octets   have been generated
        //   2) '*to'   points to the next location to place an octet
        //   3) shift_exponent is   6 more than needed for the next octet
        int i   = 0;
        while   (i != cont_octet_count && to != to_end) {
            shift_exponent -= 6;
            *to++ = static_cast<char>(0x80 + ((*from / (1 << shift_exponent)) % (1 << 6)));
            ++i;
        }
        // If   we filled up the out buffer before encoding the character
        if(to   == to_end && i != cont_octet_count) {
            from_next = from;
            to_next = to - (i+1);
            return std::codecvt_base::partial;
        }
        *from++;
    }
    from_next = from;
    to_next = to;
    // Were we done or did we run out of destination space
    if(from == from_end) return std::codecvt_base::ok;
    else return std::codecvt_base::partial;
}

// How many char objects can I process to get <= max_limit
// wchar_t objects?
int utf8_codecvt_facet::do_length(
    BOOST_CODECVT_DO_LENGTH_CONST std::mbstate_t &,
    const char * from,
    const char * from_end,
    std::size_t max_limit
#if BOOST_WORKAROUND(__IBMCPP__, BOOST_TESTED_AT(600))
) const throw()
#else
) const
#endif
{
    // RG - this code is confusing!  I need a better way to express it.
    // and test cases.

    // Invariants:
    // 1) last_octet_count has the size of the last measured character
    // 2) char_count holds the number of characters shown to fit
    // within the bounds so far (no greater than max_limit)
    // 3) from_next points to the octet 'last_octet_count' before the
    // last measured character. 
    int last_octet_count=0;
    std::size_t char_count = 0;
    const char* from_next = from;
    // Use "<" because the buffer may represent incomplete characters
    while (from_next+last_octet_count <= from_end && char_count <= max_limit) {
        from_next += last_octet_count;
        last_octet_count = (get_octet_count(*from_next));
        ++char_count;
    }
    return static_cast<int>(from_next-from_end);
}

unsigned int utf8_codecvt_facet::get_octet_count(
    unsigned char   lead_octet
){
    // if the 0-bit (MSB) is 0, then 1 character
    if (lead_octet <= 0x7f) return 1;

    // Otherwise the count number of consecutive 1 bits starting at MSB
//    assert(0xc0 <= lead_octet && lead_octet <= 0xfd);

    if (0xc0 <= lead_octet && lead_octet <= 0xdf) return 2;
    else if (0xe0 <= lead_octet && lead_octet <= 0xef) return 3;
    else if (0xf0 <= lead_octet && lead_octet <= 0xf7) return 4;
    else if (0xf8 <= lead_octet && lead_octet <= 0xfb) return 5;
    else return 6;
}

namespace {
template<std::size_t s>
int get_cont_octet_out_count_impl(wchar_t word){
    if (word < 0x80) {
        return 0;
    }
    if (word < 0x800) {
        return 1;
    }
    return 2;
}

// note the following code will generate on some platforms where
// wchar_t is defined as UCS2.  The warnings are superfluous as
// the specialization is never instantitiated with such compilers.
template<>
int get_cont_octet_out_count_impl<4>(wchar_t word){
    if (word < 0x80) {
        return 0;
    }
    if (word < 0x800) {
        return 1;
    }
    if (word < 0x10000) {
        return 2;
    }
    if (word < 0x200000) {
        return 3;
    }
    if (word < 0x4000000) {
        return 4;
    }
    return 5;
}

} // namespace anonymous

// How many "continuing octets" will be needed for this word
// ==   total octets - 1.
int utf8_codecvt_facet::get_cont_octet_out_count(
    wchar_t word
) const {
    return get_cont_octet_out_count_impl<sizeof(wchar_t)>(word);
}


#endif



,


원래 컴퓨터 문자의 시초는 아스키 코드다. 아스키 코드에서는 1 문자는 1 byte 로 이루어져 있다.

하지만 이것으로는 모든 문자를 표현하는 것이 불가능하다. 요즘처럼 글로벌 시대에 다국어를 표현하려면 1 byte 는 많이 부족하다 특히 한글은 전세계 언어중에서 가장 큰 다양성을 가지고 있는데 모두 다 조합하면 다른 언어 다 합친것의 절반이상의 용량을 차지한다. 세종대왕님 감사합니다. ^^

다국어 뿐만 아니라 특수 문자 문제도 있기 때문에 적어도 2 byte 의 길이를 가진 code set 이 필요하게 되었다.

하지만 컴퓨터는 미국에서 개발되었고 걔네들은 2byte 쓸 이유가 없다. 특히 램값이 금값인 시절에 문자열 하나에는 1 byte 이상 차지하는 건 사치다. 그래서 1 byte = 1 문자로 최근까지 이어져왔다. 하지만 우리나라 같은 곳에서는 어쩔 수 없이 편법을 써서라도 한글을 표현해야 했고, 이를 극복하기 위해서 쓰는 대표적인 개념이 codepage 라는 개념이다.

데이터는 고정된 상태에서 codepage 에 따라 보이는 모양이 변한다. 예전에 일본 게임을 한국 윈도우에서 실행하면 메뉴의 글이 깨지는 것을 볼 수 있다. code page 가 일본으로 설정되어 있어야 제대로 보이기 때문이다. 하지만 일본 게임의 일본어를 보기위해 기본 code page 를 일본으로 설정하면 한글 윈도우 내의 다른 모든 한글이 엉망이 되버리는 문제가 있다.


이러한 문제로 개발 된 것이 unicode 이다.
모든 문자셋 + 기호를 지원하기 위해 2 byte 이상의 용량을 차지하는 문자셋을 개발한 것이다.

윈도우도 windows 2000 부터는 문자 set 으로 unicode 가 사용되었다. 기존의 아스키 코드가 1 byte 라면 window 에서 사용하는 unicode 는 2 byte 로 고정되어 있는 UTF16LE (little edition) 을 사용한다. 참고로 대부분의 unix 계열은 UTF-8 을 사용한다.

나도 처음에 unicode 하면 UTF16LE 인줄 알았다. 근데 잘 보니 unicode 에 종류가 무척 많다. 다 기억은 못하지만 UTF16BE (big edition) 도 있다. 다 알것 없고 unicode 는 2가지를 많이 쓴다고만 알면 된다.

1) UTF16LE --> windows 2000, winxp, vista, windows 7 등의 unicode / 2 byte 고정
2) UTF-8 --> unix/linux 계열에서 사용 / mysql 등의 database 에서 사용 / web 에서 표준 / 1 byte ~ 4 byte 가변
 
즉 많이 쓰는 것은 UTF16LE 와 UTF-8 두가지다.
 
UTF-8 이 참 재미있는 녀석인데 이놈은 개발 당시부터 아스키를 기준으로 만들어진 기존 프로그램을 그대로 이용하기 위해서 만들어졌다. 따라서 아스키 문자열과 호환이 된다. 하지만 1 byte 인 아스키 문자열이 커버하지 못하는 부분을 1 byte ~ 4 byte 까지 더 확장해서 표현한다. 그리고 문자열 중간에 null code 가 없기 때문에 기존 아스키 프로그램에 잘 돌아간다. 이런 이유로 unix 계열 / web / database 에서 unicode 하면 대부분 UTF-8 이다.

UTF16LE 는 마이크로 소프트 윈도우즈에서 사용되는 2 byte 문자셋이다. 장점은 문자열 길이 잴때 편하다(무조건 2로 나누면 되니깐... UTF-8 은 한문자가 몇바이트인지 앞에서부터 세보지 않으면 알 수 없다.)는 것 빼고는 다른 면에서 UTF-8 보다 뭐가 좋은지 잘 모르겠다. 결정적으로 기존 아스키 프로그램에 호환이 안되기 때문에 프로그램을 다시 짜야한다.

함수를 모조리 바꿔야 하는데 이게 보통 머리아픈게 아니다. 윈도우 내장 API 함수를 보면 MessageBoxA / MessageBoxW 이렇게 2가지가 있는데 A 로 끝나는 것은 기존의 아스키 함수 / W 는 Wide Character 를 쓰는 유니코드 함수이다. MFC 같은 라이브러리에서는 MessageBox 라고 하면 셋팅을 보고 알아서  MessageBoxA / MessageBoxW 중에 한놈으로 바꿔준다.


 


 
,


요즘은 인터넷 쇼핑이 간편하게 일목 요연하게 잘되어서 참 좋습니다. 용산 전자 상가 같은 경우 이제 직접 가지를 않습니다. 갈 때마다 바가지 가격 때문에 직접 간 것을 후회하니까요.

지금은 환율이 올라서 좀 좋지 않지만 해외 인터넷 쇼핑은 정말 매력적입니다.

옷이나 장신구 전자제품 모두 가격이 우리나라 최저가보다 20% 정도 저렴합니다. 이런것을 보면 우리나라가 얼마나 물가가 높은지 실감하게 됩니다. 요즘에는 옷이나 장신구는 해외 여행 하면서 사오거나 해외 인터넷 쇼핑으로 구입합니다. 전자제품의 경우 danawa.com 의 최저가가 싸긴 하지만 희귀 아이템의 경우 해외에서 직접 받는 것이 훨씬 낫습니다.

제가 해외 쇼핑을 하는 주된 경로는 ebay 입니다.

iRobot Roomba 4210 Discovery Robotic Vacuum NEW --> 배송비+관세 합쳐도 국내 구입가의 1/3 가격.
Serener Fanless mini-ITX case with CD bay USB and Firewire --> 해외에서 밖에 못구하는 아이템

했었는데 모두 2 주정도 후에 잘 전달 되었습니다. 아래는 chek point

1) 대금 결제는 어떻게 하는가 ?
 신용카드 사용, 대금을 이체하는 방법 등이 있지만 가장 편한 방법은 paypal 을 이용하는 것입니다. 신용 카드 하나를 paypal.com 에 등록하면 이후 대금은 신용카드에서 빠져 나갑니다.

2) 구매 대행을 할 것인가 배송 대행을 할 것인가 ?
 저는 무조건 배송 대행입니다. 배송 대행을 하면 전체 비용이 대략 우리나라 소포비의 10배정도 든다고 생각하면 됩니다. 관세 문제도 훨씬 유연하게 대체 가능합니다. ㅎㅎ 신고 가격을 제가 입력하니까요. 제가 주로 이용하는 site 는 la09.com 인데 만족합니다.
   
3) 배송 대행을 할 것인가 쇼핑몰에서 직접 받을 것인가 ?
 일부 인터넷몰은 해외 고객을 위해 포장해서 보내주는 서비스를 합니다. 물품이 작고 딱 1개 구입한다면 쇼핑몰에서 직접 보내주는 방식이 좋습니다. 하지만 대부분의 경우에는 관세 문제와 묶음 포장 해서 배송비 절감효과 때문에 배송 대행 업체를 이용하는 것이 좋은 것 같습니다. ^^

4) 관세는 어떻게 내나 ?
 물품이 한국에 도착하기 전에 국세청에서 전화가 옵니다. 관세 어느정도 붙는다구... 그러면 물건 받을 때 택배 아저씨한테 대금 주면 됩니다. ^^

,