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

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.

소켓프로그래밍 깊이 보기 1 : 접속이 닫힌 후 읽을 수 있는 버퍼

Quiz 하나를 생각해보자.

클라이언트가 서버에 접속하여 뭔가를 전송하고 있다. 클라이언트는 1000 바이트를 전송하고나서 바로 소켓을 종료하였는데, 서버는 1 byte 씩 읽으면서 행의 끝을 판단하는 구조로 되어 있다. 서버가 10 바이트를 읽었는데, 실상 접속은 종료되었다. 서버쪽 프로그램은 11 바이트를 읽을 때, 접속 종료를 바로 알 수 있을까?

싱겁지만, 답은 서버쪽에서 1001번째를 읽기 시도할 때 비로소 안다는 것이다. 그 이유는 TCP는 데이터의 정확한(?) 전송을 보장하도록 되어 있기 때문인데, 끊어진 클라이언트에게 일단 1000 바이트에 대해 받았다는 신호를 보냈기 때문에, 서버 프로그램에 안정적으로 데이터를 올려 보낸후 접속이 종료되었음을 알려주는 것이다.

아래의 예는 위 문제를 구현한 것은 아니지만, 느린 수신에서 일어나는 현상을 설명하는 것이다.
보내는 쪽은 프로그램은 이미 종료되었지만, 받는 쪽은 계속 진행하고 있다.

$ ./server &
$ ./client
Send: elapsed 0 sec: sent 100 bytes
Send: elapsed 0 sec: sent 100 bytes
Send: elapsed 0 sec: sent 100 bytes
Send: elapsed 0 sec: sent 100 bytes
Send: elapsed 0 sec: sent 100 bytes
Send: elapsed 0 sec: sent 100 bytes
Recv: elapsed 0 sec: received 200 bytes
Send: elapsed 0 sec: sent 100 bytes
Send: elapsed 0 sec: sent 100 bytes
Send: elapsed 0 sec: sent 100 bytes
Send: elapsed 0 sec: sent 100 bytes
End of client
Recv: elapsed 1 sec: received 200 bytes
Recv: elapsed 2 sec: received 200 bytes
Recv: elapsed 3 sec: received 200 bytes
Recv: elapsed 4 sec: received 200 bytes
End of server

$ cat server.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>

void do_hojin2( int sock )
{
       char buf[200];
       int len;
       time_t t = time(0);
       while( ( len = recv( sock, buf, sizeof buf, 0 ) ) > 0 )
       {
               printf("Recv: elapsed %ld sec: received %d bytes\n", time(0) - t, len );
               sleep(1);
       }
}

int main()
{
       struct sockaddr_in addr;
       int sock, worksock;
       int len;
       int val = 1;

       sock = socket( PF_INET, SOCK_STREAM, 0 );

       memset( & addr, 0, sizeof addr );
       addr.sin_family = AF_INET;
       addr.sin_port = htons( 4000 );
       len = sizeof addr;

       setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, & val, sizeof val );
       bind( sock, (struct sockaddr *) & addr, len );
       listen( sock, 5 );

       worksock = accept( sock, (struct sockaddr *) & addr, & len );
       do_hojin2( worksock );
       close( worksock );
       close( sock);
       printf("End of server\n");
       return 0;
}

$ cat client.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>

void do_hojin1( int worksock )
{
       int i;
       time_t t = time(0);
       for( i=0; i<10; i++ )
       {
               char buf[100];
               int sent;

               sleep(0);
               memset( buf, 'x', sizeof buf );
               sent = send( worksock, buf, sizeof buf, 0 );
               printf("Send: elapsed %ld sec: sent %d bytes\n", time(0) - t, sent );
               fflush( stdout );
       }
}

int main()
{
       struct sockaddr_in addr;
       int sock, worksock;
       int len;
       int val = 1;

       int buf[32768];

       sock = socket( PF_INET, SOCK_STREAM, 0 );

       memset( & addr, 0, sizeof addr );
       addr.sin_family = AF_INET;
       addr.sin_port = htons( 4000 );
       len = sizeof addr;

       connect( sock, (struct sockaddr *) & addr, len );
       do_hojin1( sock );
       close( sock );
       printf("End of client\n");
       return 0;
}

+ Recent posts