[Socket] 소켓의 다양한 옵션

지금까지는 소켓을 생성해서 별다른 조작없이 바로 사용해 왔다. 이러한 경우는 기본적으로 설정되어 있는 소켓의 특성을 바탕으로 데이터를 송수신하게 된다. 하지만, 소켓의 특성을 변경시켜야만 하는 경우도 흔히 발생한다.

다양한 소켓의 옵션 중 일부

다양한 소켓의 옵션 중 일부

위의 표에서 보이듯이 소켓의 옵션은 계층별로 분류된다. IPPORTO_IP 레벨의 옵션들은 IP프로토콜에 관련된 사항들이며, IPPORTO_TCP 레벨의 옵션들은 TCP 프로토콜에 관련된 사항들이다. SOL_SOCKET 레벨의 옵션들은 소켓에 대한 가장 일반적인 옵션들로 생각하면 된다.

getsockopt & setsockopt

소켓의 옵션을 확인할 때 호출하는 함수는 getsockopt이다.

#include <sys/socket.h>

int **getsockopt**(int sock, int level, int optname, void* optval, socklen_t* optlen);
//성공 시 0, 실패 시 -1 return
  • optval : 결과 저장을 위한 버퍼
  • optlen : optval로 전달된 주소 값의 버퍼크기
  • 위의 표에서 제시한 protocol level, option name이 각각 두 번째, 세 번째 인자로 전달되어, 해당 옵션의 등록 정보를 얻어온다.

소켓의 옵션을 변경할 때 호출하는 함수는 setsockopt이다.

#include <sys/socket.h>

int **setsockopt**(int sock, int level, int optname, const void* optval, socklen_t optlen);
//성공 시 0, 실패 시 -1 return
  • optval : 변경할 옵션정보를 저장한 버퍼의 주소 값
  • optlen : 여기서는 보내주는 것이기 때문에 정수값이다. 받아 올 경우(get)는 pointer를 사용한다.

소켓이 생성되면 기본적으로 입력버퍼와 출력버퍼가 생성된다. 이와 관련된 옵션이 있는데,
SO_RCVBUF = 입력버퍼의 크기와 관련된 옵션
SO_SNDBUF = 출력버퍼의 크기와 관련된 옵션
이 두 옵션을 이용해서 입출력 버퍼의 크기를 참조, 변경이 가능하다.

하지만, 입출력 버퍼는 상당히 주의 깊게 다뤄져야 하는 영역이기 때문에, 요구하는 바가 완벽히 반영되지는 않는다.

SO_REUSEADDR

time-wait

서버, 클라이언트에 상관없이 TCP소켓에서 연결종료를 목적으로 4-way handshaking의 첫 번째 메세지를 전달하는 호스트의 소켓은 Time-wait 상태를 거친다. Time-wait 상태 동안에는 해당 소켓이 소멸되지 않아서 할당 받은 Port를 다른 소켓이 할당할 수 없다.

Untitled

time-wait은 호스트A(첫 번째 메시지를 전달하는 호스트)의 마지막 ACK가 소멸되는 상황을 대비한 것이다. 호스트A의 마지막 ACK가 소멸되면, 호스트B는 계속해서 FIN 메시지를 호스트A에게 전달하게 된다.

주소의 재할당

Time-wait는 매우 중요하지만, 늘 반가운 것은 아니다. 예를 들어, 시스템에 문제가 생겨 서버가 갑작스럽게 종료된 상황을 생각해보자. 빨리 서버를 재 가동시켜 서비스를 이어가야 하는데, time-wait 상태 때문에 몇 분을 기다릴 수밖에 없다면 이는 문제가 될 수 있다.

이를 위해, Time-wait 상태에 있는 소켓에 할당되어 있는 PORT번호를 새로 시작하는 소켓에 할당되게끔 해야한다. 아래와 같이 SO_REUSEADDR을 사용하여 옵션을 변경해주면 된다.

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

TCP_NODELAY

Nagle algorithm

Nagle알고리즘은 네트워크상에서 돌아다니는 패킷들의 흘러 넘침을 막기 위해 제안된 알고리즘이다.

Untitled

Nagle알고리즘은 앞서 전송한 데이터에 대한 ACK메시지를 받아야만, 다음 데이터를 전송하는 알고리즘.

기본적으로 TCP는 Nagle알고리즘을 적용해서 데이터를 송수신한다.

위의 그림을 살펴보면, “Nagle”을 전송하기 위해 이를 출력버퍼로 전달한다. 이 때 첫 문자 ‘N’이 들어온 시점에서는 이전에 전송한 패킷이 없으므로(수신할 ACK가 없다.) 바로 전송이 이뤄진다. 그리고 ‘N’에 대한 ACK를 기다리게 되는데, 기다리는 동안에 출력버퍼에는 문자열의 나머지 “agle”이 채워진다. 이어서 ‘N’에 대한 ACK를 수신하고 출력버퍼에 존재하는 “agle”을 하나의 패킷으로 구성해서 전송한다.

Nagle알고리즘을 사용하지 않을 경우, 문자가 순서대로 출력버퍼로 전달된다고 가정해보자. 여기서는 ACK의 수신에 상관없이 패킷의 전송이 이뤄지기 때문에 출력버퍼에 데이터가 전달되는 즉시 전송이 이뤄진다. 따라서 총 10개의 패킷이 송수신될 수 있다. 이렇게 되면, 네트워크 트래픽에 좋지 않은 영향을 미친다. 1바이트를 전송하더라도 패킷에 포함되어야 하는 헤더정보의 크기가 수십 바이트에 이르기 때문이다.

따라서, 네트워크의 효율적인 사용을 위해서는 Nagle 알고리즘을 반드시 적용해야 한다.


하지만, 때에 따라서는 Nagle알고리즘을 적용하지 않을 경우 속도의 향상을 기대할 수 있는 상황이 존재한다. 바로, “Nagle 알고리즘의 적용 여부에 따른 트래픽의 차이가 크지 않으면서도 Nagle알고리즘을 적용하는 것보다 데이터의 전송이 빠른 경우”이다. 이 경우, 필요하다면 Nagle알고리즘을 중단시켜야 한다.

방법은 간단하다. 아래와 같이 TCP_NODELAY옵션을 1(TRUE)로 변경해주면 된다.

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

Nagle알고리즘이 설정된 상태라면 getsockopt함수 호출의 결과로 0을 얻을 수 있고, 설정되지 않은 상태라면 1을 얻는다.