Monthly Archives: May 2007

네이버 사전 OpenSearch 플러그인

원서 보면서 사전 찾을때마다 좀 불편했는데, Firefox의 검색을 통해서 빠르게 네이버 사전을 찾을수 있도록 플러그인을 만들었습니다. 만드는 방법은 XML 파일 하나 만들면 되더군요. OpenSearch라는 표준?이 있나본데 Firefox 2.0 이상과 IE 7 이상에서 지원된다고 합니다.

IE 7은 현재 사용중이 아니라 테스트 못해봤고, 검색을 치는 도중에 자동 완성도 지원하는데, 네이버가 표준을 따르지 않아서 안되는거 같네요. 다음 페이지에서 설치할수 있습니다.

http://file.mix1009.net/opensearch/

아이콘은 Firefox 2.0에 포함된 네이버 검색에서 그대로 복사했습니다.

만들때 아래 URL을 참고했습니다:

http://developer.mozilla.org/en/docs/Creating_OpenSearch_plugins_for_Firefox

FIrefox에서 Ctrl-k 누르면 검색을 바로 할수 있고, 여기서 Ctrl-Up, Ctrl-Down 누르면 설치된 검색사이트를 선택할수 있습니다.

Mind Recursion (2005)

mix1009-mind recursion.mp3     

오랜만에 음악 하나 올리게 되는군요. 항상 마무리가 제일 어렵네요. 아이디어가 떠오를때 잠깐 녹음했다가, 한참후에 마무리하려니 생각처럼 잘 안되네요. 마음에 안드는 부분을 다시 녹음하려고 했었는데, 결국 다 삭제하고 원본으로 믹싱해버렸습니다. 기타, 베이스 직접 녹음했고 나머지는 미디입니다.

2005년 9월 녹음
2007년 5월 믹싱

GNU autoconf, automake

오픈소스 프로그램들은 대부분 리눅스에서 아래처럼 컴파일할수 있습니다.

$ ./configure
$ make
$ sudo make install

configure 스크립트를 실행하여 시스템에 맞게 컴파일 되도록 Makefile을 생성해줍니다. 유닉스가 종류가 많고 다양하기 때문에 그 모두를 Makefile 하나로만 지원하기는 어렵습니다. autoconf와 automake를 이용하면, 자동으로 타켓 시스템을 분석해주고, Makefile을 자동으로 만들어줍니다. configure를 실행하면 Makefile과 config.h 파일이 생성되는데, config.h에 시스템에서 제공되는 기능에 대해서 매크로로 정의를 해줍니다. 소스 파일에서 config.h를 include하고 매크로에 따라 특정 기능이 지원 안되면 다른 방법으로 구현하여 프로그램의 이식성을 높일수 있습니다. autoconf와 automake를 쓰는것만으로 프로그램 자체의 이식성이 높아지지는 않습니다.

저는 주로 FreeBSD에서 ports를 사용하고, 개발도 Berkeley Parallel make를 이용하기 때문에 autoconf와 automake를 배울 필요를 못느꼈었습니다. 사실 cross platform이 중요하지 않고 소스의 구조가 복잡하지 않으면 make의 기본 기능으로도 큰 불편없이 개발할수 있습니다. 요즘 빌드 시간도 짧으니 dependency도 크게 신경안쓰고 항상 make clean;make 해도 개발 자체가 크게 느려지지는 않더군요. 제가 유닉스에서 개발하는 프로그램은 대부분 5000 라인 이하의 데몬 프로그램들입니다. 거의 C에 가까운 C++ 프로그램이죠.

요즘 AMD64 서버들에 리눅스를 깔고 서버 프로그램들을 리눅스와 프비에서 동시에 지원하기 위해서 작업을 하고 있습니다. 처음에는 Makefile.linux를 따로 만들었는데 Makefile을 따로 관리하기 보다는 autoconf와 automake를 이용하면 더 편할것 같아서, autoconf, automake를 적용해보고 있습니다.

configure 스크립트가 만들어지기 까지 여러가지 명령을 순서대로 실행해야 해서 처음에는 복잡하게 느껴지지만 조금 익숙해지면 어렵지 않습니다. 사용자가 직접 만들어줘야하는 파일은 Makefile.amconfigure.ac 두개입니다. 나머지는 부수적인 파일들로 autoconf, automake 동작에 큰 영향을 주지 않습니다. (AUTHORS, README, COPYING 등)

libevent를 이용한 echo server를 C++로 예제 프로젝트로 만들어봤습니다.

Makefile.am에서는 실행파일과 실행파일을 만들기 위한 소스를 지정해줍니다. 아주 간단합니다.

bin_PROGRAMS = echo_server
echo_server_SOURCES = echo_server.cpp

configure.ac는 조금 복잡합니다. 프로그램 이름, 버전, 이메일 등을 적어주고 사용하는 라이브러리에 대해서 기술하도록 되어있습니다. libevent에 관련한 내용(파란색)이 대부분인데 이 부분은 memcached 에 포함된 configure.ac 파일을 참고했습니다.

AC_INIT(echo_server_libevent, 1.0, mix1009@gmail.com)
AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
AM_CONFIG_HEADER(config.h)

AC_PROG_CXX
AC_PROG_INSTALL

AC_ARG_WITH(libevent,
   AC_HELP_STRING([–with-libevent=DIRECTORY],[base directory for libevent]))
if test “$with_libevent” != “no”; then
   CFLAGS=”$CFLAGS -I$with_libevent/include”
   CXXFLAGS=”$CXXFLAGS -I$with_libevent/include”
   LDFLAGS=”$LDFLAGS -L$with_libevent/lib”
fi

LIBEVENT_URL=http://www.monkey.org/~provos/libevent/
AC_CHECK_LIB(event, event_set, ,
   [AC_MSG_ERROR(libevent is required.  You can get it from $LIBEVENT_URL)])

AC_CONFIG_FILES(Makefile)
AC_OUTPUT

이 외에도 부수적으로 준비해야하는 파일들이 몇개있습니다. NEWS, README, AUTHORS, ChangeLog는 필수적으로 있어야하는 파일들이고, 몇가지는 automake를 실행할때 –add-missing을 인자로 주면 자동으로 만들어줍니다.

제가 사용하는 FreeBSD 6.2 기준으로 설명드리겠습니다. autoconf, automake 여러버전이 설치될수 있는데, 제가 사용한 버전은 아래와 같습니다.

/home/mix1009> pkg_info | grep auto
autoconf-2.59_2     Automatically configure source code on many Un*x platforms
automake-1.9.6      GNU Standards-compliant Makefile generator (1.9)

아래와 같이 실행하면 configure가 생성됩니다.

/home/mix1009/work/echo_server_libevent> ls -l
total 8
-rw-r–r–  1 mix1009  wheel    66 May  8 13:39 Makefile.am
-rw-r–r–  1 mix1009  wheel   650 May  8 13:39 configure.ac
-rw-r–r–  1 mix1009  wheel  3027 May  8 13:39 echo_server.cpp
/home/mix1009/work/echo_server_libevent> aclocal19
/home/mix1009/work/echo_server_libevent> autoheader259
/home/mix1009/work/echo_server_libevent> autoconf259
/home/mix1009/work/echo_server_libevent> touch README AUTHORS NEWS ChangeLog
/home/mix1009/work/echo_server_libevent> automake19 –add-missing
configure.ac: installing `./install-sh’
configure.ac: installing `./missing’
Makefile.am: installing `./INSTALL’
Makefile.am: installing `./COPYING’
Makefile.am: installing `./depcomp’
/home/mix1009/work/echo_server_libevent> ls -l
total 210
-rw-r–r–  1 mix1009  wheel       0 May  8 13:40 AUTHORS
lrwxr-xr-x  1 mix1009  wheel      35 May  8 13:41 COPYING@ -> /usr/local/share/automake19/COPYING
-rw-r–r–  1 mix1009  wheel       0 May  8 13:40 ChangeLog
lrwxr-xr-x  1 mix1009  wheel      35 May  8 13:41 INSTALL@ -> /usr/local/share/automake19/INSTALL
-rw-r–r–  1 mix1009  wheel      66 May  8 13:39 Makefile.am
-rw-r–r–  1 mix1009  wheel   17475 May  8 13:41 Makefile.in
-rw-r–r–  1 mix1009  wheel       0 May  8 13:40 NEWS
-rw-r–r–  1 mix1009  wheel       0 May  8 13:40 README
-rw-r–r–  1 mix1009  wheel   31538 May  8 13:40 aclocal.m4
drwxr-xr-x  2 mix1009  wheel     512 May  8 13:41 autom4te.cache/
-rw-r–r–  1 mix1009  wheel     640 May  8 13:40 config.h.in
-rwxr-xr-x  1 mix1009  wheel  150289 May  8 13:40 configure*
-rw-r–r–  1 mix1009  wheel     650 May  8 13:39 configure.ac
lrwxr-xr-x  1 mix1009  wheel      35 May  8 13:41 depcomp@ -> /usr/local/share/automake19/depcomp
-rw-r–r–  1 mix1009  wheel    3027 May  8 13:39 echo_server.cpp
lrwxr-xr-x  1 mix1009  wheel      38 May  8 13:41 install-sh@ -> /usr/local/share/automake19/install-sh
lrwxr-xr-x  1 mix1009  wheel      35 May  8 13:41 missing@ -> /usr/local/share/automake19/missing

처음 없는 파일들을 만들기 위한 명령인 touch와 automake 실행할때 –add-missing은 파일이 만들어진 이후로는 인자를 주지 않아도 됩니다. 파일이 모두 만들어진 이후로는 아래와같은 명령만 쳐주면 됩니다.

/home/mix1009/work/echo_server_libevent> aclocal19
/home/mix1009/work/echo_server_libevent> autoheader259
/home/mix1009/work/echo_server_libevent> autoconf259
/home/mix1009/work/echo_server_libevent> automake19


그리고 –add-missing으로 파일을 생성할때 심볼릭 링크로 생성되는데 소스를 배포하거나 CVS에 commit하기 전에, 파일을 직접 복사하는게 좋습니다.

configure 스크립트가 생성되면, 아래와 같이 타켓 시스템에서 컴파일할수 있습니다. configure가 만들어지기 전까지는 하나의 서버에서 실행해서 결과 파일들(configure, Makefile.in, config.h.in 등)을 배포하면 되지만, 이후 부분부터는 타켓 플렛폼에서 실행해야 합니다. configure가 실행될때 타켓 시스템에 특정 기능이 구현되었는지를 검사하고 라이브러리 설치여부등을 검사합니다. 그 결과에 따라서 config.h 파일을 만들어줍니다.

아래처럼 타겟 시스템에서 컴파일하고 설치할수 있습니다.

/home/mix1009/work/echo_server_libevent> ./configure –with-libevent=/usr/local
checking for a BSD-compatible install… /usr/bin/install -c
checking whether build environment is sane… yes
(생략)
checking for event_set in -levent… yes
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands
/home/mix1009/work/echo_server_libevent> make
(생략)
/home/mix1009/work/echo_server_libevent> sudo make install
(생략)

리눅스에서는 configure를 인자없이 실행해도 libevent를 정상적으로 찾는데, FreeBSD에서는 위와 같이 인자로 지정해주어야 찾습니다.

각 툴들을 간략히 설명하겠습니다. aclocal은 autoconf에서 미처 구현되지 못한 부분(?)을 처리하는 것으로 차후에는autoconf에 포함될거라고 합니다. autoheaders는 configure.ac로부터 config.h.in을 만듭니다. automake는 configure.ac와 Makefile.am 내용에 따라서 Makefile.in을 생성합니다.autoconf는 configure.ac에 따라서 configure를 생성합니다. configure는 config.h.in과Makefile.in을 기준으로 시스템 정보를 분석하여 Makefile과 config.h를 만들어줍니다.

각 프로그램의 입출력 파일들을 살펴보면 아래와 같습니다. http://sourceware.org/autobook/의 Appendix C를 참고했습니다.

configure.ac — aclocal —> aclocal.m4
configure.ac, (optional aclocal.m4) — autoheaders —> config.h.in
configure.ac, Makefile.am — automake —> Makefile.in
configure.ac, (optional aclocal.m4) — autoconf —> configure
Makefile.in, config.h.in — configure —> Makefile, config.h

처음에는 복잡하게 느껴지지만, 한번 사용해보면 Makefile.am에서 소스 파일만 추가하고, 사용하는 라이브러리가 추가될때 configure.ac에서 라이브러리 지정만 하면 되기 때문에 사용에 어려움이 없습니다. 도입후에 여러 플렛폼에서 컴파일하면서 발생하는 문제점들을 하나씩 해결해 나가면 프로그램의 이식성을 높일수 있습니다. 문제가 발생하는 함수등을 configure.ac에서 지정하고 config.h에 정의된 매크로에 따라서 소스에서 적절히 처리하면 이식성이 차차 높아지게 됩니다.

autoconf, automake 실행하기 전 소스입니다. libevent를 이용한 echo 서버 예제 입니다. 소스 자체의 완성도는 높지 않습니다. configure.ac, Makefile.am, echo_server.cpp, COPYING 네개의 파일이 포함되어있습니다.

1214426580.tgz

ocaml-event를 이용한 echo server

ocaml-event(libevent ocaml wrapper)를 이용하여 echo server를 OCaml 언어로 작성해 봤습니다. libevent C API에 익숙해서 ocaml 답지 않게 짜진거 같은 느낌도 들지만 워낙 프로그램이 간단하다보니 크게 틀려질 부분은 없어보이는군요. 다른건 문제가 없었는데 Makefile에서 ocamlopt로 native 컴파일할때 C 라이브러리가 링크가 안되서 약간 헤맸고, printf 후에 flush stdout을 하는 부분이 있는데, stdout이 Unix 네임스페이스에 있는 stdout으로 인식이 되어 컴파일이 안되더군요. 그래서 Pervasives.stdout으로 지정하니 컴파일 잘되네요.

아래는 echo_server_event.ml 소스입니다.

[CODE type=ocaml]
(*
* echo server using libevent
*
* Copyright (c) 2007 Chun-Koo Park
* All rights reserved.
*)

open Unix

let rec sock_write sock buf offset = function
  | 0 -> ()
  | len ->
     let nwritten = write sock buf offset len in
       sock_write sock buf (offset + nwritten) (len – nwritten)

let echo_callback event fd event_type =
  let buf = String.create 64 and nread = ref 1 in
   while !nread > 0 do
     nread := read fd buf 0 64;
     sock_write fd buf 0 !nread;
   done;
   if !nread > 0 then
     Libevent.add event None
   else
     Printf.printf “connection closed\n”;
     flush Pervasives.stdout

let accept_callback event fd event_type =
  let asock, addr = accept fd in
  let evnew = Libevent.create () in

  Libevent.set evnew asock [Libevent.READ] false (echo_callback evnew);
  Libevent.add evnew None;

  Libevent.add event None

let tcp_server_sock port =
  let ssock = socket PF_INET SOCK_STREAM 0
  and addr = inet_addr_any in
   bind ssock (ADDR_INET (addr, port));
   setsockopt ssock SO_REUSEADDR true;
   listen ssock 5;
   ssock

let _ =
  let listenport = 2007 in
  let acceptfd = tcp_server_sock listenport in
  let acceptev = Libevent.create () in

  Libevent.set acceptev acceptfd [Libevent.READ] false (accept_callback acceptev);
  Libevent.add acceptev None;

  Libevent.dispatch ()
[/HTML][/CODE]

ocaml-event를 이용한 echo 서버와 thread와 fork로 짠 echo 서버의 Makefile입니다. ocaml findlib를 이용했습니다. ocaml findlib에 대해서 얼마전에 올린글을 참고하세요.

[CODE type=makefile]
# Makefile
#
# Copyright (c) 2007 Chun-Koo Park
# All rights reserved.

OCAMLC=ocamlfind ocamlc
#OCAMLC=ocamlfind ocamlopt

EXEC=echo_server_fork echo_server_thread echo_server_event
all: $(EXEC)

echo_server_fork: echo_server_fork.ml
   $(OCAMLC) -o $@ -package unix -linkpkg $@.ml

echo_server_thread: echo_server_thread.ml
   $(OCAMLC) -o $@ -thread -package “threads unix” -linkpkg $@.ml

echo_server_event: echo_server_event.ml
   $(OCAMLC) -o $@ -package “libevent unix” -linkpkg $@.ml -ccopt -L/usr/local/lib -cclib -levent

clean:
   rm -f *.cmo *.cmi *.cmx *.o $(EXEC)

# vi:set noet:
[/HTML][/CODE]

thread 사용할때는 -thread 옵션을 주고, ocamlopt로 native 컴파일할 경우를 위해서 -cclib로 libevent C 라이브러리를 지정해주었습니다. -cclib event로 지정하면 라이브러리를 찾을거라 생각했는데 -cclib -levent 형태로 라이브러리 지정을 해야되더군요. ocamlc로 byte compile할때는 -cclib 를 지정하지 않아도 됩니다.

Ocaml로 작성한 세가지 echo server 소스입니다. ( libevent / fork / thread )

1402904692.tgz

FreeBSD blackhole 기능

FreeBSD에서 DoS 공격을 근본적으로 막을 방법은 뾰족히 없지만 좀 느리게 만드는 방법입니다. PF(Packet Filter)로 잘 설정하고 싶지만 연결을 많이 유지하는 서버에서 PF가 성능저하가 있는거 같아서 일단 다른방법으로 대처했습니다.

man blackhole 하면 자세한 설명 볼수 있으며,

net.inet.tcp.blackhole와 net.inet.udp.blackhole을 세팅하면 됩니다.

# sysctl -w net.inet.tcp.blackhole=1
# sysctl -w net.inet.udp.blackhole=1

포트 스캔할때, 닫혀있는 포트에 대해서는 리셋(RST) 패킷이 날라가는데 이걸 아예 안보내게 합니다. tcp.blackhole의 경우 1이면 연결(SYN) 패킷에 대해서 RST를 안보내며, 2로 세팅하면 모든 패킷에 대해서 RST를 안보냅니다. UDP는 따로 연결 패킷이 없기 때문에 1로 세팅하면 닫혀있는 포트로 오는 패킷에 대해서 RST 응답을 안보내고 무시합니다.

포트스캔을 하면 없는 포트로 많은 패킷이 오기 때문에 RST 패킷이 많이 나가게 됩니다. 이럴 경우 시스템에 부하를 일으킬 소지가 있으므로, FreeBSD에서는 RST를 보낼때 1초에 200개의 RST 패킷만 보내도록 제한이 되어있습니다. 이를 넘을 경우 아래와 같은 경고 메시지가 dmesg 등에 나타납니다.

Limiting closed port RST response from 286 to 200 packets/sec
Limiting closed port RST response from 463 to 200 packets/sec

여기 IP 정보가 나오지 않기 때문에 정보를 보기 위해서는

# sysctl -w net.inet.tcp.log_in_vain=1
# sysctl -w net.inet.udp.log_in_vain=1

하지만 이렇게 하면 로깅되는 정보가 너무 상당히 때문에 시스템에 더 큰 부하를 일으킬 가능성이있습니다. 공격을 당하는 서버에서는 잠깐동안 기능을 켰다가 끄도록 아래처럼 실행하세요.

# sysctl -w net.inet.tcp.log_in_vain=1; sleep 5; sysctl -w net.inet.tcp.log_in_vain=0

위에 처럼 서버(SERVER_IP)에서 돌리고, 원격 서버(SCANNER_IP)에서 nmap을 돌려봤습니다. 스캔 당하는 SERVER_IP에서는 blackhole 기능은 끈상태입니다.

Connection attempt to TCP SERVER_IP:25 from SCANNER_IP:38947 flags:0x02
Connection attempt to TCP SERVER_IP:1723 from SCANNER_IP:38947 flags:0x02
Connection attempt to TCP SERVER_IP:3389 from SCANNER_IP:38947 flags:0x02
… (생략)
Limiting closed port RST response from 233 to 200 packets/sec
… (생략)
Limiting closed port RST response from 262 to 200 packets/sec
… (생략)
Limiting closed port RST response from 279 to 200 packets/sec

blackhole=1로 설정한 상태에서는 Limiting closed port… 메시지가 나타나지 않고, nmap의 실행속도가 많이 느려집니다. 글쓰면서 실행해봤는데 몇분이 지나도 끝나지가 않는군요. 답답해서 nmap 실행창에서 엔터를 쳤더니 다음과 같은 메시지가 나오네요. ㅎㅎ 콘솔 프로그램인데 이런거까지 신경써주는군요. ^^

Stats: 0:06:49 elapsed; 1 hosts completed (1 up), 1 undergoing SYN Stealth Scan
SYN Stealth Scan Timing: About 72.17% done; ETC: 01:59 (0:02:37 remaining)

한가지 문제가 blackhole 기능을 켜두면 원격에서 서버 모니터링할때 서버가 죽어도 응답이 바로 오지않는 문제가 있는데, PF를 통해서 포트에 따라서 선택적으로 blackhole 기능처럼 RST 보내지 않도록 설정하는것이 더 좋을거 같네요.

ocaml findlib : ocamlfind

OCaml 언어에 예전부터 관심이 많았었는데, 오랜만에 다시 보니 새롭군요. 그전에 배우면서 CVS에 올렸던 날짜를 보니 2002년이네요. 5년이 지났지만 실전에 아직 OCaml를 쓸 기회가 없다보니 조금 안쓰다보면 까먹어서 자꾸 다시 배우게 되네요.

그 전에 배울때는 OCaml에 포함된 기본 컴파일러만 make와 병행해서 사용했었는데, ocaml-event 라이브러리를 깔다보니 ocamlfind라는 실행파일을 통해서 인스톨하게 되어 있더군요. ocaml-event는 libevent의 ocaml에서 사용할수 있도록 하는 라이브러리입니다. FreeBSD ports중에 /usr/ports/devel/ocaml-findlib를 깔고, ocaml-event는 수동으로 소스를 컴파일하여 인스톨했습니다. /usr/ports/devel/ocaml-event 에 있지만 여기서 인스톨하면 ocamlfind에 등록이 이상하게 안되더군요…

ocaml-findlib를 깔면 중심 실행파일이 ocamlfind인데, 이 파일이 상당히 다양한 기능들은 가지고 있습니다. Ocaml 라이브러리/프로그램 인스톨, 언인스톨, 컴파일, 소스에서 문서 추출, 라이브러리 종속성 조회, 라이브러리 브라우징 등의 기능을 제공하며, 브라우징의 경우 Tk를 이용하여 X11 환경에서만 실행됩니다. 자세한 것은 man 페이지나 findlib user guide 문서를 참고하세요.

저는 컴파일 할때 ocamlc나 ocamlopt를 바로 호출하지 않고 ocamlfind를 통해서 호출한 방법만 설명드리도록 하겠습니다. ocamlc는 자바처럼 byte code 형태로 컴파일되고, ocamlopt는 타겟 CPU에서만 실행되는 native 형태로 컴파일이 됩니다. 한 ocaml 프로그램 내에서 byte code와 native code로 컴파일된것을 링크할수 없으므로 Makefile를 이용한다면 이를 따로따로 관리해야합니다. ocamlfind를 이용하면 cmo(byte code object)나 cmx(native object)를 지정하지 않아도 되서 Makefile의 크기가 많이 줄어드는것 같네요. 라이브러리 지정도 cma, cmxa를 따로 지정하지 않으며, 또한 라이브러리의 종속성을 자동으로 판단하여 라이브러리 링크 순서도 자동으로 판단해주는거 같습니다.

예전에 짰던 echo_server fork버전과 thread버전의 Makefile.old와 변경된 Makefile.new을 비교해보시면 ocamlfind가 유용하다는걸 느끼실수 있을겁니다. 그리고 Makefile.old는 native로 컴파일할려면 많은 부분 고쳐야하지만, Makefile.new에서는 한줄만 바꾸면 됩니다.

[CODE type=make]
# Makefile.old
# Copyright (c) 2002 Chun-Koo Park

OCAMLC=ocamlc
OCAMLCFLAGS=-thread
.SUFFIXES: .ml .mli .cmo .cmi

LIBS=unix.cma

EXEC=echo_server_fork echo_server_thread
all: $(EXEC)

clean:
   rm -f *.cmo *.cmi *.cmx *.o $(EXEC) .depend

.mli.cmi:
   $(OCAMLC) $(OCAMLCFLAGS) -c $<

.ml.cmo:
   $(OCAMLC) $(OCAMLCFLAGS) -c $<

echo_server_fork: $@.cmo $(OBJS)
   $(OCAMLC) -o $@ $(OCAMLCFLAGS) $(LIBS) $@.cmo

echo_server_thread: $@.cmo $(OBJS)
   $(OCAMLC) -thread -o $@ $(OCAMLCFLAGS) unix.cma threads.cma $@.cmo

# vi:set noet:
[/HTML][/CODE]

[CODE type=make]
# Makefile.new
# Copyright (c) 2007 Chun-Koo Park

OCAMLC=ocamlfind ocamlc
#OCAMLC=ocamlfind ocamlopt

EXEC=echo_server_fork echo_server_thread
all: $(EXEC)

echo_server_fork: echo_server_fork.ml
   $(OCAMLC) -o $@ -package unix -linkpkg $<

echo_server_thread: echo_server_thread.ml
   $(OCAMLC) -o $@ -thread -package “threads unix” -linkpkg $<

clean:
   rm -f *.cmo *.cmi *.cmx *.o $(EXEC) .depend

# vi:set noet:
[/HTML][/CODE]

위 Makefile 들은 FreeBSD에서 BSD Parallel Make에서 테스트된 파일들입니다. 마지막 줄은 vim에서 탭을 자동으로 공백으로 확장하지 않도록 하는 명령입니다. (noexpandtab)

findlib에 포함된 make_wizard를 사용하면 위저드 형태로 7단계로 Makefile를 만들어줍니다. 생성되는 Makefile이 상당히 길고, 생성된 Makefile은 위저드로 편집이 불가능하기 때문에 사용이 꺼려지긴 합니다. 하지만, 참고할만한 마땅한 Makefile이 별로 없었는데 소스만 봐도 큰 도움이 될듯하네요. 다음은 make_wizard를 실행한 화면입니다.

UNIX Socket Model & libevent

회사에서 libevent에 대한 스터디를 하기로 했습니다. 서버 프로그램 개발시 FreeBSD에서 KQueue와 Solaris에서 /dev/poll 개발 경험이 있습니다. 최근에는 리눅스 서버에서 libevent를 이용하여 서버 프로그램도 제작했습니다. OS별로 각기 API가 조금씩 틀려서 이식성이 떨어져서 이제는 libevent로 통일해서 개발하려고 합니다.

여기서 설명하는 소켓 모델은 여러 소켓을 한 프로세스에서 동시에 처리하기 위한 방식으로, 주로 단일 쓰레드 방식의 어플리케이션에서 여러 소켓 연결(세션)을 처리하기 위해서 사용합니다. 소켓 뿐 아니라 파일 IO 등을 같이 처리할수 있으며, File Descriptor를 통해서 액세스 합니다.

select와 poll이 유닉스 표준으로 정해져 있는 함수이며, 프로토타입은 다음과 같습니다.
[CODE type=cpp]
int select(int nfds, fd_set *r_fds, fd_set *w_fds, fd_set *exceptfds, struct timeval *timeout);
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
[/HTML][/CODE]

select와 poll 모두 file descriptor 리스트를 인자로 넘기고, timeout 동안 기다립니다. Timeout 시간 이전에 전달한 fd 중에 이벤트가 있으면 리턴합니다.

단일 쓰레드 소켓 서버 프로그램의 pseudo code는 대충 아래와 같습니다.
[CODE type=cpp]
while (1) {
  setup_fd_list();
  select_or_poll();
  handle_fd_events();
}
[/HTML][/CODE]

Select는 fd의 리스트를 비트 단위로 인코딩해서 보냅니다. FD_SETSIZE를 정의해서 bitset의 길이를 정해줄수 있지만, 시스템에 따라서 제약이 있는 경우도 있다고 합니다. Poll은 struct array로 fd 리스트를 전달하여 길이에는 제한이 없지만, array의 중간 fd가 지워지면 처리가 좀더 복잡해집니다. Select와 poll 모두 호출할때마다 fd 리스트 전체를 전달하기 때문에 연결수가 많아지면 비효율적이 됩니다. 유저스페이스에서 커널로 전달하는 데이타가 연결수에 비례하여 선형적으로 늘게됩니다.

아래는 libevent 홈페이지에 있는 소켓 모델간의 비교 그래프입니다. select와 poll 모두 연결수가 많아질수록 처리시간이 비례하여 증가하는것을 볼수 있습니다.

이러한 문제점을 해결하기 위해서 OS별로 다른 소켓 모델을 개발했습니다. BSD에서는 Kqueue, Solaris에서는 /dev/poll, Linux에서는 epoll 등이 있습니다. 이 모델들은 fd 리스트 전체를 항상 전달하지 않고, 한번 등록한 fd는 호출할때 다시 등록하지 않아도 등록된 상태로 되어 커널 쪽에서 관리가 됩니다. fd를 등록하고, 나중에 삭제할 때나 이벤트가 있을 때 까지 등록된 상태로 남아있어, 커널과 유저스페이스 간의 통신 오버헤드를 대폭 줄였습니다. 따라서 연결이 늘더라도 이를 커널쪽에서 효율적으로 처리하여 처리시간이 크게 늘지 않도록 구현되어 있습니다. 위에 그래프를 보면 Kqueue와 epoll은 연결수가 늘어나도 처리시간은 거의 일정한 것을 관찰할수 있습니다.

하지만 이러한 소켓 모델들은 표준화 되지 않고, 구현 범위도 조금씩 차이가 나기때문에 이식성이 좋은 소켓 서버를 구현하는 것은 쉽지 않습니다. Libevent는 이러한 소켓 모델들을 모두 지원하는 라이브러리로 시스템에 따라서 제일좋은 소켓 모델을 선택하여 동작하도록 되어 있습니다.

참고 URL: http://www.kegel.com/c10k.html

c10k 문제(10000 연결 문제)라고 하여 연결을 동시에 많이 유지하는 서버 프로그래밍에 대해서잘 정리되어 있는 페이지입니다.

스터디 주제가 이미 어느정도 아는 내용으로 정해졌고, 새로 오신 분들이 유닉스 경험이 많지 않아서 스터디하면서 나름대로 새로운 내용들을 공부해봐야겠습니다. 일단 개인 관심언어인 Ocaml로 ocaml-event를 이용해서 서버 프로그램을 작성해보고, Make대신 automake와 autoconf 공부도 좀 해야겠네요. 워낙 BSD Parallel Make에 익숙해져서 요즘 리눅스에서 GNU Make 쓰는데 당황스럽더군요. –;