일상의 정리

소켓을 올바르게 닫기

프로그래밍/C++
펌글: 원본 링크 http://blog.naver.com/poins/50000828154

1.요약 

socket을 닫는 옳바른 방법에 대해서 알아본다. 


2.본문 

socket을 닫는 일반적이고 안전한 방법은 다음과 같다. 

shutdown(m_socket, SD_BOTH); 

closesocket( m_socket ); 

각각의 의미는 다음과 같다. 

shutdown : 연결된 상대방 소켓에 연결이 종료됨을 알린다. 
closesocket : 소켓 핸들을 닫는다. 

위에서 설명한것 같이 closesocket은 열려진 소켓의 핸들을 닫는 역활만을 하는것이 공식적이다. 그러나 실제 동작을 살펴보면 shutdown을 한것같은 효과를 볼 수 있다. 이것은 socket의 timeout처리 때문일것으로 생각된다. 

다음은 이런한 과정을 거칠때 내부적인 수행과정이다. 

Client side 
(1) Invokes shutdown(s, SD_SEND) to signal end of session and that client has no more data to send. 

Server side 
(2) Receives FD_CLOSE, indicating graceful shutdown in progress and that all data has been received. 
(3) Sends any remaining response data. 
(4) Invokes shutdown(s, SD_SEND) to indicate server has no more data to send. 
(4') Invokes closesocket. 


Client side 
(5') Gets FD_READ and calls recv to get any response data sent by server. 
(5) Receives FD_CLOSE indication. 
(6) Invokes closesocket. 

위의 과정을 살펴보면 우리는 shutdown과 closesocket사이에 recv가 있어야함을 알 수 있다. 하지만 보통은 이과정을 생략하고 있다. 

다음은 뉴스 그룹에서 socket종료에 관련된 내용을 번역한것이다. 

jclin란 아이디를 가진 사람이 자기는 정상적으로 소켓을 종료했는데 netstat로 종료과정을 관찰하니 소켓이 'TIME_WAIT'상태로 남아있다가 얼마후에 종료되는것을 발견했다. 그래서 이런한 timeout을 해소할 수 있는 방법을 물어왔다. 

------------------------------------------------------------------- 
jclin 
저는 에코서버와 에코 클라이언트를 만들었습니다. 
... 
<중략> 
... 
서버와 클라이언트는 다음과 같은 과정으로 종료했습니다. 

1:   linger li = {0, 0}; 

2:   setsockopt(m_socket, SOL_SOCKET, SO_LINGER, (char*)&li, sizeof(li)); 

3:   shutdown(m_socket, SD_BOTH); 

4:   closesocket( m_socket ); 

서버나 클라이언트를 종료하고 netstat로 종료시에 소켓에 어떠한 일이 일어나는지 보았습니다. 저는 많은 소켓들이 'TIME_WAIT'으로 표시되어있는것을 보았습니다. 그리고 그것들은 잠시후에 사라졌습니다. 

1,2,3,4의 과정들을 생략하거나 조정해 보았지만 마찬가지였습니다. 

제 질문은 waiting timeout없이 종료할수 없냐는것입니다. 

이유는 많은 연결을 해야하는 서버에서 일정시간동안 소켓을 wait하고 있는것은 시스템 리소스를 낭비하는것이 되기 때문입니다. 

------------------------------------------------------------------- 

Ed Astle 

TIME_WAIT는 TCP/IP의 내용이기 보다는 socket에 관련된 내용입니다. 이것은 2MSL(maximum segment lifetime) wait state이라고 불리기도합니다. 

tcp stack이 이런한 상태일때 이 연결에 관련된 어떠한 TCP segment도 거부됩니다. 

... 
<중략> 
... 
다음과 같이 추가하기를 권합니다. 

code = ::shutdown( m_hSocket, SD_BOTH );     // Send a FIN here 



// Wait for socket to fail (ie closed by other end) 



if( code != SOCKET_ERROR ) 

{   fd_set readfds; 

    fd_set errorfds; 

    timeval timeout; 



    FD_ZERO( &readfds ); 

    FD_ZERO( &errorfds ); 

    FD_SET( m_hSocket, &readfds ); 

    FD_SET( m_hSocket, &errorfds ); 



    timeout.tv_sec  = MAX_LINGER_SECONDS; 

    timeout.tv_usec = 0; 

    ::select( 1, &readfds, NULL, &errorfds, &timeout ); 

} 



code = ::closesocket( m_hSocket ); 

m_hSocket = INVALID_SOCKET; 

이것이 ACK과 FIN을 기다는 방법입니다. 이렇게 하지 않았을때는 TIME_WAIT을 감수해야합니다. 

저는 이러한 방법으로 정상적으로 종료하는 서버들을 만들었습니다. 이렇게 하지않았을때는 ACK신호를 받지 못해 mainframe에서 사용자가 logout했다는 로그가 남지 않게되었습니다. 

------------------------------------------------------------------- 

결론을 내자면 마지막에 신호를 recv를 하는 코드를 꼭 써야 한다는것이된다. 많은 예제 코드들이 이러한 방법을 쓰지만 많은 프로그래머들이 이러한 방법을 무시한다. 종료시 recv종류를 쓴 이유가 서버측의 남을 데이터를 모두 받아들이기위한 방법이라고 예상했기 때문이다. 또한 winsock의 경우는 많이 BSD소켓에서 많은 부분이 변형 되었기 때문에 내부적으로 이런 처리들이 이루어지고 있을지도 모르겠다. 하지만 정상적인 방법을 이것이라는것을 설명하고자 했다. 

BIND 동적 업데이트 하기

리눅스/설치

외부에서 DHCP로 변경되는 IP를 BIND에 동적으로 업데이트 해 주기
기본적인 문서는 아래를 참조
http://bind.cslab.kr/dynamic-update

외부 업데이트를 위해서 TSIG (Transaction Signature)를 이용함
1. 서버측 TSIG 설정
TSIG 키 생성

dnssec-keygen 프로그램으로 키 생성
  • -a hmac-md5 : 알고리즘 종류
  • -b 128 : 키 길이(128 bit) / 최대값 : 512 bits
  • -n HOST : 키 종류(HOST)
# dnssec-keygen -a hmac-md5 -b 128 -n HOST test.com.

  • 생성파일 확인 : 다음 2개의 파일이 생성됨
K{name}.+157.+{random}.key
K{name}.+157.+{random}.private

{name}은 저 위에서 HOST 에 지정한 도메인(test.com) 명임

  • named.conf 수정
zone “test.com" {
    type master;
    file “test.com.external.zone.db";
    // TSIG Key Based Access Control
    allow-update { key test.com.; };
};
key "test.com." {
    algorithm hmac-md5;
    secret "XXXXXXXXXXXXXXXXXXXX";
};

named.conf 의 해당 도메인(test.com)에 allow-update 부분에 key로 업데이트 하도록 추가

아래쪽 XXXXX 부분에는 위에서 생성한 .private 파일안의 Key: 이후에 나오는 문자열을 복사해 넣는다.
named.conf 는 다수의 사람에게 공개될 수 있으므로, 위의 key 부분은 별도의 파일로 만들어 주는 것이 좋다
이럴 경우에는 예를 들면 tsig.key 라는 파일에 위 구문을 저장한 후 named.conf 에는
include "/etc/tsig.key";
를 추가해서 Key 파일을 읽어 들이도록 한다.
  • 파일권한설정
/var/named (물론 chroot  설정이 되어 있다면 설정된 디렉토리) 의 사용자 권한이 named 사용자가 write 할 수 있는지 확인하고 안되어 있다면 권한을 부여한다.
chown named named;


2. 클라이언트 측 스크립트
원격지 DNS를 동적으로 업데이트할 때는 nsupdate를 이용한다.
서버에서 만든 2개의 키파일을 복사해 온다.

#!/bin/sh
# Update the name server with our info
HOSTNAME=host.domain.com
PREV_FILE=/root/prev_ip
PREV_IP=`cat $PREV_FILE`
CUR_IP=`/sbin/ifconfig eth0 | head -2 | tail -1 | awk '{ printf("%s\n", $2); }' | sed "s/addr://"`
if [ "$PREV_IP" != "$CUR_IP" ]
then
/usr/bin/nsupdate -d -k /root/keys/Kdomain.com.+157+59903.key <<-EOF
       prereq yxdomain $HOSTNAME
       update delete $HOSTNAME A
       update add $HOSTNAME 60 A $CUR_IP
       send
EOF
echo $CUR_IP > $PREV_FILE
fi
exit 0
다음 명령으로 nsupdate를 실행
nsupdate -d -k K{name}.+157.+{random}.private
>prereq nxdomain <호스트명>.
>prereq yxdomain <호스트명>.
>
위에서 보듯이 prereq 로 호스트명을 지정한다.
이때 호스트명이 이미 존재할 경우 yxdomain 을 없을 경우 nxdomain 을 입력해 주어야 한다..
자세한 건 BIND 문서를 참조

호스트를 추가 하거나 삭제 하는 명령어를 실행하고 최종적으로 send 명령어로 명령을 전송한다.
nsupdate -d -k Ktest.com.+157.+12345.private
>prereq yxdomain new.test.com.
>update delete new.test.com.
>update add new.test.com. 60 IN A 192.168.0.5
>show
>send

아래는 자동으로 IP를 업데이트 하는 스크립트이다.

#!/bin/sh
# Update the name server with our info
HOSTNAME=host.domain.com
PREV_FILE=/root/prev_ip
PREV_IP=`cat $PREV_FILE`
CUR_IP=`/sbin/ifconfig eth0 | head -2 | tail -1 | awk '{ printf("%s\n", $2); }' | sed "s/addr://"`
if [ "$PREV_IP" != "$CUR_IP" ]
then
/usr/bin/nsupdate -d -k /root/keys/Kdomain.com.+157+59903.key <<-EOF
       prereq yxdomain $HOSTNAME
       update delete $HOSTNAME A
       update add $HOSTNAME 60 A $CUR_IP
       send
EOF
echo $CUR_IP > $PREV_FILE
fi
exit 0

위 스크립트를 crontab 에 등록해서 5분마다 실행하게 할 경우 IP 변경시에 자동으로 업데이트 한다.

VBA에서 Printer Port명 NE00 라고 나오는 것은?

프로그래밍/VB

비베에서 매크로로 작업을 하다 프린터를 선택하려고 보면 

어떤어떤 모델 on NExx:
이런 식으로 나온다.

이 xx 가 어디 있는 지 찾아 보니..

HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\PrinterPorts

이 위치에 있는 포트명이다.

이 포트는 LPT1 과 같은 물리포트에는 해당되지 않는데..
네트웍 포트 또는 USB포트와 같은 가상포트일 경우 NExx 이런식으로 붙는다.

문제는 이 NExx 라는 값이 부팅할 때 마다 변할 수 있다는 거..
따라서 하드 코딩으로 포트를 박으면 안되고 일단 저 레지스트리를 검색해서 프린터명으로 포트를 가져와야 한다.

더 좋은 방법을 찾기 전까지는 일단은 이게 최선일 듯..

ORACLE 기본 Date 형식 변경하기

프로그래밍/ORACLE
http://www.oradev.com/oracle_date_format.jsp

select * from v$nls_parameters;

nls_parameters에 값들 보기

ALTER session set nls_date_format='YYYY-MM-DD HH24:MI:SS:FF3';
ALTER session set nls_timestamp_format='YYYY-MM-DD HH24:MI:SS:FF3';

date 형식 수정하기

원본출처
http://starplatina.tistory.com/entry/Oracle-date-format

Linux 상에서 svn 사용하기

리눅스/설치
대개의 프로젝트는 윈도에서 하므로 TortoiseSVN을 사용하지만...
일부 서버는 리눅스 상에서 개발하므로 리눅스에서도 사용해야 되는 경우가....

다음은 리눅스에서 svn 기본 명령 사용법 정리이다.

1. 저장소 만들기
일단 root로 로그인
# cd /home/svn
--> 저장소를 생성
# svnadmin create --fs-type fsfs <repository명>
--> svn group이 저장소에 Access 가능하도록 directory 속성변경
# chmod -R o-x <repository명>
# chmod -R o-r <repository명>
# chmod -R g+w <repository명>
# chgrp -R svn <repository명>

2. 자신의 계정에서 생성한 저장소 Access
--> 자신의 사용자로 로그인
--> ~/.bash_profile 에 다음 줄 추가하여 환경변수 생성
export SVN_EDITOR=vi
--> 원하는 폴더로 감
$ cd ~/gits
--> 요렇게 하면 현재 디렉토리 아래에 <directory명>에 해당되는 directory가 생성되고 그 아래 소스트리를 만들면 된다.
$ svn checkout svn+ssh://localhost/home/svn/<repository명> <directory명>
--> <directory명> 으로 가서 작업
--> 로컬 디렉토리 또는 파일 저장소에 추가하기
$ svn add <파일명>
--> 로컬 디렉토리 또는 파일 커밋하기
$ svn commit
--> 저장소의 최근 내용으로 로컬 파일 업데이트 하기
$ svn update

더 자세한 내용은 아래 페이지 참조

http://www.pyrasis.com/main/Subversion-HOWTO

FreeNX 설치

리눅스/설치

1. http://www.nomachine.com/ 에서 다운로드 받는다.

2. Client, Node, Server 순으로 설치를 한다.
  # sudo rpm -i nxclient-3.3.0-6.x86_64.rpm
  # sudo rpm -i nxnode-3.3.0-12.x86_64.rpm
  # sudo rpm -i nxserver-3.3.0-14.x86_64.rpm

3. 설치 후 /usr/NX/scripts/setup/nxserver --install 실행

4. 키 생성을 위해 /usr/NX/bin/nxserver --keygen 실행

5. /usr/NX/share/keys/default.id_dsa.key 내용을 클라이언트 프로그램 설정 부분에 키 항목에 추가.

6. 만약 별도의 사용자 DB를 유지하고자 한다면 /usr/NX/etc/server.cfg 파일을 수정
EnableUserDB = "1"
EnablePasswordDB = "1"
"1"로 한 경우에는 6-1로..

기존 리눅스 사용자를 그대로 쓰려면 위의 "1"을 "0"으로 바꾼다.
EnableClipboard = "both"

6-1. 계정 추가 및 암호설정을 위해 nxserver --useradd username 실행

7. 데몬 재시작. /etc/init.d/nxserver restart

8. sshd 설정 확인
Load up your /etc/ssh/sshd_config file into an editor:

/etc/ssh/sshd_config 편집

다음 라인을 추가하거나 확인한다.
AuthorizedKeysFile /usr/NX/home/nx/.ssh/authorized_keys2

sshd 데몬을 재시작
sudo /etc/init.d/ssh restart

다음 명령을 쳐서 아래 화면 내용이 나오면 정상적인 상태이다
sudo /usr/NX/bin/nxserver --status

This should return:

NX> 900 Connecting to server ..
NX> 110 NX Server is running.
NX> 999 Bye.

9. 접속 후 한글이 안되는 경우가 있다. 이런 경우 다음과 같이 하면 된다.
사용자 홈 디렉토리에 .bash_history 파일에 다음 내용을 추가 한다.

export LC_CTYPE="ko_KR.UTF-8"
export XMODIFIERS=@im=SCIM
export GTK_IM_MODULE=scim
export QT_IM_MODULE=scim

Visual Leak Dectector

프로그래밍/C++
개발할 때 문제 되는 것 중의 하나가 Memory Leak이다.
상용툴인 BoundsChecker 를 사용하면 되지만...
값이 만만치 않다는 거...
공짜로 훌륭한 툴이 있더라.. 바로 Visual Leak Dectector
MS Visual Studio 를 사용한다면 바로 적용할 수 있다..
안타까운 건 현재 진행중인 프로젝트는 BC++ 6.0 이라는 거지만...

나중에 MS-VC++을 사용할 때를 대비해서 일단 로깅...
한가하면 BC++로 포팅할 수 있을 지 검토해 볼 예정...

http://www.codeproject.com/KB/applications/visualleakdetector.aspx

Borland C++ Builder 6.0 STLPort 설치

프로그래밍/C++
Borland C++ Builder 6.0 에 map 을 사용하다 생각한 데로 동작하지 않아서 이거 저거 찾아 보다가..
STL 버전을 업데이트 해 보기로 결심...

자료를 찾아 보니 BC++에는 STL Port를 설치 가능...
BC++ 5.0 버전에 STL Port 4.5.3 을 설치하는 링크는 있는 데...
http://turboc.borlandforum.com/impboard/impboard.dll?action=read&db=cpp_res&no=7

위 링크대로 설치할 경우 6.0에서는 에러가 난무... ㅡㅡ
결국 http://www.stlport.org 에서 최신 버전인 5.2.1 다운...

Doc 아래 Readme.borland 파일을 읽어 보니
configure -c bcc 하면 된단다..

그런 위 명령을 수행하면 Unknown compiler : bcc 에러 작렬..
이런...

다시 또 구글링을 열심히 해 보았으나 답이 없더라...
질문들은 열심히 올렸는 데 답은 없더라는...

이짓 저짓 하고 심지어는 에러 나는 소스를 추적해서 고치려고 까지 해 보았지만...
그럴 시간이 많지 않은 어려움이...

그래서 해 본 게 아래 버전 부터 차분히 해 보자..
4.6.1 실패
5.0 실패
5.1.7 성공...
안되는 건 그럴 수 밖에 ...
STL Port 에서 테스트 해 보지 않았거나 아예 지원을 안하는 거다.

5.1.7 컴파일은 다음과 같다.. (결국 위에서 말한 Readme.borland 파일 안에 다 들어 있음)
아 이 전에 http://www.mingw.org 에 가서 GNU Make 를 다운 받아서
Borland Make 가 아닌 GNU Make 를 사용할 수 있도록 해 두어야 한다.

1. BCC 의 bin 폴더 안에 있는 bcc32.cfg 파일을 열어서 인클루드 패스가 정확한지 확인 하고 아래 패스(물론 각자 환경에 맞는)가 제대로 들어 있는 지 확인한다.
    -I%BORLAND_PATH%\BCC6\include

2. brcc 컴파일을 위해서 cmd 환경변수에 아래를 추가한다. (안 그러면 windows.h 가 없다고 에러 남)
   set INCLUDE=%BORLAND_PATH%\BCC6\include

3. 1번에서 했던 폴더에 ilink32.cfg 파일을 열어서 아래 2개 라이브러리 패스가 정확한지 확인(아마 PSDK가 없을 듯)
    -L%BORLAND_PATH%\BCC6\lib;%BORLAND_PATH%\BCC6\lib\PSDK
 
4. STLPort 를 풀어 놓은 폴더의 build/lib 로 가서
    configure -c bcc

5. 성공하면
    make -fbcc.mak install

6. 위와 같이 하면 DLL 버전이 만들어 지므로 만약 static lib 가 필요하다면
    make -fbcc.mak clean
    make -fbcc.mak install-static

7. 다 되었다면 적당한 위치로 STL port 폴더를 옮기고
프로젝트의 include 패스를 STLPort/include 로
LIB 패스를 STLPort/lib 로 추가하고 기존 STL 패스는 삭제한다.

근데 다 하고 나서 보니 map 이 이상한 동작을 했던 건 내가 잘못 해서 그랬던 것이더라.. ㅡㅡ
뭐 한 거지?

개발 소스째 제공해야 되므로 BCC 설정을 바꾸는 건 일단 왠만하면 피해야 되서..
그래서 일단 테스트는 나중에 하기로...

이상 6.0 에서 최신 버전인 5.2.1은 아니지만 5.1.7은 사용할 수 있다는...

토탈 커맨더 (Total Commander) 환경 설정 파일 위치

유틸리티/Total Commander

윈도를 다시 깔고 나서 제일 먼저 설치하는 게 토탈 커맨더입니다.
그런데 토탈 커맨더는 기존 설치본이 있을 경우 그냥 바로가기만 만들면 되므로 편하지만...
이렇게 할 경우 기본 설정 파일 위치가 C:\Windows 가 되어 버립니다.

설정 파일 위치는 레지스트리에 들어 있으므로 regedit를 실행해서 다음 값을 추가하면 됩니다.

HKEY_CURRENT_USER\Software\Ghisler\Total Commander 에 있습니다.
값은, (모두 스트링)
FtpIniName = .\wcx_ftp.ini
IniFileName = .\wincmd.ini
InstallDir = D:\totalcmd (실제 설치된 경로)

편하게 하려면 아래 레지스트리 파일을 열어서 설치 경로만 수정한 후 더블 클릭하면 됩니다.


/tmp 디렉토리 suid 불가능하도록 마운트 하기

리눅스/해킹
이번 해킹 사건으로 더 이상 해킹에 대해 무관심 해서는 안될 거 같다는 교훈을 ...

찾아 보니 /tmp 는 아무나 쓰기 권한이 있으므로 해킹 임시 파일 저장고로 주로 이용된다.

따라서 이 디렉토리에 suid 권한이 없도록 마운트 할 것을 권장한단다.
 

아래는 기존 시스템에 /tmp 를 nosuid 로 마운트 하는 법
 

190Mb partition을 생성
#cd /dev/; dd if=/dev/zero of=tmpMnt bs=1024 count=200000
파티션을 포맷

#mke2fs /dev/tmpMnt

기존 데이터를 백업

#cp -Rp /tmp /tmp_backup

새로운 파티션을 마운트

#mount -o loop,noexec,nosuid,rw /dev/tmpMnt /tmp
권한 설정
#chmod 0777 /tmp
기존 데이터를 다시 리턴
#cp -Rp /tmp_backup/* /tmp/

다음 부팅 때를 위하여 /etc/fstab 에 다음 라인 추가

/dev/tmpMnt /tmp ext2 loop,noexec,nosuid,rw 0 0

 
/var/tmp 디렉토리를 삭제하고 심볼릭 링크를 걸어 줌

#rm -rf /var/tmp/
#ln -s /tmp/ /var/

완료...

이제 백업된 디렉토리를 지워 주자

#rm -rf /tmp_backup