Emacs 를 사용하다 보면 언제나 속 터지는 일이 있습니다. 바로 폰트!! 이 폰트 때문에 날리는 시간과 검색에 몸바친 열정등 에너지 소모가 엄청납니다.  더구나 요즘 우분투를 업그레이드 하면 시스템에 등록되어 있는 폰트를 xfontsel 에서 읽을 수가 없기 때문에 '구리구리한' 폰트를 쓸 수밖에 없지요.

저는 프로그래머로서 '뽀대'를 중요시 하는데 차마 그런 폰트로 개발을 할 수가 없지요!!

그래서 제가 해결한건 아니고요. 절친하고 리눅스 포함 시스템 쪽에 도사인 Koei군에게 도움을 받아서 폰트를 설치하게 됐습니다.

매번 업그레이드 하는 우분투를 따라서 Koei 군에게 언제나 신세 질 수도 없고 해서 간단하게 작성하는 법을 정리했습니다.

1. Xgl 구동시 기본 폰트를 읽어서 장착시키기.

 /etc/X11/Xsession.d/98xserver-xgl_start-server

위 파일을 읽어서 다음에 나오는 글 상자처럼 고칩니다.

# This file is sourced by Xsession(5), not executed.

XGL_START=/usr/share/xserver-xgl/Xgl-session
XGL_DISPLAY=:1
XGL_FONTPATH="-fp $(grep -i fontpath /etc/X11/xorg.conf | egrep -v "[:space:]*#" | sed "s/.\+\"\(.\+\)\"/\1,/g" | xargs echo | sed "s/\ //g" | sed "s/,\$//")"
XGL_OPTS="-nolisten tcp -fullscreen -br +xinerama $XGL_FONTPATH"

if [ -n $XDG_CONFIG_HOME ] ; then
    KILLSWITCH=$HOME/.config/xserver-xgl/disable
else
    KILLSWITCH=$XDG_CONFIG_HOME/xserver-xgl/disable
fi

if [ -x $XGL_START ] && [ ! -e $KILLSWITCH ]; then
        STARTUP="$XGL_START $XGL_DISPLAY $XGL_OPTS --execute $STARTUP"
fi

XGL_FONTPATH 부분을 추가한 것입니다.

2. Monaco 폰트 등록하고 이를 시스템에 알려주는 작업을 합니다.

우분투 에서 Monaco 폰트 쓰게 해주는 Tip

defoma-font 명령을 이용해서 등록해준다. (ex: defom-font register-all defoma-hints-monaco )
defoma-hints-monaco 는 코에이 군이 작성 (아래 삽입된 것이 그 내용)
 category truetype
begin /usr/share/fonts/truetype/monaco/Monaco.ttf
Family = Monaco
FontName = Monaco
Encoding = Unicode
Location = English
Charset = ISO8859-1 ISO8859-9 ISO8859-15 ISO10646-1
UniCharset = ISO8859-1 ISO8859-9 ISO8859-15
GeneralFamily = Typewriter
Weight = Medium
Width = Fixed
Shape = NoSerif Upright
Foundry = Apple
Priority = 20
end

/usr/share/fonts/truetype/monaco/ 폴더를 만들고 폰트파일 복사해서 적용해준다



링크

('' 위부분이 링크입니다. 클릭하시면 MSDN 이 연결됩니다.

최근에 열심히 알바 뛰고 있는 확장형 C++ 입니다. 국내에 레퍼런스라고는
MSDN 이 고작이더군요 하지만 부지런히 하다보니 대충 알만 합니다. ^^;

MSDN 을 10여년 가까이 접해오다 보면 한가지 특징이 있습니다. 중간에 링크가
깨질 수가 있다는 것!! 그래서 예제 코드를 남깁니다.

아래는 관리되는 코드를 비관리 코드 (Native) 의 배열로 변경하는 과정입니다. 중간의
pin_ptr 이 포인트 입니다.

// load_unmanaged_resources_into_Byte_array.cpp
// compile with: /clr
using namespace System;
void unmanaged_func( unsigned char * p ) {
for ( int i = 0; i < 10; i++ )
p[ i ] = i;
}

public ref class A {
public:
void func() {
array<Byte> ^b = gcnew array<Byte>(10);
pin_ptr<Byte> p = &b[ 0 ];
Byte * np = p;
unmanaged_func( np ); // pass pointer to the block of CLR array.
for ( int i = 0; i < 10; i++ )
Console::Write( b[ i ] );
Console::WriteLine();
}
};

int main() {
A^ g = gcnew A;
g->func();
}


반대되는 비관리되는 배열을 관리되는 코드로 바꾸는 부분입니다.

// load_unmanaged_resources_into_Byte_array_2.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

#include <string.h>
int main() {
   char buf[] = "Native String";
   int len = strlen(buf);
   array<Byte> ^byteArray = gcnew array<Byte>(len + 2);
  
   // convert any native pointer to IntPtr by doing C-Style cast
   Marshal::Copy( (IntPtr)buf, byteArray, 0, len );
}

기존의 svn 과 연동해서 작업하는데는 전혀 문제가 없습니다. 즉 기존의 소스를 가져오는 방식은
svn up svn://wiki.nully.co.kr/source 이런식으로 가져오고 있습니다. 이 방식은 ccnet 과 ccservice 방식에서 전부 동일하게 돌아갑니다..

문제는 svn up https://wiki.nully.co.kr/source 방식으로 가져오는 것이 문제인데 (즉 SSL을 통해서 소스를 가져오는 경우)  이는 인증서를 가져오는 문제가 있어서 에러가 발생하는데 , 프롬프트가 나오기 때문에 기존의 프로젝트를 돌리는 사용자와 서비스가 돌아가는 계정이 불일치 하기 때문에 발생하는 문제입니다.

그래서 기존의 Cruisecontrol.NET 서비스를 사용자를 프로젝트를 구동하는 유저로 로긴하게 바꾸어 주면 모든 것이 해결됩니다.

그림은 추후에 추가할 예정임

예전에 제가 국내 거대 SI 업체와 같이 일할때 생각이 납니다. 그 시절믜
팀구성은 나름(!) 잘 짜여졌다고 업체가 자랑하는 팀 구조로 되어
있었습니다. PM - Project Manager - 와 6명의 개발팀으로 이루어져
있고, 소스 레파지토리로 VSS (Visual Source Safer) 를 사용했고,
6명의 개발팀은 1명의 PL (Program or Project Leader) 과 5명의 개발자들이
포진해 있는 구조였습니다.

 한명의 PL은 각 개발자가 개발한 소스를 취합해서 하나의 단위 기간당
릴리즈 하는 임무를 맡고 있으며 PM 과 개발자의 중간 위치에
해당했습니다. 상당히 중요한 업무였지요, 실제로 바쁘기도 하고요.
보통 5명의 개발자들의 개인 컴퓨터가 개발 머신에 해당하고 PL 의 컴퓨터가 요즘
말하는 Stage Server 의 기능을 대신 했던 구조였습니다. 즉 각각의
개발자가 레파지토리에 소스를 올리면 PL 이 전부 소스를 내려 받아서 컴파일
합니다. 에러가 발생하면 책임자에게 수정하라고 요청을 해서 다 될때까지
PL 이 빌드 진행을 맡지요. 그래서 중간 릴리즈가 되면 VSS 에서 레파지토리
백업을 받고 그것을 날짜 태그를 붙여서 저장합니다.

 처음에는 팀이 잘 돌아갑니다. 이때 저는 혼자만 개발을 주로 하다가 팀단위로
개발은 처음 하게 된데다가, 또 팀이 완벽(?)하게 돌아가는 모습을 보고 큰 감동을
받았습니다. 그런데 지금 생각해보면 처음에는 잘 돌아갈 수밖에
없었습니다. PM 이 고객과 협의 사항이 거의 없었고 , 개발자들도 슬슬
개발하는 단계니 적당히 적당히 릴리즈 전날만 가볍게 새주면 슬슬 잘
돌아갔습니다. 소스데이타가 릴리즈 할때마다 백업데이타로 몇백메가씩
늘어나는 것만 제외하면 ( 이당시 VSS 는 태그 기능이 없었습니다)
이상적인 팀이라고 생각할만큼 좋았습니다. 사실 고통에 익숙해져 있었던 시절이라
날을 안새는게 이상하게 여겼지요.

 결국 개발 중간부터 슬슬 일이 발생하기 시작합니다. PM 은 고객한테
깨졌다면서 고객 요구 사항을 가져오면서 PL 을 들볶기 시작합니다. PL 은
대응 문서 만드느라고 정신이 없어서 중간 중간 체크아웃 해서 릴리즈 본을
만드는 것을 한두번씩 거르기 시작합니다. 5명이 개발한 프로그램은 그
자신의 로컬 컴퓨터에서만 잘 돌아갑니다. 하지만 합칠려고만 하면 왜 그리
에러가 튀어나오는지 개발 시간보다 그거 잡는 시간이 더 길어집니다.릴리즈
시간이 다가오면 정형성 맞추느라 이제 날을 새도 데모 일정을 연기할
수밖에 없습니다.

 그리고, 납기일이 다가오는 막판 온갖 도핑약으로 도배를 해 가면서
죽음의 3개월을 보내고 딜레이 되서 PM 이 온갖 양주로 고객을 잠수시켜서
얻어낸 시간만큼의 2개월쯤을 또한 죽음의 세월로 보냅니다.

결국 큰 프로젝트에서 문제가 되는 것은 여러 개발자들이 만든 모듈을
합치는 부분입니다. 이 부분의 부하가 커지기 시작하면 프로젝트는 대책
없이 흘러가기 시작합니다. 저의 단한번의 큰 프로젝트는 그렇게 완전히
실패하고 말았습니다. 인간적으로 PL 이 할 일이 너무 많았지요.

CI 는 이 PL 이 하게되는 가장 중요한 업무인 모듈통합, 빌드 진행을 맡아서
하는 시스템 프로세스 입니다. 실수하기 쉽고 지겨워지기 마련인 단순한
작업을 기계가 대신 해주는 시스템이지요.

개발자가 소스 레파지토리로 업데이트를 하는 순간 모듈 통합 빌드가
시작되는 구조라고 생각하시면 제일 간단합니다. 그리고 그 결과를 웹이나
트레이를 통해서 쉽게 알 수 있는 정말 효율적인 시스템입니다.

역사에 가정이 없다고들 하는데 , 만약 제가 그 시절로 돌아갈 수 있다면
당연하게 이 시스템 도입을 강력히 주장하겠습니다. 제 일정에 맞게 프로젝트를
완수하면서도 성공시킬 자신이 있습니다.

Linux

;; theme 설정
(require 'etheme)
(etheme-apply-theme "cinsk")

;; ruby 모드 설정
(require 'ruby-mode)
(require 'inf-ruby)
(require 'ruby-electric)
(setq auto-mode-alist (cons '("\.rb$" . ruby-mode) auto-mode-alist))

;; ecb 설정
(add-to-list 'load-path
         "/usr/share/emacs-snapshot/site-lisp/ecb/")
(require 'ecb)

;; text mode 할때마다 auto fill 모드로 전환
(add-hook 'text-mode-hook 'turn-on-auto-fill)

;; emacs-rails 설정
(setq load-path (cons "~/.emacs.d/emacs-rails" load-path))
(defun try-complete-abbrev (old)
(if (expand-abbrev) t nil))

(setq hippie-expand-try-functions-list
'(try-complete-abbrev
try-complete-file-name
try-expand-dabbrev))

(require 'rails)
)
;; 3 벌식 키보드 설정
(defun set-my-keyboard()
 "my keyboard setup"
(set-input-method "korean-hangul3")
(global-set-key [?\S- ] 'toggle-input-method)
)
(when enable-multibyte-characters
(set-my-emacs-coding-system)
(set-my-keyboard)
)
;; ecb 외관 설정
(custom-set-variables
  ;; custom-set-variables was added by Custom.
  ;; If you edit it by hand, you could mess it up, so be careful.
  ;; Your init file should contain only one such instance.
  ;; If there is more than one, they won't work right.
 '(ecb-layout-name "left14")
 '(ecb-options-version "2.32")
 '(transient-mark-mode (quote identity)))
(custom-set-faces
  ;; custom-set-faces was added by Custom.
  ;; If you edit it by hand, you could mess it up, so be careful.
  ;; Your init file should contain only one such instance.
  ;; If there is more than one, they won't work right.
 )


Window

(require 'cl)
(defun set-my-emacs-coding-system()
"my coding system"
 
;; 폰트 설정
(create-fontset-from-fontset-spec "-*-Monaco-*-*-*-*-13-*-*-*-*-*-fontset-term16,
ascii:-*-Monaco-normal-r-normal-*-13-*-*-*-*-*-*-*,
korean-ksc5601:-*-굴림체-normal-r-normal-normal-13-*-96-96-c-*-KSC5601.1987-*")
(set-face-font 'default "fontset-term16")

;; lisp 프로그램 설정
(set-variable 'inferior-lisp-program
  "C:/Progra~1/GCL-2.6.1/bin/gcl1.bat")
(autoload 'fi:common-lisp "fi-site-init" "" t)

(set-language-environment "korean")
;; theme 설정
(setq load-path (cons (expand-file-name "~/.emacs.d/") load-path))
  (require 'etheme)
(etheme-apply-theme "cinsk")

;; ecb 를 설치하기 위한 기본적으로 필요한 패키지들
(add-to-list 'load-path "c:/Program Files (x86)/Emacs/site-lisp/speedbar-0.14beta4")
(add-to-list 'load-path "c:/Program Files (x86)/Emacs/site-lisp/eieio-0.17")
(add-to-list 'load-path "c:/Program Files (x86)/Emacs/site-lisp/semantic-1.4.4")
(setq semantic-load-turn-everything-on t)
(require 'semantic-load)
;; ecb 설정
(add-to-list 'load-path "c:/Program Files (x86)/Emacs/site-lisp/ecb-2.32")
;; *ecb-2.32
(require 'ecb)
)
;; 3벌식 설정
(defun set-my-keyboard()
"my keyboard setup"
(set-input-method "korean-hangul3")
(global-set-key [?\S- ] 'toggle-input-method)
)
(defun set-my-win32-coding()
"my windows environment"
(set-w32-system-coding-system 'euc-kr)
(set-clipboard-coding-system 'euc-kr)
)
(when enable-multibyte-characters
(set-my-emacs-coding-system)
(set-my-keyboard)
(set-my-win32-coding)
)

;; nxml support
  (load "~/.emacs.d/nxml-mode-20041004/rng-auto.el")
  (setq auto-mode-alist
        (cons '("
\\.\\(xml\\|xsl\\|rng\\|xhtml\\)\\'" . nxml-mode)
       auto-mode-alist))
  (unify-8859-on-decoding-mode)

;; Ruby support
    (when (eql system-type 'windows-nt) ; Windows
      (when (eq (shell-command "ruby") 0)
        ; !WARNING! ugly hack because of 1.8.4-20
        (setq config-rubyelispdir
       "doc/ruby/ruby-1.8.4/misc" )
        ; get the installation directory
        (setq config-rubydir
       (substring
        (shell-command-to-string
         "ruby -rrbconfig -e 'puts Config::CONFIG[ \"exec_prefix\"]'") 0 -1))
        ; add the emacs lisp directory so emacs acan find it
        (setq load-path (append
           (list (expand-file-name
          config-rubyelispdir config-rubydir))
           load-path))
        ;; define autoloads
        ;; from inf-ruby.el
        (autoload 'ruby-mode "ruby-mode"
          "Mode for editing ruby source files" t)
        (setq auto-mode-alist
       (append '(("
\\.rb$" . ruby-mode)
          ("[Rr]akefile" . ruby-mode))
        auto-mode-alist))
        (setq interpreter-mode-alist (append '(("ruby" . ruby-mode))
          interpreter-mode-alist))
        (autoload 'run-ruby "inf-ruby"
          "Run an inferior Ruby process")
        (autoload 'inf-ruby-keys "inf-ruby"
          "Set local key defs for inf-ruby in ruby-mode")
        (add-hook 'ruby-mode-hook
           '(lambda ()
       (inf-ruby-keys)))))
(custom-set-variables
  ;; custom-set-variables was added by Custom.
  ;; If you edit it by hand, you could mess it up, so be careful.
  ;; Your init file should contain only one such instance.
  ;; If there is more than one, they won't work right.
 '(ecb-layout-name "left14")
 '(ecb-options-version "2.32"))
(custom-set-faces
  ;; custom-set-faces was added by Custom.
  ;; If you edit it by hand, you could mess it up, so be careful.
  ;; Your init file should contain only one such instance.
  ;; If there is more than one, they won't work right.
 )
;; emacs-rails 설정
(setq load-path (cons "~/.emacs.d/rails" load-path))

(defun try-complete-abbrev (old)
(if (expand-abbrev) t nil))

(setq hippie-expand-try-functions-list
'(try-complete-abbrev
try-complete-file-name
try-expand-dabbrev))

(require 'rails)



 

요즘 Emacs 에 푹 빠져 있습니다. 제가 한번 미치면 정신이 없이 빠져 드는 성격이라 ^^ 그래서 emacs 를 이거 저거 건드려 보고 있지만 , 이거참.. 외국 사이트 조차 emacs 에 대한 자료는 없네요 .

그래도 여기저기 두드려 가면서 얻은 지식을 일차적으로 정리할까 생각중입니다. 그중 첫번째인 일단 환경 파일 부터 !!!

; 문자셋 utf-8 쓰기
(require 'cl)
(defun set-my-emacs-coding-system()
"my coding system"
(set-variable 'inferior-lisp-program "/usr/bin/gcl")
(set-language-environment "korean")
(setq-default coding-system 'utf-8)
(setq-default buffer-file-coding-system 'utf-8)
(setq-default buffer-coding-system 'utf-8)
(setq-default file-name-coding-system 'utf-8)
(setq-default senmail-coding-system 'utf-8)
(setq file-coding-system 'utf-8)
(setq sendmail-coding-system 'utf-8)
(setq terminal-coding-system 'utf-8)
(setq shell-coding-system 'utf-8)

; 여기 까지가 emacs 모든 환경에서 utf-8 으로 문자를 인코딩 하게 세팅 해주는 부분입니다.

(setq load-path (cons (expand-file-name "~/.emacs.d/") load-path))
  (require 'etheme)
(etheme-apply-theme "cinsk")
)

; 여기까지는 emacs 에서 구동되는 테마에 관한것 입니다. 아주 이쁘게 보이죠 ^^


(defun set-my-keyboard()
 "my keyboard setup"
(set-input-method "korean-hangul3")
(global-set-key [\S- ] 'toggle-input-method)
)

; 한글 세팅 입니다. 저는 삼벌식 유저라 hangul3 입니다.
; 2벌식 유저는 hangul2 로 세팅하시면 됩니다.
; global 로 시작하는것은 한/영 전환인데 linux 건 , 윈도건 저는 기본이 shift -space 전환이라 있으나 마나한 세팅이더군요 (실제로 작동하지 않는다는 것입니다) 그래서 C-\ 로 변환

(when enable-multibyte-characters
(set-my-emacs-coding-system)
(set-my-keyboard)
)

; 위에 세팅한 함수들을 실행해 주는 부분이라 하겠습니다. 최근에 어떤 기능인지 알았음 ^^

(defun unicode-shell ()
"Execute the shell buffer in UTF-8 encoding.
Note that you'll need to set the environment variable LANG and others
appropriately."
(interactive)
(let ((coding-system-for-read 'utf-8)
(coding-system-for-write 'utf-8)
(coding-system-require-warning t))
(call-interactively 'shell)))

;최근에 무지 고생하면서 알아낸 emacs 에서 shell 모드 사용시 utf - 8 문자열 셋으로 보이게 세팅해 주는 부분입니다.

(unicode-shell)
누리인포스 라는 회사에서 아는 사이니 도와달라고 하는 일을 억지로 맡아서 하게 됐습니다. 그래서 아는 사이가 더 무섭다는 말을 하는 겁니다. 좋게 좋게 대해주면 사람을 무시하게 되는건가요. 하지만 그건 있습니다. 너무 열받아 할 필요는 없다는거지요 , 그 사람은 저에게 빚을 지고 있는것이니까요

일 자체의 컨셉은 간단했습니다. 초기 목적은 AS400 의 사용자 정보를 가져와서 Active Directory 에 그 내용을 입력하는것이였습니다. 그게 AS400 이 Oracle 로 바뀌어서 더 접근성이 용이해졌습니다.

그나마 일이 편했던건 우리가 무료로 일해준다는 성격이 강해서 저쪽이 이거 저거 해달라는 요청을 많이 못하더군요. 과감하게 고객에게 제안도 했습니다. 그래서 상호 싱크 없구, 데몬형태 무시하고 , 단일 어플리케이션 형태로 만들어서 윈도 스케쥴러에 등록해서 호출하는 방식으로 설계를 했습니다. (후후 깡패 SI 업체가 되 봤습니다. +ㅂ+ )

초기에 C++ 로 진행할려고 해서 구글링을 했는데 나오는 소스는 제가 보기에도 짜증나는 방식으로 되어 있더군요. 그래서 같이 짝 프로그래밍을 하는 코에군의 의견에 100% 동의 했습니다. VB.NET 으로 하자!!!

그래서 물었습니다 'VB.NET 해본적 있어? 나는 VB 도 해본적 없는데 이거 첨 해보는 언어인데 괜찮을까?' 그랬더니 코에군의 대답이 걸작입니다. '저두 당연히 해본적이 한번도 없져'  

일순 정적 ...

우아아!!!! 어쩌라고!!! 둘이 지금 첨해보는 언어가지고 상용 어플리케이션을 짜자고? 지금 고객을 우롱하는거지? 이런거 고객이 절대 납득할리가 없어!! 난리를 쳤더니 , 코에가 자기는 C# 을 해본 경력이 있으니 (아주 쬐금 -ㅅ- ) 괜찮을 꺼라고 합니다. 그러자 다시 제가 물었습니다. 'C# 하고 VB.NET 하고 무슨 관계가 ...'  그렇습니다. 알고 있지만 살짝 까먹었.. 전 그쪽은 개 초보 입니다 흑 ㅜ.ㅜ

C#하고 VB.NET 은 유사하다고 합니다. 같은 .NET 계열이라 그런듯 합니다. 하지만 걱정 많은 저는 다시 궁금합니다. 정말 쉽게 할 수 있을까? 오히려 코에군이 저를 다독거립니다. '형 걱정마세요, VB 는 원래 프로그래밍 언어의 창녀라고 불리고 있대요, 그만큼 많은 사람이 쉽게 접할 수 있다는 거겠죠' 음담패설의 영역으로 설명을 해주니 갑자기 힘이 납니다.

그리고 둘이서 정말 간단하게 설계를 했습니다. 설계라고 하기도 창피할 정도입니다 ^^
오라클에서 사용자 정보를 읽어서 액티브 디렉토리 (LDAP 입니다 . 이건 보안회사 시절의 정보가 있으니 나중에 찾아서 업뎃하겠습니다)에 정보를 입력 & 업데이트 하는 것이다. 라고 가볍게 스토리를 정했습니다.

이대로는 코딩이 어려우니까 조금 더 나눌 필요가 있겠군요

  •  오라클에서 사용자 정보를 읽어온다
  •  액티브 디렉토리에 정보를 업데이트한다(입력도 포함)

자 이제 스토리가 2개로 늘어났군요. 음 더 나눌 수가 있겠지만 더 나누는건 별 의미가 없는것 같죠? 이게 2개의 스토리 입니다. 우리는 2개의 스토리를 구현하는겁니다.

두서 없이 썼지만, 이건 코에군과 정말 즐거운 맘으로 쓰잘데기 없는 농담을 주고 받으며 진행하며 정말 초기 결정부터 스토리 나누는데까지 전광석화 였습니다. 두사람이 진행방향, 프로그래밍 언어 선택에 있어서 정말 쾌속이라고 할 정도로 합의가 빨리 되서 의사결정이 빨랐다는게 어떤 진행방향에 추진력을 가했다고 볼 수 있습니다.

그럼 다음에는 첫 세부 스토리를 구현하는것을 보겠습니다.

written by 광이랑

The ADAPTIVE Communication Environment  의 약자가 ACE 이다. 간략하게 기원을 말하면 Douglas C. Schmidts 란 분이 기초를 만들고 Open Source 로 공개를 하자 개발자들이 벌떼처럼 모여들어서 지금의 ACE 를 만들어 냈다.

정식에 가까운 소개를 하자면 ACE 의 Overview 를 번역하는 정도에 그칠듯 하니 일단 ACE 의 공개 홈페이지를 링크하고 (걍 오버뷰를 해석해서 쓸까... )

http://www.cs.wustl.edu/~schmidt/ACE.html


어째서 내가 ACE 를 선택했는지 내 입장에서 글을 쓰는게  여러모로 편리할것 같다. Java 대신 C++ 을 선택한 내 삶에 후회는 없었지만 , 내 입장에서 Java 는 부러운점이 많았다. 그 쉬운 코딩, 강력한 적응성, 쉬운 이식성 - 내가 게으르기 때문에 더더욱 !!

그러다가 맡게 된 KT 전파연구소의 분석서버, 서버는 근 5년간 해 오고 있던 일이라 별로 부담은 없었지만 왜 그리 귀찮았는지.. 다시 소켓 연결하는 부분 Process 나 User 관리하는 부분 , 소켓 관리하는 부분을 다시 항상 하는 모듈과 사용하는 C++ 언어에 연관되게 다시 작성하는 일 ( AIX 면 VAC , 다른 Unix 모듈이면 사용하는 언어가 따로 등등 ) 들을 다시 할려고 생각하니 갑자기 드는 생각이 있었습니다. "아 앞으로도 서버를 만들때마다 이짓을 해야 하나.."

갑자기 드는 생각이 있었습니다. 만약 서버 어플리케이션 종류대로(포크방식이든, 쓰레드 방식이든) 한 모듈을 만들어 놓구, 다른 컴포넌트를 조합해서 서버가 만들어지는 형태로 구현 시켜 놓으면, 나중에 OS 가 바껴도 돌아갈 수 있는 구조가 (흡사 자바처럼) 있다면 그 얼마나 편할까, 한가지 형태만 잘 만들어 두면 조금의 변경만 가해도 그 비싸다는 서버 프로그램을 뚝딱 하고 만들 수가 있지 않을까!! 란 생각에 찾은것이 ACE 입니다.

ACE 는 오픈프로젝트로 전 세계의 잘나가는 사람들이 손을 댔고, 또  전 세계에 자기가 필요로 하는 OS , 개발툴 환경들에 맞게 수정이 가해져서 이식성이 매우 높습니다. 자체적으로도 텍스트가 잘 정의되어 있구, 또 그 텍스트를 바탕으로 해서 나온 책도 여러권 존재합니다. 즉 배우기도 쉽고 - C++ 을 아는 사람이면 누구나 - , 예제도 잘 정리되어 있고, 그 예제에 대한 해석까지 존재하는 정말 속된말로 '괜찮은' 솔루션 입니다. 물론 부정적인 견해의 안티도 존재합니다. 저에게 많은 도움을 주는 Koei 군도 자기말로는 아니라고는 하는데 ACE에 대한 부정적의견을 가끔 내비치고는 합니다.

하지만 개발을 진행해오면서 느꼈던 제 유일의 감정은 말을 하곤 합니다. '여러 사람이 오랬동안 작업과 리팩터링을 해온것은 절대 내가 만든 라이브러리에 떨어질 이유가 없다.' 남들이 뭐라고 해도 이제 귀에 잘 안들어 오는거 같습니다. 쓰면서 진짜 편안함을 느꼈기 때문에, 이게 광신의 지름길이긴 하지만 일단 계속 사용하면서 불편함을 느낄때까지는 써보려고 마음 먹구 진행합니다.

이것은 제 ACE를 쓰면서 시작된 서버프로그래밍의 역사입니다. KT 전파연구소 작업을 할때 진행했던것입니다.

http://www.mamiyami.com/document/cpp_network_prog_volume2/0201795256_ch03lev1sec3.html



원글은 여기서 가져오고 있지만 실제로 저 글은 C++ 네트워크 프로그래밍 2권에 해당하는 내용일 뿐이다.

ACE 적인 설명이 들어있지만 정리하자면 (일반적인 이야기로 풀어서 표기하자면)

왠만한 클라이언트가 끊어지는 경우는 서버쪽에서 모두 알 수가 있다. 소켓 자체에 무언가 읽을 수 있다고 표기가 되기 때문이다. 이때 recv 함수들을 이용해서 그 내용을 받아오면 recv 함수가 0 아니면 -1 을 리턴한다. 이것때문에 소켓의 접속이 끊어졌다는걸 알 수가 있는데.

-  랜선이 갑자기 뽑힌 경우나 , 아니면 잠시 뽑혔다가 다시 꼽힌 경우

- 클라이언트 컴퓨터가 갑자기 이상이 생긴 경우 , 그래서 클라이언트의 접속이 이상해서 계속 이어지지 않는 경우


이런 경우는 소켓에 생긴 이벤트를 알 수가 없다.

이럴때를 위해서 다루는 방법 몇가지 방법이 있는데

- TCP 가 킵얼라이브 시스템을 이용해서 (자체적인) 아무 응답이 없을때 소켓을 트리거가 종료하는 시스템이 있다. 닫히는 순간을 이용해서 recv 함수가 반응하는것을 이용하는 방법이지만 아쉽게도 매우 오랜 시간이 걸린다.

- 어플리케이션 레벨 정책으로 구현도 가능하다. 흔히 말하는 '살아 있어?' 라고 확인하는 메시지 이다. '살아 있어' 메시지를 일정 기간마다 전송하는데 실패하거나 응답이 없으면 접속이 끊어진것으로 판단한다.

- ACE 에서 지원하는 일정 기간 동안 소켓 통신이 없으면 접속을 끊어버리는 기능이다.


실제로 코딩상에서 어떻게 구현하는지 그 예를 살펴 보겠다.

ACE_Time_Value tv(60 * 3);
int n = _client_peer.recv(buf, sizeof (DWORD), &tv) ;

이 코드를 보면 첫 줄은 시간을 설정하는것이다. 60*3 이니 180 초 즉 3분이다. 3분동안 클라이언트에서 무엇인가 값이 오기를 기다리고 있는것이다.

recv 함수는 에러시에는  -1 이나 0 을 리턴한다.

0인 경우는 소켓이 Full 이 되서 더이상 읽을 것이 없을 때로 알고 있다. (확실하지는 않다. )

-1 인 경우가 다채로운 에러가 났을 경우다. 대표적인 경우가 클라이언트가 갑자기 연결을 끊었을때나 클라이언트 프로그램이 에러가 났던가 하는 경우이다.

여기서는 시간을 설정했기 때문에 3분동안 클라이언트로 부터 값이 오지 않으면 -1 과 errno 값이 ETIMEOUT 값이 들어 있지만, 일정에 쫓겨 TIMEOUT 은 설정하지 않고 단순히

-1 이면 한 에러로 처리하는 루틴이 들어가 있다.


if ( n == -1  )
{
    // time out & socket error
    if (!SendKeepAlive())
       return -1;

    // proper action
    return 0;
}
else if ( n == 0)
{
     ACE_ERROR_RETURN ((LM_ERROR, "(%P|%t) socket closed \n"), -1);
}
else if ( n != sizeof (DWORD))
{
     ACE_ERROR_RETURN ((LM_ERROR, "(%P|%t) received wrong packet \n"), -1);
}

// keep alive reset
NotRequireKeepAlive() ;

n 은 위에서 recv 함수에서 리턴되는 값이다. -1 인 경우가 접속이 종료됐거나 TIMEOUT 인 경우라고 했었다. 그밖의 다른 경우는 에러처리를 해주고 맨 아랫줄의 NotRequireKeepAlive 함수는 정상적으로 클라이언트가 Packet 을 보내서 정상적으로 Packet 의 길이를 받았을때 Keep Alive 상태를 리셋 해주는 함수이다. (단순히 Bool 변수 세팅)

BOOL Client_Handler::SendKeepAlive()
{
  // 현재 시간 얻어오기  
  ACE_Time_Value now = ACE_OS::gettimeofday ();

  // Keep Alive 메시지를 한번도 안 보냈다면
  // Keep Alive 메시지를 보내야 한다.
  if ( _bIsSendingKeepAlive == FALSE)
  {
     // sending Keep Alive
    _bIsSendingKeepAlive = TRUE ;
     // 현재 시간을 저장한다.
    _last_send_keepalive = now ;

     // Keep Alive Packet 을 클라이언트로 전송하는 부분이다.
     CmdKeepAlive packet ;
     MemArchive sendAR (MemArchive::encode);
     packet.Serialize(sendAR);

     // 상대편 Client 가 접속이 끊어져 있는 상태면
     // 전송이 실패할것이다.
     if (!_ptr_packetHandler->Write(_client_peer, sendAR))
      return FALSE ;

     // 단지 잠수(?) 하고 있을뿐이면 성공할것이다.
      return TRUE ;
}
else
{

     // 지금 시간과 마지막으로 Keep Alive 보낸 시간의 차이가
     // 지정해둔 Max 시간과 차이가 있다면
     // Client 는 비정상 접속 종료로 여겨도 무방하다.
     if ( now - _last_send_keepalive >= _max_client_timeout )
    {
       // keep alive failed

        return FALSE ;
    }
   }

  return TRUE;
}

위와 같은 형식으로 처리하는 루틴을 첨가하였다.

해결에 도움을 준 코에군에게 무한한 감사를 (코에군 갱장해!!)

written by 광이랑

+ Recent posts