time_wait 상태 재현하기

jinwook han·2022년 6월 5일
0
post-thumbnail

서버 클라이언트 모델에서, TIME_WAIT TCP 상태를 재현한다.
서버는 daytime 서버를 활용한다.
코드는 모두를 위한 리눅스 책 코드를 사용했다.
아래 repository의 daytime.c, daytimed.c 파일에서 코드를 볼 수 있다.

https://github.com/Jpub/Linux_for_Everyone
(위 코드에 약간의 변형을 함)

클라이언트

daytime.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

static int open_connection(char *host, char *service);

int main(int argc, char *argv[]) {
    int sock;
    FILE *f;
    char buf[1024];

    sock = open_connection((argc > 1 ? argv[1] : "localhost"), "daytime"); /* 1 */
    f = fdopen(sock, "r");
    if (!f) {
        perror("fdopen(3)");
	exit(1);
    }
    fgets(buf, sizeof buf, f); /* 5 */
    fclose(f); /* 6 */
    fputs(buf, stdout); /* 7 */
    exit(0);

}

static int open_connection(char *host, char *service) {
    int sock;
    struct addrinfo hints, *res, *ai;
    int err;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    if ((err = getaddrinfo(host, service, &hints, &res)) != 0) { /* 2 */
        fprintf(stderr, "getaddrinfo(3): %s\n", gai_strerror(err));
	exit(1);
    }
    
    for (ai = res; ai; ai = ai -> ai_next) {
        sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); /* 3 */
	if (sock < 0) {
	    continue;
	}
	if (connect(sock, ai -> ai_addr, ai -> ai_addrlen) < 0) { /* 4 */
	    close(sock);
	    continue;
	}
	freeaddrinfo(res);
	return sock;
    }
    fprintf(stderr, "socket(2)/connect(2) failed");
    freeaddrinfo(res);
    exit(1);
}
  1. 인자에 따라 localhost 또는 외부 ip를 넘긴다. 서비스명은 daytime으로 설정해서, port 13번에 대해서 연결할 수 있도록 한다.
  2. host name으로 ip를 조회한다.
  3. 2에서 얻은 정보로 socket 구조체를 생성한다.
  4. 서버와 연결한다.
  5. 서버에서 받은 입력(time 정보)를 버퍼로 옮긴다.
  6. 서버와 커넥션을 끊는다.
  7. 버퍼의 내용을 출력한다.

서버

daytimed.c

#if defined(__digital__) && defined(__unix__)
# ifdef _XOPEN_SOURCE
#  undef _XOPEN_SOURCE
# endif
# define _XOPEN_SOURCE 500
# define _OSF_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <signal.h>

#define DEFAULT_PORT 13

static int listen_socket(int port);

int main(int argc, char *argv[]) {
    struct sockaddr_storage addr;
    socklen_t addrlen = sizeof addr;
    int sock, server;
    time_t t;
    struct tm *tm;
    char *timestr;

    server = listen_socket(argc > 1? atoi(argv[1]) : DEFAULT_PORT); /* 1 */

    for(;;) {
        sock = accept(server, (struct sockaddr*)&addr, &addrlen); /* 6 */
    
        if (sock < 0) {
            perror("accept(2)");
	    exit(1);
        }

        time(&t);
        tm = localtime(&t);
        timestr = asctime(tm);
        write(sock, timestr, strlen(timestr)); /* 7 */
        close(sock); /* 8 */
    }
    close(server);
    exit(0);
}

static int listen_socket(int port) {
    struct addrinfo hints, *res, *ai;
    int err;
    char service[16];
    
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    snprintf(service, sizeof service, "%d", port);

    if ((err = getaddrinfo(NULL, service, &hints, &res))!= 0) { /* 2 */
	fprintf(stderr, "%s\n", gai_strerror(err));
	exit(1);
    }

    for (ai = res; ai; ai = ai->ai_next) {
	int sock;

	sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); /* 3 */
	if (sock < 0) continue;
	if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { /* 4 */
	    close(sock);
	    continue;
	}
	if (listen(sock, 5) < 0) { /* 5 */
	    close(sock);
	    continue;
	}
	freeaddrinfo(res);
	fprintf(stderr, "listening on port %d...\n", port);
	return sock;
    }
    fprintf(stderr, "cannot listen socket\n");
    exit(1);
}
  1. DEFAULT_PORT:13 또는 인자로 받은 포트로 함수를 호출한다.
  2. 도메인 이름으로 주소 구조체를 얻는다.
  3. 2에서 얻은 주소 구조체로 socket을 생성한다.
  4. bind 호출
  5. listen 호출
  6. 클라이언트와 접속한다.
  7. time 정보를 소켓에 write한다.
  8. write한 후 클라이언트와 연결을 끊는다.

테스트

1.. daytime 서버를 실행한다.

2.. client 프로그램도 실행한다. client는 서버를 호출한다.

결과가 출력됐다.
이 시점에서 서버, 클라이언트는 모두 연결이 종료됐다.

3.. 즉시 netstat으로 소켓 상태를 확인한다.

다음 명령어를 사용한다.

netstat -a | netstat grep daytime

TIME_WAIT 상태를 확인할 수 있다.
서버가 먼저 close를 호출했기 때문에, 서버 측이 active close가 된다.
따라서 서버의 소켓 상태가 TIME_WAIT이 된다.

** netstat 표의 컬럼은 Proto,Recv-Q,Send-Q,Local Address,Foreign Address,(state)순이다.

** TIME_WAIT 시간은 60초로 하드코딩되어 있다고 한다. 운영체제에 따라 값이 다를 수는 있을 것 같다. (https://tech.kakao.com/2016/04/21/closewait-timewait/)

참고자료

https://github.com/Jpub/Linux_for_Everyone
https://tech.kakao.com/2016/04/21/closewait-timewait/
https://linux.die.net/man/8/netstat

0개의 댓글