나의 브을로오그으

#9. 소켓의 옵션과 입출력 버퍼의 크기 본문

네트워크/열혈 TCP_IP 소켓프로그래밍

#9. 소켓의 옵션과 입출력 버퍼의 크기

__jhp_+ 2022. 8. 8. 20:52

소켓의 다양한 옵션

Protocol Level Option Name Get Set
SOL_SOCKET SO_SNDBUF
SO_RCVBUF
SO_REUSEADDR
SO_KEEPALIVE
SO_BROADCAST
SO_DONTROUTE
SO_OOBINLINE
SO_ERROR
SO_TYPE
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
X
X
IPPROTO_IP IP_TOS
IP_TTL
IP_MULTICAST_TTL
IP_MULTICAST_LOOP
IP_MULTICAST_IF
O
O
O
O
O
O
O
O
O
O
IPPROTO_TCP TCP_KEEPALIVE
TCP_NODELAY
TCP_MAXSEG
O
O
O
O
O
O

 

getsockopt & setsockopt

표 09-1에서 보이듯이 거의 모든 옵션은 설정상태의 참조(Get) 및 변경(Set)이 가능하다(물론 참조만, 그리고 설정만 가능한 옵션도 있다). 그리고 옵션의 참조및 변경에는 다음 두 함수를 사용한다.

 

[소켓의 옵션을 확인하는 함수]

#include <sys/socket.h>

int getsockopt(int sock, int level, int optname, void* optval, socklen_t* optlen);
-> 성공 시 0, 실패 시 -1

sock : 옵션확인을 위한 소켓의 파일 디스크립터 전달.

level : 확인할 옵션의 프로토콜 레벨 전달.

optname : 확인할 옵션의 이름 전달.

optval : 확인결과의 저장을 위한 버퍼의 주소 값 전달.

optlen : 네 번째 매개변수 optval로 전달된 주소 값의 버퍼크기를 담고 있는 변수의 주소 값 전달, 함수호출이 완료되면 이 변수에는 네 번째 인자를 통해 반환된 옵션정보의 크기가 바이트 단위로 계산되어 저장된다.

 

[소켓의 옵션을 변경하는 함수]

#include <sys/socket.h>
int setsockopt(int sock, int level, int optname, const void* optval, socklen_t optlen);
-> 성공 시 0, 실패 시 -1 반환

sock : 옵션변경을 위한 소켓의 파일 디스크립터 전달.

level : 변경할 옵션의 프로토콜 레벨 전달.

optname : 확인할 옵션의 이름 전달

optval : 변경할 옵션정보를 저장한 버퍼의 주소 값 전달.

optlen : 네 번째 매개변수 Optval로 전달된 옵션정보의 바이트 단위 크기 전달.

 

/* linux */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
void error_handling(char* message);

int main(int argc, char* argv[])
{
	int tcp_sock, udp_sock;
	int sock_type;
	socklen_t optlen;
	int state;

	optlen = sizeof(sock_type);
	tcp_sock = socket(PF_INET, SOCK_STREAM, 0);
	udp_sock = socket(PF_INET, SOCK_DGRAM, 0);
	printf("SOCK_STREAM: %d \n", SOCK_STREAM);
	pritnf("SOCK_DGRAM: %d \n", SOCK_DGRAM);
	
	state = getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
	if (state)
		error_handling("getsockopt() error!");
	printf("Socket type one: %d \n", sock_type);

	state = getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
	if (state)
		error_handling("getsockopt() error!");
	printf("Socket type two: %d \n", sock_type);
}

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

TCP, UDP 소켓을 각각 생성하고 있다.

TCP, UDP 소켓 생성시 인자로 전달하는 SOCK_STREAM, SOCK_DGRAM의 상수 값을 출력하고 있다.

소켓의 타입정보를 얻고 있다. TCP 소켓이라면 SOCK_STREAM의 상수 값인 1을 얻게 될 것이고, UDP 소켓이라면, SOCK_DGRAM의 상수 값인 2를 얻게 될 것이다.

 

 

[실행결과]

root@my_linux:/tcpip# gcc sock_type.c -o socktype
root@my_linux:/tcpip# ./socktype
SOCK_STREAM: 1
SOCK_DGRAM: 2
Socket type one: 1
Socket type two: 2

(참고로 SO_TYPE은 확인은 가능하지만 변경은 불가능한 대표적인 옵션 - 소켓의 타입 정보 확인을 위한 옵션. 소켓의 타입은 소켓 생성시 한번 결정되면 변경이 불가능하다!.)

 

 

SO_SNDBUF & SO_RCVBUF

소켓이 생성되면 기본적으로 입력버퍼와 출력버퍼가 생성된다고 하였다.  두 옵션의 버퍼의 크기와 관련된 옵션이다. 이 두 옵션을 이용해서 버퍼의 크기 참조 뿐만 아니라 변경도 가능하다.

 

[get_buf.c]

/* linux */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
void error_handling(char* message);

int main(int argc, char* argv[])
{
	int sock;
	int snd_buf, rcv_buf, state;
	socklen_t len;

	sock = socket(PF_INET, SOCK_STREAM, 0);
	len = sizeof(snd_buf);
	state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len);
	if (state)
		error_handling("getsockopt() error");

	len = sizeof(rcv_buf);
	state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, &len);
	if (state)
		error_handling("getsockopt() error");
	printf("Input buffer size: %d \n", rcv_buf);
	printf("Output buffer size: %d \n", snd_buf);

	return 0;
}

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

 

[실행결과]

root@my_linux:/tcpip# gcc get_buf.c -o getbuf
root@my_linux:/tcpip# ./getbuf
Input buffer size: 87380
Output buffer size: 16384

 

 

[set_buf.c]

/* linux */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
void error_handling(char* message);

int main(int argc, char* argv[])
{
	int sock;
	int snd_buf=1024*3, rcv_buf=1024*3;
	int state;
	socklen_t len;

	sock = socket(PF_INET, SOCK_STREAM, 0);
	state = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, sizeof(rcv_buf));
	if (state)
		error_handling("setsockopt() error");

	state = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, sizeof(snd_buf));
	if (state)
		error_handling("setsockopt() error");

	len = sizeof(snd_buf);
	state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)snd_buf, &len);
	if (state)
		error_handling("getsockopt() error");

	len = sizeof(rcv_buf);
	state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)rcv_buf, &len);
	if (state)
		error_handling("getsockopt() error");

	printf("Input buffer size: %d \n", rcv_buf);
	printf("Output buffer size: %d \n", snd_buf);
	return 0;
}

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

 

[실행결과]

root@my_linux:/tcpip# gcc set_buf.c -o setbuf
root@my_linux:/tcpip# ./setbuf
Input buffer size: 6144
Output buffer size: 6144

원래라면 1024*3크기로 설정되어야 하지만 초과해서 설정되었다. 이유가 뭘까? 만약 입력버퍼의 크기를 0으로 한다면?

tcp 통신을 할 수 없을것이다. 따라서 내부적으로 입출력 버퍼의 최소 크기를 OS 내부에서 지정해 놓고 setsockopt() 하려는 크기가 이보다 작다면, 적용이 되지 않는다. 

다만 우리가 확신 할 수 있는것은 우리가 호출한 setsockopt() 함수 호출시 버퍼의 크기에 영향을 주었다는 것이다.

 

 

#2. SO_REUSEADDR

SO_REUSEADDR옵션과 그와 관련 있는 Time-wait 상태는 상대적으로 중요하다.

/* linux */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.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, clnt_sock;
	char message[30];
	int option, str_len;
	socklen_t optlen, clnt_adr_sz;
	struct sockaddr_in serv_adr, clnt_adr;
	if (argc != 2) {
		printf("Usage : %s <port> \n");
		exit(1);
	}

	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	if (serv_sock == -1)
		error_handling("socket() error");
	/*
	optlen = sizeof(optlen) 
	option = TRUE;
	setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void*)&option, optlen);
	*/

	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_adr.sin_port = htons(atoi(argv[1]));

	if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)))
		error_handling("bind() error");
	if (listen(serv_sock, 5) == -1)
		error_handling("listen() error");
	clnt_adr_sz = sizeof(clnt_adr);
	clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
	
	while ((str_len = read(clnt_sock, message, sizeof(message))) != 0)
	{
		write(clnt_sock, message, str_len);
		write(1, message, str_len);
	}
	close(clnt_sock);
	close(serv_sock);
	return 0;
}

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

단순한 에코 서버 프로그램이다.

이렇게 두고, 에코 클라이언트 프로그램에서 Ctrl+c 또는 q가 입력되었을 때 프로그램을 종료시킨다.

즉, 서버측으로 종료를 먼저 알리게끔 하라는 뜻이다. 그러면 클라이언트 콘솔에서 Q메시지를 입력하면 close 함수를 호출하게 되어 서버측으로 FIN 메시지를 먼저 전송하면서 Four-ways handshaking 과정을 거치게 된다.

CTRL+c 키를 입력해도 서버측으로 FIN 메시지를 전송한다. 보통은 클라이언트 측에서 연결 종료를 요청하기 때문에 매우 일반적인 상황이다.

그러나 서버와 클라이언트가 연결된 상태에서 서버 측 콘솔에서 CTRL+C를 입력한다. 즉, 서버 프로그램을 강제 종료한다.

이는 서버 -> 클라이언트 측으로 FIN 메시지를 전달하는 경우를 의미한다. 이런 경우 서버를 다시 재실행해보면 bind() error라는 메시지가 출력된다. 이유는 동일한 PORT 번호를 기준으로 서버를 재실행했기 때문이다. 물론 약간의 시간이 지난 이후 다시 재실행해보면 잘 될것이다.

 

Time-wait 상태

사실 TCP 소켓에서 연결 종료 시 Four-ways handshaking 이후에 소켓이 바로 소멸되지 않고 Time-wait 상태라는 것을 일정시간 거친다. 물론 Time-wait 상태는 먼저 연결의 종료를 요청한(먼저 FIN 메시지를 전송한)호스트만 거친다. 이 때문에 서버가 먼저 연결의 종료를 요청해서 종료하고 나면, 바로 이어서 실행할 수 없는 것이다. 소켓이 Time-wait 상태에 있는 동안에는 해당 소켓의 PORT번호가 사용중인 상태이기 때문이다. 

따라서 앞서 bind함수 호출 시 error가 나는 이유는 당연하다.

 

 

[클라이언트 소켓은 Time-wait 상태를 거치지 않을까?]

Time-wait 상태는 서버에만 존재하는 것이라는 오해가 있다. 그러나 소켓의 TIme-wait 상태는 클라이언트냐 서버냐에 상관없이 존재한다. 먼저 연결의 종료를 요청하면 해당 소켓은 반드시 Time-wait 상태를 거친다. 그러나 클라이언트의 Time-wait 상태는 신경을 쓰지 않아도 된다. 왜냐하면 클라이언트 소켓의 PORT번호는 임의로 할당되기 떄문이다. 즉, 서버와 달리 프로그램이 실행될 때마다 PORT번호가 유동적으로 할당되기 때문에 Time-wait 상태에 대해 신경을 쓰지 않아도 된다.

 

 

주소의 재할당

그러나 Time-wait가 늘 반가운 것은 아니다. 시스템에 문제가 생겨서 서비스 중인 서버가 갑작스럽게 종료된 상황을 생각해보자. 다시 재빨리 서버를 가동시켜야 하지만, Time-wait 상태 때문에 몇 분을 기다릴 수밖에 없다면 이는 문제가 될 수 있다. 또한 Time-wait 상태는 상황에 따라서 더 길어질 수 있어서 더 큰 문제로 이어질 수 있다

예를 들어 호스트 A가 전송하는 Four-way handshaking 과정에서 마지막 데이터가 손실이 되면, 호스트 B는 자신이 보낸 FIN 메시지를 다시 보낸다. 그러면 FIN 메시지를 수신한 호스트 A는 Time-wait 타이머를 재가동한다. 이때 네트워크 상황이 좋지 못하다면, 해당 작업이 계속해서 반복적으로 일어날 수 있다.

따라서 이런 문제를 해결하기 위한 해결책으로써 소켓의 옵션 중에서 SO_REUSEADDR의 상태를 변경하면 된다. 이의 적절한 변경을 통해서 Time-wait 상태에 있는 소켓에 할당되어 있는 PORT번호를 새로 시작하는 소켓에 할당되게끔 할 수 있다. SO_REUSEADDR의 디폴트 값은 0(FALSE)인데, 이는 Time-wait 상태에 있는 소켓의 PORT번호는 할당이 불가능함을 의미한다. 그래서 이를 단지 TRUE(1)로 바꾸어 setsockopt 함수를 호출해 값을 집어넣어 주면 된다.

optlen=sizeof(option);
option=TRUE; // 1
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void*)&option, optlen);

 

 

#3. TCP_NODELAY

 

Nagle 알고리즘

Nagle 알고리즘은 네트워크상에서 돌아다니는 패킷들의 흘러 넘침을 막기 위해서 1984년에 제안된 알고리즘이다. 이는 TCP상에서 적용되는 매우 단순한 알고리즘으로 알고리즘 적용 시 데이터 송신 -> ACK 메시지를 받아야만 다음 데이터를 전송한다. 기본적으로 TCP소켓은 Nagle 알고리즘을 적용해서 데이터를 송수신한다. 때문에 ACK가 수신될 때까지 최대한 버퍼링을 해서 데이터를 전송한다. 때문에 송신측이 ACK 메시지를 수신 될 때까지 최대한 버퍼링을 해서 데이터를 전송한다. 

예를 들어 "Nagle"을 보낸다고 해보자.

처음 N이 출력버퍼로 들어올 때 (수신할 ACK가 없으므로) 곧바로 전송이 이뤄진다. 그리고 'N'에 대한 ACK를 기다리는 동안 나머지 "agle"가 출력버퍼에 버퍼링되며, 이후 'N'에 대한 ACK 메시지를 수신하면, "agle"을 하나의 패킷으로 구성해서 전송한다. 즉, 하나의 문자열에 4개의 패킷이 송수신 되는 것이다.

 

이번에는 Nagle알고리즘 적용 없이 예를 들어보자.

"Nagle"을 출력버퍼로 버퍼링을 하는데 따로 ACK메시지를 받지 않기 때문에 'N'을 수신한 순간 곧바로 송신하게 된다.

이후 ACK의 수신에 상관 없이 "agle"이 출력버퍼로 들어오는 순서대로 바로바로 전송하게 되는데 이 과정을 통해 패킷 10개가 송수신 된다.

이렇듯  Nagle 알고리즘을 적용하지 않으면 네트워크 트래픽(Traffic: 네트워크에 걸리는 부하나 혼잡의 정도)에는 좋지 않은 영향을 미친다. 1바이트를 전송하더라도 패킷에 포함되어야 하는 헤더 정보의 크기가 수십 바이트에 이르기 때문에 

네트워크의 효율적인 사용을 위해서는 Nagle 알고리즘을 반드시 적용해야 한다.(위의 예시는 극단적인 예시임)

 

※참고 : 프로그램상에서 문자열을 출력버퍼로 전달할 때, 한 문자씩 전달하지 않고 전체 문자열을 한번에 전달하기 때문에 문자열 Nagle의 전송이 1바이트씩 이루어지지는 않을 것이다. 하지만 문자열을 이루는 문자가 약간의 시간간격을 두고 출력버퍼로 전달된다면 1바이트씩 송수신이 이루어지는 상황이 연출될 수 있다. 

 

그러나!! Nagle알고리즘이 항상 좋은것은 아니다. 전송하는 데이터의 특성에 따라서 Nagle 알고리즘의 적용 여부에 따른 트래픽의 차이가 크지 않으면서도 Nagle 알고리즘을 적용하는 것보다 데이터의 전송이 빠른 경우도 있다. 대표적인 것이 '큰 용량의 데이터 전송'이다. 파일 데이터를 출력버퍼로 밀어 넣는 작업은 시간이 걸리지 않는다. 때문에 Nagle 알고리즘을 적용하지 않아도 출력 버퍼를 거의 꽉 채운 상태에서 패킷을 전송하게 된다. 따라서 패킷의 수가 크게 증가하지 않을 뿐더러, ACK를 기다리지 않고 연속해서 데이터를 전송하니 전송속도도 놀랍게 향상된다.

일반적으로 Nagle 알고리즘을 적용하지 않으면 속도의 향상을 기대할 수 있으나, 무조건 Nagle 알고리즘을 적용하지 않을 경우에는 트래픽에 상당한 부담을 주게 되어 더 좋지 않은 결과를 얻을 수 있다.  따라서 데이트의 특성을 정확히 알지 않고 Nagle 알고리즘을 중단하는 일은 없어야 한다.

 

"Nagle 알고리즘을 중단해야 하는 경우는 해당 알고리즘의 적용 여부에 따른 트래픽의 차이가 크지 않으면 Nagle 알고리즘을 적용하지 않는것이 데이터 전송에 빠르다. "

int opt_val = 1;
setsockopt(sock, IPPROT_TCP, TCP_NODELAY, (void*)&opt_val, sizeof(opt_val));

이렇게 TCP_NODELAY 값을 1(TRUE)로 변경해주면 된다.

 

 

#4. 윈도우 기반으로 구현하기

#include <WinSock2.h>
int getsockopt(SOCKET sock, int level, int optname, char* optval, int* optlen);
-> 성공 시 0, 실패 시 SOCKET_ERROR 반환

sock: 옵션확인을 위한 소켓의 핸들 전달.

level: 확인할 옵션의 프로토콜 레벨 전달.

optname: 확인할 옵션의 이름 전달.

optval: 확인결과의 저장을 위한 버퍼의 주소 값 전달.

optlen: 네 번째 매개변수 optval로 전달된 주소 값의 버퍼 크기를 담고 있는 변수의 주소 값 전달, 함수호출이 완료되면 이 변수에는 네 번째 인자를 통해 봔환된 옵션정보의 크기가 바이트 단위로 계산되어 저장된다.

 

※ 한 가지 주의점은 매개변수 optval의 자료형이 char형 포인터라는 것이다. 리눅스 기반에서는 void형 포인터였다. 따라서 적절히 형변환을 해주자.

#include <WinSock2.h>

int setsockopt(SOCKET sock, int level, int optname, const char* optval, int optlen);
-> 성공 시 0, 실패 시 SOCKET_ERROR 반환

sock: 옵션변경을 위한 소켓의 핸들 전달.

level: 변경할 옵션의 프로토콜 레벨 전달.

optname: 변경할 옵션의 이름 전달.

optval: 변경할 옵션정보를 저장한 버퍼의 주소 값 전달.

optlen: 네 번째 매개변수 optval로 전달된 옵션정보의 바이트 단위 크기 전달.

 

 

[buf_win.c]

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>

#pragma comment(lib, "ws2_32.lib")

void ErrorHandling(char* message);
void ShowSocketBufSize(SOCKET sock);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSock;
	int sndBuf, rcvBuf, state;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error");

	hSock = socket(PF_INET, SOCK_STREAM, 0);
	ShowSocketBufSize(hSock);

	sndBuf = 1024 * 3, rcvBuf = 1024 * 3;
	state = setsockopt(hSock, SOL_SOCKET, SO_SNDBUF, (const char*)&sndBuf, sizeof(sndBuf));
	if (state == SOCKET_ERROR)
		ErrorHandling("setsockopt() error");

	state = setsockopt(hSock, SOL_SOCKET, SO_RCVBUF, (const char*)&rcvBuf, sizeof(rcvBuf));
	if (state == SOCKET_ERROR)
		ErrorHandling("setsockopt() error");

	ShowSocketBufSize(hSock);
	closesocket(hSock);
	WSACleanup();
	return 0;
}

void ShowSocketBufSize(SOCKET hSock)
{
	int sndBuf, rcvBuf, state, len;
	len = sizeof(sndBuf);
	state = getsockopt(hSock, SOL_SOCKET, SO_SNDBUF, (char*)&sndBuf, &len);
	if (state == SOCKET_ERROR)
		ErrorHandling("getsockopt() error");

	len = sizeof(rcvBuf);
	state = getsockopt(hSock, SOL_SOCKET, SO_RCVBUF, (char*)&rcvBuf, &len);
	if (state == SOCKET_ERROR)
		ErrorHandling("getsockopt() error");

	printf("Input buffer size: %d \n", sndBuf);
	printf("Output buffer size: %d \n", rcvBuf);

	return;
}

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

 

내용 확인문제

01. 다음 중 Time-wait 상태에 대해서 잘못 설명한 것을 모두 고르면?

a. Time-wait 상태는 서버 프로그램에서 생성한 소켓에서만 발생한다.

b. 연결종료의 Four-way handshaking 과정에서 먼저 FIN 메시지를 전달한 소켓이 Time-wait 상태가 된다.

c. 연결요청 과정에서 전송하는 SYN 메시지의 전송순서에 따라서 Time-wait 상태는 연결종료와 상관없이 일어날 수 있다.

d. Time-wait 상태는 불필요하게 발생하는 것이 대부분이므로, 가급적이면 발생하지 않도록 소켓의 옵션을 변경해야 한다.

답)

a. Time-wait 상태는 서버, 클라이언트 구분 없이 FIN신호를 보낸(연결 종료를 한) 소켓에서 발생한다. 참고로 클라이언트의 Time-wait 상태는 어차피 PORT번호가 임의로 할당되므로 신경쓰지 않아도 된다.

c. Time-wait 상태는 반드시 FIN 메시지를 전송받아야 일어난다.

d. 이런경우는 소켓의 경우 서비스 중인 서버가 다운되면, 다시 재가동 시켜야 하므로 이러한 Time-wait 상태가 불필요하겠지만, 호스트 A와 B가 있다고 가정했을 때 호스트A가 종료되어 ACK를 B로 보냈는데, 호스트 B가 전송받지 못했다고 생각되어 재전송 시도를 해야한다. 그러나 이미 호스트 A가 종료되었기 때문에 호스트B는 영원히 FIN 메시지를 받지 못한다.

그래서 Time-wait 상태가 필요하다. 이 상태에 놓여도 호스트B로 마지막 ACK메시지를 재전송 할 수 있고,  호스트 B는 정상적으로 종료할 수 있게 된다.

 

 

02. 옵션 TCP_NODELAY는 Nagle 알고리즘과 관련이 있다. 이 옵션을 이용해서 Nagle 알고리즘을 해제할 수도 있는데, 그렇다면 어떠한 경우에 한해서 Nagle 알고리즘의 해제를 고민해 볼 수 있겠는가? 이를 송수힌하는 데이터의 특성과 관련해서 설명해보자.

답)

Nagle알고리즘을 적용하나 해제하나 네트워크 트래픽이 비슷하고, Nagle알고리즘을 적용하지 않는것보다 데이터 전송이 빠르다면 Nagle 알고리즘을 해제하는 것이 유리하다. 그리고 또 '대용량 데이터 송수신"과 같은 데이터 송수신에도 Nagle 알고리즘을 해제하는 것이 유리한데, 보내야 할 데이터가 많다보니 Nagle 알고리즘과 상관 없이 출력버퍼에 충분한 버퍼링이 이루어져서 데이터가 전달되기 때문이다.