반응형
http://rucaus.egloos.com/2372401
Linux/TCP] SO_LINGER, TIME-WAIT 상태
1. 평범한 close()종료와 SO_LINGER
나는 처음에 SO_LINGER가 무조건 TIME-WAIT를 없애는데 쓰이는 줄 알았지만 틀린 생각이었다. SO_LINGER 옵션을 어떤 특정한 방식으로 설정하면, 이후 소켓의 종료 방식이 약간 다르게 바뀌고, 종료 방식의 차이로 인하여 결과적으로 TIME-WAIT 상태가 아예 일어나지 않는다 - 라고 보는 것이 더 정확하다.
SO_LINGER 옵션은 유저가 close() 함수를 호출했을 때 아랫단 커널이 어떻게 행동할지를 정하는 옵션이다.
우리가 그냥 socket에 대해 close()를 한다면. 즉 SO_LINGER 관련하여 아무 옵션도 주지 않고 평범하게 close 한다면 다음과 같이 기본적인 동작을 한다.
1) 아마 유저는 더 이상 그 소켓에 대하여 보내고 받을 것이 없다고 판단했으므로 close()를 호출했을 거다.
2) close() 콜은 유저가 더이상 그 소켓을 갖고 아무것도 보내고 받지 않겠다는 의미이므로, close() 이후 socket에 대한 통제권은 유저를 떠나 TCP 레이어 단(커널)으로 넘어간다. 그러므로 유저는 close() 함수 결과를 기다리지 않고 (블로킹되지 않고) 바로 다른 다음 작업을 할 수 있다.
3) 한편 커널은 알아서 우아한 종료(graceful shutdown)로 종료하려 한다. 이것은 TCP 메시지의 컨트롤 플래그 관점에서, FIN-ACK FIN-ACK가 오가서 종료하는 것을 말한다. FIN은 자신의 데이터를 다 보냈을때, 즉 이제 앞으로는 내가 send할 데이터가 없을 때 보내는 종료 플래그이다. 커널은 자신의 버퍼에 있는 데이터를 모두 다 보내서 비운 다음 FIN을 보냈을 거고, ack를 기다릴 것이고, 상대방의 FIN을 기다릴 것이고, 상대의 FIN을 받고 그에 대한 ack를 보낸 이후 TIME-WAIT상태에서 ack가 잘 갔나 약간 기다렸다가 소켓을 완전히 종료(CLOSED)할 것이다.
하지만 SO_LINGER 옵션을 사용해서 이런 종료 방식을 바꿀 수 있다.
struct linger ling;
ling.l_onoff = 1;
ling.l_linger = 0;
setsockopt( socket, SO_LINGER, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
linger 구조체의 l_onoff를 1로 설정한다면, 위와 같은 default 종료 방식 대신 l_linger에 유저가 설정한 값에 따라 종료하게 된다.
[ l_linger > 0 ]
l_linger 가 0보다 큰 양수이면, 해당 값의 시간동안 커널은 우아한 종료를 하려고 노력한다. (즉 자신의 버퍼를 해당 시간내에 비우고, FIN-ACK-FIN-ACK 종료를 하기 위해 노력한다.)
그리고 그 시간 동안 유저는 1) 자기가 만든 소켓이 블로킹 소켓이라면 close 작업에 블로킹된 상태로, 시간이 끝나거나 / 우아한 종료가 될 때까지 기다려야 하고, 2) 논블로킹 소켓으로 만들었다면 EWOULDBLOCK 에러 메시지만 받고 바로 커널의 리턴을 받아 다음 작업을 할 수 있다.
만약 해당 시간 내에 우아한 종료를 못 하면, 커널은 RST(리셋 플래그)를 세팅해서 보내버린다. 만약 커널의 버퍼에 못 보낸 TCP 데이터가 있다고 하더라도, 그것은 모두 그냥 버린다. 리셋을 보낸 이후, 상대쪽에서 내 소켓에 뭔가 읽거나 쓰려고 해도, 상대는 ECONNRESET 에러만 받게 될 것이다.
[ l_linger = 0 ]
l_linger가 0일 경우 아예 우아한 종료를 시도하지 않는다. 처음부터 커널은 자기 버퍼의 데이터를 모두 버리고, 상대방에게는 RST 플래그를 세팅해서 보낸다.
2. SO_LINGER에서 linger 옵션을 켜고 (l_onoff = 1), linger time을 0으로 주는게 (l_linger = 0) 왜 TIME-WAIT을 없애나.
그것은 SO_LINGER 옵션을 통해, close() 호출시 FIN 대신 RST이 '바로' 나가도록 설정했기 때문이다.
TIME-WAIT 상태는, 결론적으로 내가 FIN 플래그를 먼저 보냈기 때문에 생긴 결과이다. (FIN-ACK FIN-ACK에서, 나의 마지막 ACK가 잘 갔나 보장하기 위해 있는 상태이므로.)
그런데 SO_LINGER에서 linger 옵션을 켜고, linger time을 0으로 주면, FIN이 나가는 대신 RST이 나간다. 그것도 close()를 호출한 즉시.
애초에 FIN-ACK-FIN-ACK 보내고 TIME_WAIT 상태가 되는 대신,
RST을 보내고 -CLOSED 상태가 되버리므로.
TIME-WAIT이 발생할 기회가 아예 안 주어지는 것이다.
(이 글을 쓸때 참고한 출처 : UNIX socket Q&A. http://developerweb.net/viewtopic.php?id=2982 그리고 TCP/IP Illustrated 책..
글쓴이가 이해하고 있는 바가 틀리다면 답글로 정정을 부탁드립니다.)
반응형