서버를 만들고, 여러 테스트하는 것 중에 영구 접속이 끊어지는 상황을 재현하는 것은 쉽지 않을 수 있다. 디버거를 이용하여 간단히 종료시켜 보는 법을 알아 본다.

python으로 돌고 있는 데몬(pid 11688)이 있다고 하고, 데몬의 DB 접속은 영구 접속에 해당하는데, 이 접속을 강제로 종료시키는 것을 가정한다.

$ lsof -n -p 11688

COMMAND   PID   USER   FD   TYPE   DEVICE  SIZE/OFF      NODE NAME
python  11688 pynoos  cwd    DIR    202,3        80   1636949 /misc/django_projects/app/broadcast
python  11688 pynoos  rtd    DIR    202,3      4096       128 /
python  11688 pynoos  txt    REG    202,3      7216     53035 /usr/bin/python
python  11688 pynoos  mem    REG    202,3     12408 134524267 /usr/lib64/python/lib-dynload/grpmodule.so
python  11688 pynoos  mem    REG    202,3     13520 134538649 /usr/lib64/python/lib-dynload/_bisectmodule.so
python  11688 pynoos  mem    REG    202,3     48240 134524376 /usr/lib64/python/lib-dynload/arraymodule.so
python  11688 pynoos  mem    REG    202,3    694224 134524317 /usr/lib64/python/lib-dynload/unicodedata.so
python  11688 pynoos  mem    REG    202,3     24176 134524160 /usr/lib64/python/lib-dynload/zlibmodule.so
python  11688 pynoos  mem    REG    202,3     14496  69180222 /usr/lib64/libutil-2.17.so
...
... (생략)
...
python  11688 pynoos  mem    REG    202,3     19288  69048848 /usr/lib64/libdl-2.17.so
python  11688 pynoos  mem    REG    202,3    142232  69048881 /usr/lib64/libpthread-2.17.so
python  11688 pynoos  mem    REG    202,3   1847496  67226793 /usr/lib64/libpython.so.1.0
python  11688 pynoos  mem    REG    202,3    163400  69048840 /usr/lib64/ld-2.17.so
python  11688 pynoos    0r   CHR      1,3       0t0      1028 /dev/null
python  11688 pynoos    1u   CHR    136,4       0t0         7 /dev/pts/4
python  11688 pynoos    2u   CHR    136,4       0t0         7 /dev/pts/4
python  11688 pynoos    3w   REG    202,3   3009102 136203946 /misc/log/projects/app.log
python  11688 pynoos    4r   CHR      1,9       0t0      1033 /dev/urandom
python  11688 pynoos    5w   REG    202,3 266251952 135014754 /misc/log/projects/app.sql
python  11688 pynoos    6u  IPv4 26138940       0t0       UDP *:9413
python  11688 pynoos    7u  IPv4 26137372       0t0       TCP 10.1.1.1:33148->10.1.1.2:mysql (ESTABLISHED)

먼저 열려 있는 파일들의 리스트(list of open files; lsof)를 확인한다. 위의 예에서는 7u로 되어 있는 값이 mysql 커넥션 디스크립터다. 이 디스크립터를 종료하면 mysql과의 접속이 종료되는 것이다.

$ gdb /usr/bin/python  11688
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-115.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /usr/bin/python...Reading symbols from /usr/bin/python...(no debugging symbols found)...done.
(no debugging symbols found)...done.
Attaching to program: /usr/bin/python, process  11688
Reading symbols from /lib64/libpython.so.1.0...Reading symbols from /lib64/libpython.so.1.0...(no debugging symbols found)...done.
(no debugging symbols found)...done.
Loaded symbols for /lib64/libpython.so.1.0
Reading symbols from /lib64/libpthread.so.0...(no debugging symbols found)...done.
[New LWP 12995]
[New LWP 12994]
[New LWP 12992]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Loaded symbols for /lib64/libpthread.so.0
Reading symbols from /lib64/libdl.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/libdl.so.2
...
... (생략)
...
Loaded symbols for /usr/lib64/python/lib-dynload/arraymodule.so
Reading symbols from /usr/lib64/python/lib-dynload/_bisectmodule.so...Reading symbols from /usr/lib64/python/lib-dynload/_bisectmodule.so...(no debugging symbols found)...done.
(no debugging symbols found)...done.
Loaded symbols for /usr/lib64/python/lib-dynload/_bisectmodule.so
Reading symbols from /usr/lib64/python/lib-dynload/grpmodule.so...Reading symbols from /usr/lib64/python/lib-dynload/grpmodule.so...(no debugging symbols found)...done.
(no debugging symbols found)...done.
Loaded symbols for /usr/lib64/python/lib-dynload/grpmodule.so
0x00007f0de576b953 in select () from /lib64/libc.so.6
(gdb) call close(7)
$1 = 0

gdb는 디버깅 중인 프로세스의 입장으로 간단한 시스템 콜을 호출 할 수 있는데, 마지막 명령처럼 "call close(7)" 을 호출하면, 디스크립터를 종료하게 된다. 그리고, ^D를 눌러 gdb를 종료하면 프로세스는 실행을 재개하며, 영문도 모르는 채 DB 접속이 끊어지는 상황을 만나게 된다.

Happy Debugging.

TCP/IP 서버를 만들때는 다음과 같은 방법으로 만들게 된다.

1. 소켓 생성
2. bind
3. listen
4. accept 로 클라이언트 소켓 생성

소켓 프로그램을 처음하는 사람들이 겪게 되는 의문 중의 하나는 무엇에다가 묶고(bind), 듣기전까지는 어떤 일이 일어나길래 들어야(listen)하는가인데, 여기에는 발상의 전환이 필요하다. 일반적으로 파일을 열고 파일에서 읽는 과정을 생각해보면, 이미 경로라는 구별되어 있는 개체가 존재하고 그것을 다루기 위한 기술자(descriptor)를 만들어 기술자를 모든 파일 관련 입출력 함수에 인자로 넘기게 되는데, 소켓 프로그램은 그 반대라는 것이 중요하다. 기술자(socket)를 먼저 만들고, 그 기술자를 구별될 수 있는 경로 혹은 주소에 가져다가 붙이는 일을 한다.

그 이유는 기술자 혹은 소켓의 개념과 주소의 개념이 각각 내부적인 것과 외부적인 것을 대표하는 개념이 이미  OS 초창기부터 존재해 왔고 , 기존의 풍부한 기술자 기반의 시스템 호출과 유사하게 가져가려는 심미적인 설계 때문에 생긴 것이라 할 수 있다.

listen 이라는 것은 쉽게 이야기하면, 이제부터 외부의 접속을 듣겠다, 받아들일 준비 상태로 만들어라는 뜻이겠지만, 보다 기술적인 얘기를 하자면, listen 상태에 들어 있는 소켓은 응용 프로그램이 accept를 하는 것과 상관없이 커널에서 접속에 관계된 3 way handshaking을 구동시키라는 뜻이된다. 중요한 것은 accept와 상관없이 일어나는 것이며, accept는 그렇게 접속과정을 끝낸 소켓을 큐에서 꺼내어 특정 주소에 bind 되어 있는 소켓을 만들어 내는 일을 하는 것이다.

그러면, 일반적으로 서버 소켓을 만들때 사용하는 재사용 가능한 소켓은 언제 설정해 주어야 하는가?

1. 소켓 생성
2. setsockopt( s, SOL_SOCKET, SO_REUSEADDR ... );
3. bind
4. listen
5. accept 로 클라이언트 소켓 생성

bind 작업에 들어갈 때, 묶어 주는 주소에 대해 서버 소켓이 listen 상태에서 벗어나는 순간 바로 (주소에 해당하는 ESTABLISHED 혹은 TIME_WAIT 상태로 남아 있는)가 있어도) 다시 접속가능한 bind 를 지정하기 위해서 소켓 옵션의 설정은 bind 이전에 해야하는 것이 옳다. 재사용가능 한 것이 소켓 옵션이 아니라 주소에 대한 것이므로 소켓에 옵션을 실어 bind 시스템 호출을 해서 넘기는 것이다. 즉, 다음은 올바른 사용법이 아니라는 것이다.

1. 소켓 생성
2. bind
3. setsockopt( s, SOL_SOCKET, SO_REUSEADDR ... );
4. listen
5. accept 로 클라이언트 소켓 생성

물론 구현에 따라 위 순서로 해도 동작가능할 수 있다. 하지만, 의미상 그렇지 않다는 것을 알고 사용하자.

+ Recent posts