외로운 Nova의 작업실

c 소켓프로그래밍 공부 - 10(소켓의 옵션) 본문

Programming/C

c 소켓프로그래밍 공부 - 10(소켓의 옵션)

Nova_ 2022. 3. 28. 10:10

안녕하세요. 오늘은 소켓의 다양한 옵션들을 출력하고 설정해보는 것을 공부해볼까합니다.

소켓의 옵션에는 너무 많은 옵션들이있어서 자주 사용되는 옵션들만 보려고합니다.

먼저 아래는 옵션들을 얻어(get)오고 설정(set)하는 함수의 정의입니다.

int getsockopt(SOCKET sock, int level, int opname, char* optval, int* optlen);
//sock에 opname설정의 값을 optval주소값에 optlen만큼 넣는다

int setsockopt(SOCKET sock, int level, int opname, char* optval, int optlen);
//sock에 opname의 설정값을 optval주소에 있는 optlen크기의 값으로 변경한다.

위의 정의에서 level이라는 변수가 있습니다.

이 변수는 socket의 옵션마다 설정 및 얻어올 수 있는 계층을 의미합니다.

아래는 계층에따른 얻어올 수 있는 옵션들입니다.

Protocol Level Option Name
SOL_SOCKET SO_SNDBUF
SOL_SOCKET SO_RCVBUF 
SOL_SOCKET SO_TYPE 
SOL_SOCKET SO_REUSEADDR
IPPROTO_IP IP_TOS
IPPROTO_IP IP_TTL
IPPROTO_TCP TCP_NODELAY

 

위 표를 참고해서 사용해보는 예제를 작성해보도록 하겠습니다

먼저, SO_SNDBUF와 SO_RCVBUF의 크기를 출력해보도록 하겠습니다.

#include <stdio.h>
#include <winsock2.h>
#include <WS2tcpip.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
int main(int argc, char* argv[]) {

	WSADATA wsaData;
	SOCKET sock;
	int sndBuf, rcvBuf;
	int len = sizeof(sndBuf);

	WSAStartup(MAKEWORD(2, 2), &wsaData); // 소켓 버전 맞춰줌

	sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //소켓 생성

	getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sndBuf, &len); //sendbuf get
	printf("send buf size : %d\n", sndBuf); //sendbuf 출력
	
	getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvBuf, &len); //recvbuf get
	printf("recv buf size : %d", rcvBuf);


	WSACleanup();
	


}

아래는 결과 출력 사진입니다.

2번째로는 SO_REUSEADDR 옵션을 설정해보도록 하겠습니다.

먼저 SO_REUSEADDR옵션을 알기위해서는 먼저 time-wait의 개념을 알아야됩니다.

time-wait이란 우리가 서버를 종료하면 호스트와 클라이언트는 four-way handshaking 방식으로 종료합니다.

아래는 four-handshaking 방식의 순서입니다.

1. 호스트1 ---저 종료가능할까요?-->> 호스트2

2. 호스트1 <<---네 잠시만요-- 호스트2

3. 호스트1 <<---저 먼저 종료하겠습니다-- 호스트2

4. 호스트1(메시지보냄과 동시 종료) ---저도 종료하겠습니다-->> 호스트2(메시지 받음과동시 종료)

이러한 흐름속에서 한가지 가정을 해보겠습니다.

만약, 4번째 단계에서 호스트1의 메시지가 호스트2에게 닿지않았다면? 어떻게될까요?

이러한 상황이 벌어진다면 호스트2는 3번째 단계의 메시지를 다시보낼겁니다.

하지만  4번째 단계에서 호스트 1은 메시지 보냄과 동시에 종료됬음으로 호스트2가 다시보낸 메시지를 받지못할 것입니다.

이러한 상황을 방지하기 위해 호스트1은 종료를 잠깐 늦춰서 호스트2가 4번째 단계 메시지를 잘 받았는지 확인하는 시간을 가집니다.

이를 time-wait이라합니다.

four-handshaking에 time-wait을 포함시켜 단계별 순서를 정하면 아래와 같습니다.

1. 호스트1 ---저 종료가능할까요?-->> 호스트2

2. 호스트1 <<---네 잠시만요-- 호스트2

3. 호스트1 <<---저 먼저 종료하겠습니다-- 호스트2

4. 호스트1---저도 종료하겠습니다-->> 호스트2(메시지 받음과동시 종료)

5. 호스트1의 time-wait(호스트2로부터 3번째 단계 메시지가 또 오지않는지 확인 후 종료)

 

이러한 상황에서 만약 호스트1이 클라이언트라면 다른 컴퓨터와 통신할때마다 포트를 바꾸기에 연결이 바로되지만,

만약 호스트1이 서버라면 사용하는 포트번호가 정해져있기때문에, 소켓을 종료하고 다시 소켓을 열려면 이전 소켓의 time-wait 때문에 서버가 안열리는 상황이 연출됩니다.

이 time-wait을 사용하는지 안하는지 결정하는 옵션이 바로 SO_REUSEADDR입니다.

 

아래는 SO_REUSEADDR을 설정(set)하는 코드입니다.

#include <stdio.h>
#include <winsock2.h>
#include <WS2tcpip.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
int main(int argc, char* argv[]) {

	WSADATA wsaData;
	SOCKET sock;
	char option = TRUE;
	int len = sizeof(option);

	WSAStartup(MAKEWORD(2, 2), &wsaData); // 소켓 버전 맞춰줌

	sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //소켓 생성
	
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, len); //time-wait없이 포트 재사용가능으로 변경

	WSACleanup();
	
}

이번에는 TCP_NODELAY 옵션에대해서 설명해보도록 하겠습니다.

TCP_NODELAY 옵션을 알기위해서는 먼저 Nagle알고리즘에대해서 알아야합니다.

Nagle 알고리즘은 네트워크상에서 패킷들이 넘쳐 흘르는것을 막기위해서 고안된 알고리즘입니다.

단순하게 "응답이 있을경우에만 패킷을 준다" 라고 생각하시면됩니다.

예를들어 호스트 1이 호스트2에게 "Nagle"이라는 문자열을 알파벳하나씩 보내는 상황을 연출해보겠습니다.

Nagle알고리즘 적용 X

호스트1 ---N--> 호스트2

호스트1 ---a--> 호스트2

호스트1 ---g--> 호스트2

호스트1 ---l--> 호스트2

호스트1 ---e-->호스트2

호스트1 <---N 받음-- 호스트2

호스트1 <---a 받음-- 호스트2

호스트1 <---g 받음-- 호스트2

호스트1 <---l 받음-- 호스트2

호스트1 <---e 받음-- 호스트2

 

만약 Nagle 알고리즘이 적용이 안된 상황에서 호스트2가 소켓이 안열려있었다면, 아래와 같은 상황이 연출될것입니다.

호스트1 ---N--> 호스트2

호스트1 ---a--> 호스트2

호스트1 ---g--> 호스트2

호스트1 ---l--> 호스트2

호스트1 ---e-->호스트2

//호스트2 응답없음

 

이와 같은 상황일때 호스트1이 보내는 것들은 쓸모없는 패킷들이 됩니다.

이러한 상황을 방지하고자 Nagle 알고리즘을 적용시켰습니다.

아래는 Nagle알고리즘을 적용해서 통신하는 순서입니다.

 

Nagle알고리즘 적용 O

호스트1 ---N--> 호스트2

호스트1 <---N 받음-- 호스트2

호스트1 ---a--> 호스트2

호스트1 <---a 받음-- 호스트2

호스트1 ---g--> 호스트2

호스트1 <---g 받음-- 호스트2

호스트1 ---l--> 호스트2

호스트1 <---l 받음-- 호스트2

호스트1 ---e-->호스트2

호스트1 <---e 받음-- 호스트2

 

만약 Nagle알고리즘이 적용되고, 호스트2가 응답이 없다면 아래와 같이 통신될것입니다.

호스트1 ---N--> 호스트2

//호스트2 응답없음으로 통신 불가

 

이렇게 불필요한 패킷들을 막아주는 역할을 합니다.

 

하지만 Nagle알고리즘이 불필요한 상황이 있습니다.

호스트1과 호스트2는 통신이 가능한 상황이고, 호스트1이 보내야할 데이터의 크기가 출력 버퍼보다 많이 큰경우 입니다.

구체적으로 출력버퍼가 4바이트이고, 보내야할 데이터의 크기가 1바이트일때 Nagle알고리즘이 있다면 아래와같은 상황이 연출됩니다.

 

호스트1 ---첫번째 1바이트--> 호스트2

호스트1 <---1바이트 받음-- 호스트2

호스트1 ---두번째 1바이트--> 호스트2

호스트1 <---1바이트 받음-- 호스트2

호스트1 ---세번째 1바이트--> 호스트2

호스트1 <---1바이트 받음-- 호스트2

호스트1 ---네번째 1바이트--> 호스트2

호스트1 <---1바이트 받음-- 호스트2

 

이런식으로 호스트1은 호스트2에게 받았다는 응답을 받아야 다음걸 보냅니다.

그래서 응답이 돌아오는 시간이 낭비가 됩니다. 

이부분을 해결하기위해 Nagle알고리즘을 적용하지않으면

 

호스트1 ---첫번째 1바이트--> 호스트2

호스트1 ---두번째 1바이트--> 호스트2

호스트1 ---세번째 1바이트--> 호스트2

호스트1 ---네번째 1바이트--> 호스트2

호스트1 <---1바이트 받음-- 호스트2

호스트1 <---1바이트 받음-- 호스트2

호스트1 <---1바이트 받음-- 호스트2

호스트1 <---1바이트 받음-- 호스트2

 

이런식으로 빠르게 호스트1이 호스트2에게 데이터를 전달하고 호스트2는 데이터를 빠르게 처리하고 나중에 응답을 보낼 수 있게 됩니다.

 

이러한 Nagle 알고리즘을 끄거나 키는 옵션이 바로 TCP_NODELAY 옵션입니다.

아래는 Nagle 알고리즘을 끄는 코드입니다.

 

#include <stdio.h>
#include <winsock2.h>
#include <WS2tcpip.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
int main(int argc, char* argv[]) {

	WSADATA wsaData;
	SOCKET sock;
	char option = TRUE;
	int len = sizeof(option);

	WSAStartup(MAKEWORD(2, 2), &wsaData); // 소켓 버전 맞춰줌

	sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //소켓 생성
	
	setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &option, len); //Nagle 알고리즘 off

	WSACleanup();
	
}

여기까지 주로 쓰는 소켓 옵션들을 공부해 보았습니다.

다음에는 멀티프로세서에대해서 공부해보겠습니다.

Comments