네트워크 환경은 매우 다양하며, 예측하기 힘든 경우도 많이 발생한다. 때문에 네트워크프로그램의 종류에 따라서 소켓의 세부사항을 조절해야 하는 경우가 발생한다. 이러한 소켓옵션 설정을 위해서 소켓은 getsockopt()와 setsockopt()두개의 함수를 제공한다. 이름에서 알 수 있듯이 getsockopt는 현재의 소켓옵션값을 가져오기 위해서, setsockopt는 소켓옵션값을 변경하기 위해서 사용한다.
예를 들자면 동일한 네트워 프로그램이라고 하더라도 ATM망에서 작동하는 것과 인터넷망 PPP에서 작동하는 것은 환경에 있어서 차이가 생길 수 밖에 없을 것이다. 소켓버퍼의 크기를 예로 들자면, 일반적으로 (대역폭 * 지연율) * 2의 공식에 따를 경우 최적의 효과를 보여준다고 한다. 다음은 이들 함수의 사용방법이다.
#include
#include
int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);
s : 소켓지정번호
level : 소켓의 레벨로 어떤 레벨의 소켓정보를 가져오거나 변경할 것인지를 명시하며, SOL_SOCKET와 IPPROTO_TCP 중 하나를 사용할 수 있다.
optname : 설정을 위한 소켓옵션의 번호
optval : 설정값을 저장하기 위한 버퍼의 포인터
optlen : optval 버퍼의 크기
설정값을 void * 로 넘기는 이유는 설정하고자 하는 소켓옵션에 따라서, boolean, interger, 구조체등 다양한 크기를 가지는 데이터형이 사용되기 때문이다. 만약 변경하고자 하는 소켓옵션이 boolean을 따른다면, 0혹은 1이 사용될 것이다.
SOL_SOCKET레벨에서 사용할 수 있는 옵션과 데이타형은 다음과 같다.
옵션값 데이터형 설명
SO_BROADCAST BOOL 브로드캐스트 메시지 전달이 가능하도록 한다.
SO_DEBUG BOOL 디버깅 정보를 레코딩 한다.
SO_DONTLINGER BOOL 소켓을 닫을때 보내지 않은 데이터를 보내기 위해서 블럭되지 않도록 한다.
SO_DONTROUTE BOOL 라우팅 하지 않고 직접 인터페이스로 보낸다.
SO_GROUP_PRIORITY int 사용하지 않음
SO_KEEPALIVE BOOL Keepalives를 전달한다.
SO_LINGER struct LINGER 소켓을 닫을 때 전송되지 않은 데이터의 처리 규칙
SO_RCVBUF int 데이터를 수신하기 위한 버퍼공간의 명시
SO_REUSEADDR BOOL 이미 사용된 주소를 재사용 (bind) 하도록 한다.
SO_SNDBUF int 데이터 전송을 위한 버퍼공간 명시
IPPROTO_TCP레벨에서 사용할 수 있는 옵션과 데이터형이다.
TCP_NODELAY BOOL Nagle 알고리즘 제어
SO_REUSEADDR
간단한 예로, 소켓을 이용한 서버프로그램을 운용하다 보면 강제종료되거나 비정상 종료되는 경우가 발생한다. 테스트를 목적으로 할 경우에는 특히 강제종료 시켜야 하는 경우가 자주 발생하는데, 강제종료 시키고 프로그램을 다시 실행시킬경우 다음과 같은 메시지를 종종 보게 된다.
bind error : Address already in use
이는 기존 프로그램이 종료되었지만, 비정상종료된 상태로 아직 커널이 bind정보를 유지하고 있음으로 발생하는 문제다. 보통 1-2분 정도 지나만 커널이 알아서 정리를 하긴 하지만, 그 시간동안 기달려야 한다는 것은 상당히 번거로운 일이 될 것이다. 이 경우 다음과 같은 코드를 삽입함으로써 문제를 해결할 수 있다.
int sock = socket(...);
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&bf, (int)sizeof(bf));
이렇게 하면 커널은 기존에 bind로 할당된 소켓자원을 프로세스가 재 사용할 수 있도록 허락하게 된다.
다음은 소켓버퍼의 크기를 가져오고 설정하는 완전한 코드다.
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int sockfd;
int bufsize;
int rn;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Error");
return 1;
}
rn = sizeof(int);
// 현재 RCVBUF 값을 얻어온다.
if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &bufsize, (socklen_t *)&rn) < 0)
{
perror("Set Error");
return 1;
}
printf("Socket RCV Buf Size is %d\n", bufsize);
// 버퍼의 크기를 100000 으로 만든다.
bufsize = 100000;
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (void *)&bufsize, (socklen_t)rn) < 0)
{
perror("Set Error");
return 1;
}
return 0;
}
TCP_NODELAY
이 옵션을 이해하려면 Nagle알고리즘에 대해서 이해를 해야 한다. Nagle 알고리즘이 적용되면, 운영체제는 패킷을 ACK가 오기를 기다렸다가 도착하면, 그 동안 쌓여있던 데이터를 한꺼번에 보내게 된다. 이러한 방식을 사용하게 되면, 대역폭이 낮은 WAN에서 빈번한 전송을 줄이게 됨으로 효과적인 대역폭활용이 가능해진다.
대부분의 경우에 있어서 Nagle 알고리즘은 효율적으로 작동하긴 하지만, 빈번한 응답이 중요한 서비스의 경우에는 적당하지 않은 경우가 발생한다. 예를 들어 X-Terminal을 이용할 경우 마우스 이벤트는 즉시 전달될 필요가 있는데, Nagle알고리즘을 사용하면 아무래도 반응시간이 떨어지게 될 것이다. 실시간적인 반응이 중요한 온라인 게임역시 Nagle 알고리즘을 제거하는게 좋을 것이다.
아래의 이미지는 nagle이 적용되었을 때와 그렇지 않을 때, 어떻게 데이터 전송이 일어나는지를 보여주고 있다.
SO_LINGER
SO_LINGER은 소켓이 close()되었을 때, 소켓버퍼에 남아있는 데이터를 어떻게 할 것이지를 결정하기 위해서 사용한다. 다음은 SO_LINGER 옵션에 사용되는 데이터구조체이다.
struct linger
{
int l_onoff;
int l_linger;
}
l_onoff : linger 옵션을 끌것인지 킬 것인지 결정
l_linger : 기다리는 시간의 결정
위의 두개의 멤버변수의 값을 어떻게 하느냐에 따라 3가지 close방식을 결정되어 진다.
l_onoff == 0 : 이 경우 l_linger의 영향을 받지 않는다. 소켓의 기본설정으로 소켓버퍼에 남아 있는 모든 데이터를 보낸다. 이때 close()는 바로 리턴을 하게 되므로 백그라운드에서 이러한 일이 일어나게 된다. 우아한 연결 종료를 보장한다.
l_onoff > 0 이고 l_linger == 0 : close()는 바로 리턴을 하며, 소켓버퍼에 아직 남아있는 데이터는 버려 버린다. TCP 연결상태일 경우에는 상대편 호스트에 리셋을 위한 RST 패킷을 보낸다. hard 혹은 abortive 종료라고 부른다.
l_onoff > 0 이고 l_linger > 0 : 버퍼에 남아있는 데이터를 모두 보내는 우아한 연결 종료를 행한다. 이때 close()에서는 l_linger에 지정된 시간만큼 블럭상태에서 대기한다. 만약 지정된 시간내에 데이터를 모두 보냈다면 리턴이 되고, 시간이 초과되었다면 에러와 함께 리턴이 된다.
댓글 없음:
댓글 쓰기