TIME-WAIT 상태란, 연결 종료 시 마지막 패킷 전송 실패를 대비하기 위한 상태이다. TCP 연결 종료과정은 four-way handshaking을 하게 된다. TCP 에서 연결을 종료를 그림으로 설명하면 다음과 같다.
A : B 야, 이제 전화 끊자
B : 어~ 잠시만 기달려주셈
B : 이제 됐다, 전화 끊으삼
A : 끊어야지
마지막에 A 가 끊어야지 하고, TIME-WAIT 에 들어갑니다.
이유인즉, TIME-WAIT 에 있는 동안 혹시나 마지막에 B 에게 보낸 ACK 메세지가
도착하지 않았을 경우, B 에서 A 에게 다시 ACK, FIN 메세지를 보내게 됩니다.
그러면 A 는 TIME-WAIT 에서 빠져나와서
B 에게 ACK 메세지를 보내고 다시 TIME-WAIT 에 들어가게 됩니다.
For-way handshaking 과정이 끝난 상태에서 A의 소켓이 바로 소멸되는 것이 아니라 TIME-WAIT상태로
들어가게 된다. 반면에 B는 바로 연결이 종료되고 소켓이 소멸된다. 즉, 먼저 연결 종료를 요청할 경우 TIME-WAIT상태를
거쳐야 하다는 뜻이다.
만약, A가 먼저 연결을 종료(Ctrl-C 키) 시켰다고 하자. 그럼 A가 B에게 FIN메시지를 시작으로 four-way
handshaking을 하게 된다. 이어서 다시 프로그램을 실행 시키면, bind()함수 호출에서 문제가 발생했다고 나올
것이다. 이 이유가 바로 TIME-WAIT 때문이다. A의 TIME-WAIT가 일정시간 계속 유지 되고 있기 때문에 즉, 아직
소켓이 소멸된 것이 아니기 때문에(해당 port가 사용 중이기 때문에) bind()함수에서 에러가 난 것이다.
그럼 TIME-WAIT는 왜 갖는 것일까? 바로 마지막 패킷이 제대로 전송이 되었는지를 확인하기 위해 필요한 것이다. 위
그림에서 B에서 A에게로 “종료 준비가 완료 됐다”는FIN메시지를 보내고 A는 “자기가 종료 했다”는 메시지를 B에게 보내게
된다. 하지만 “자기가 종료를 했다”는 메시지를 B가 받지 못했을 경우 B는 또 다시 “종료 준비가 완료 됐다”는 FIN메시지를
A에게 보내게 되는데, 만약 TIME-WAIT가 없어서, A가 바로 종료를 하게 되었다면, 원활한 종료가 이루어지지 않을
것이다.
그래서 TIME-WAIT가 있으면 B가 A에게 재전송한 FIN메시지를 받을 수 있게 되고 A는 다시 “자기가 종료를
했다”라는 메시지를 보내게 되어 정상적인 종료를 이끌게 된다. 만약에 또 다시 “자기가 종료를 했다”라는 메시지를 보내게 되면,
또 일정 시간의 TIME-WAIT 시간이 주어지게 된다. 그래서 TIME-WAIT시간은 더 길어질 수도 있다.
만약 TIME-WAIT 시간이 모두 지나도 B 로부터 응답이 오지않는다고 가정하면,
A 는 그제서야 IP 에 연결된 포트를 소켓으로부터 반환하게 됩니다.
혹시 모를 패킷 전송 실패에 대비하기 위해 TIME-WAIT 이 존재하는 것입니다.
하지만, 안정적인 동작을 위해 만든 TIME-WAIT 이 사용자들에겐 답답함을 줄 수 있다고 하는데요.
실제 TIME-WAIT 이 2~3분 정도 걸리는 시간이기 때문에,만약 서버가 재가동을 위해 TIME-WAIT 에 머물러
있는 경우 이 때의 사용자들은 답답해할 수 있다는 문제입니다. 그래서 TCP/IP 는 TIME-WAIT 에 빠져있는 소켓을 다시
사용할 수 있는 방법을 제공하고 있습니다.
serv_sock=socket(PF_INET, SOCK_STREAM, 0);
optlen = sizeof(option);
option = TRUE; // #define TRUE 1
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
위 코드를 사용하면, TIME-WAIT 상태의 소켓도 바로 바인딩하여 사용할 수 있습니다.
다음은 TIME-WAIT 소켓을 바로 재사용하는 예제입니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define TRUE 1
#define FALSE 0
void error_handling(char *message);
int main(int argc, char **argv)
{
int serv_sock;
int clnt_sock;
char message[30];
int str_len;
int option;
socklen_t optlen;
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
int clnt_addr_size;
if(argc!=2){
printf(“Usage : %s <port>\n”, argv[0]);
exit(1);
}
serv_sock=socket(PF_INET, SOCK_STREAM, 0);
if(serv_sock == -1)
error_handling(“socket() error”);
optlen = sizeof(option);
option = TRUE;
/* setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)); */
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htons(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)))
error_handling(“bind() error “);
if(listen(serv_sock, 5)==-1)
error_handling(“listen error”);
clnt_addr_size=sizeof(clnt_addr);
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
/* 데이터 수신 및 전송 */
while( (str_len=read(clnt_sock,message, sizeof(message))) != 0)
{
write(clnt_sock, message, str_len);
write(1, message, str_len);
}
close(clnt_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc(‘\n’, stderr);
exit(1);
}
여기서 중간에 주석처리한 부분을 해제하면 TIME-WAIT 소켓을 바로 재사용할 수 있게 됩니다. 주석을 해제하고
컴파일하여 프로그램을 실행한 뒤에. CTRL+C 로 강제 종료를 하고 다시 프로그램을 실행합니다. 아래 스샷은 TIME-WAIT
상태의 소켓을 재사용하려 할 때 오류가 발생하는 부분을 설명한 것입니다. (주석을 해제하지 않음)
[출처] TCP TIME-WAIT 상태 이해하기 |작성자 몽키몽키
———————————————————————————–
서버의 TIME_OUT 시간 변경
– 솔라리스의 경우
ndd /dev/tcp tcp_time_wait_interval
ndd -set /dev/tcp tcp_time_wait_interval 20000
———————————————————————————–
리눅스의 경우 TIME_WAIT값을 /etc/sysctl.conf 에 저장해놓고
(net.ipv4.tcp_fin_timeout = 1200 (초) 형식이라고 하네요..)
리부팅을 하던가 아니면 /proc/sys/net/ipv4/tcp_*항목을 수정하고
네트웍을 다시 시작하면 된다고 그럽니다.
(echo 1200 > /proc/sys/net/ipv4/tcp_fin_timeout )
솔라리스의 경우는 ndd유틸리티를 쓰면 됩니다.
(ndd /dev/tcp \? 라고 타이핑하시면 설정할수 있는 값들이 나오는데
거기에서 ndd -set /dev/tcp tcp**** VALUE 하시면 되는것 같습니다.
맨페이지에 자세히 나와있습니다.)
[출처] TCP/IP TIME_WAIT 상태|작성자 유진아빠
리눅스에서 HTTP 서버를 만들어서 테스트 중인데 강제로 서버를 종료하고 다시 시작을 하면 Client에서 data를 보내도 Server가 수신을 못하더군요. 종료 후 약 10초 이상 기다렸다가 다시 시작하면 제대로 동작하는데 종료를 하고 몇초 뒤에 시작하면 Server가 전혀 수신을 못하고 있어서 구글링을 열심히 해도 답을 찾지 못했는데 위의 주석 부분 코드를 복사해서 추가했더니 100% 정상 동작하네요. 정말 감사합니다.