Part1_소켓의 다양한 옵션(2)

·2023년 11월 19일
0
post-custom-banner

[SO_REUSEADDR]

(1) 주소 할당 시 에러의 발생

// reuseaddr.c

#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 = htonl(INADDR_ANY);
	serv_addr.sin_prot = 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, messagem sizeof(message))) != 0)
	{
		write(clnt_sock, message, str_len);
		write(1, message, str_len);
	}

	close(clnt_sock);
	return 0;
}

void error_handlinb(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

[실행 결과]

  • 클라이언트에서 연결 종료 요청하기
    • ‘q’ 누르고 엔터를 누르게 되면 소켓이 종료가 되면서 프로그램도 종료함 이렇게 해서 소켓을 종료 해도 4 way handshake 과정을 거치게 됨 다시 서버를 실행시켜도 서버가 바로 실행됨 → 즉, 소켓을 종료했다고 해서 바로 소멸되는 것이 아님

  • 서버에서 연결 종료 요청하기
    • Ctrl + C키를 누르게 되면 4-way handshake 과정을 거치게 됨 다시 서버를 실행시키면 bind 함수 호출에서 문제가 발생됨 대략 3분 정도 지난 후 다시 실행하면 정상적으로 동작함

⇒ 종료 요청을 누가 했느냐에 따라 결과 값이 달라지고 있음

(2) TIME-WAIT 상태

위 그림은 서버가 프로그램 상에서 Ctrl + C키를 누른 상황임

그림과 같이 4 way handshake가 끝난 상태에서 A의 소켓이 바로 소멸되는 것이 아니라 TIME-WAIT 상태로 들어감

이전 예제에서 오류가 난 이유는 서버측 소켓이 소멸되지 않고 TIME-WAIT 상태일 때 (서버가 여전히 존재할 때) bind 함수를 호출했기 때문임 (이미 IP와 Port가 사용중이므로)

(참고) TIME-WAIT 상태는 무엇 때문에 존재할까?

만약 A → B로 마지막 종료 메시지인 ACK를 전송하고 나서 바로 종료되었다고 가정해 보자.

그런데 A가 B에게 마지막으로 전달한 이 ACK가 중간에 소멸되어 버렸다면 B 클라이언트는 FIN 메시지를 받지 못했다고 생각하고 다시 한 번 FIN을 전송하게 됨

→ 그러나 A는 이미 종료된 상태에 있기 때문에 B는 영원히 FIN에 대한 마지막 ACK 메시지를 못 받게 됨

TIME-WAIT 상태로 대기중에 있었다면 마지막 ACK의 재전송이 진행되었을 것이고 B는 무사히 종료하게 됨

만약 한 명의 접속자(클라이언트)도 없다면 FIN 메시지 전송을 시작으로 4 -way handshake를 거치지 않음

→ TIME-WAIT 상태에 들어 가지 않음

만약 서버가 보낸 ACK 메시지가 클라이언트에게 도착하기 전에 손실되어 TIME-WAIT 상태에 있는 서버가 기존에 연결되어 있던 클라이언트로부터 패킷을 수신하게 되면 TIME-WAIT 상태를 유지하는 타이머는 다시 시작함

→ 생각보다 TIME-WAIT 상태가 길어질 수 있음

어떻게 해결해야 할까?

소켓의 옵션 중 하나인 SO_REUSEADDR의 상태를 변경 해 주면 됨

옵션 값이 1인 경우에는 TIME-WAIT 상태에 있는 소켓에 할당되어 있는 IP 주소와 Port를 새로 시작하는 소켓에 할당해 주게 됨

(단, 디폴트 값이 0이므로 반드시 변경을 해 줘야 함 → 위 예제에서 주석 처리를 해제하면 문제 없이 서버가 바로 동작함)

[TCP_NODELAY, Nagle 알고리즘]

Nagle 알고리즘은 네트워크 상에서 돌아다니는 패킷들이 흘러 넘치는 일이 없도록 하기 위해서 제안된 알고리즘

TCP 프로토콜 상에서 구현되며 단순하다는 특징을 가짐

→ ‘기존에 전송한 패킷이 있을 경우 그 패킷에 대한 ACK를 받아야만 다음 전송을 진행하는 알고리즘’

Nagle 알고리즘을 사용하지 않으면 전송할 데이터가 있는 경우 ACK를 기다리지 않고 바로 전송하기 때문에 네트워크 트래픽에는 좋지 않음

(1바이트를 전송하더라도 기본적으로 포함되어야 하는 헤더의 크기가 수십 바이트에 이르기 때문)

하지만 장점만 있는 것은 아님 → Nagle 알고리즘을 적용하지 않은 경우가 더 빠름

(TCP 소켓은 디폴트로 Nagle 알고리즘을 적용하고 있으며 반드시 분명한 이유가 있을 때만 Nagle 알고리즘을 중지시켜야 기대하는 효과를 얻을 수 있음)

[Nagle 알고리즘의 중단 요청]

TCP_NODELAY 옵션이 1(TRUE)인 경우 Nagle 알고리즘을 적용하지 않겠다는 의미임

// nagle.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>

#define TRUE 1
#define FALSE 0

int main(int argc, char** argv)
{
	int sock;
	int state, opt_val, opt_len;

	sock = socket(PF_INET, SOCK_STREAM, 0);

	// 디폴트 nagle 알고리즘 설정 상태 확인
	state = getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt_val, &opt_len);

	if(state)
	{
		puts("getsockopt() error!");
		exit(1);
	}

	printf("디폴트 nagle 알고리즘 : %s \n", opt_val ? "비설정" : "설정");

	// nagle 알고리즘을 diable 시킴
	opt_val = TRUE;
	state = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt_val, sizeof(opt_val));

	if(state)
	{
		puts("setsockopt() error!");
		exit(1);
	}

	// 변경된 nagle 알고리즘 확인
	getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt_val, &opt_len);
	printf("변경된 nagle 알고리즘 : %s \n", opt_val ? "비설정" : "설정");

	close(sock);
	return 0;
}

[실행 결과]

post-custom-banner

0개의 댓글