글 작성자: Sowhat_93

[프로그래밍/소켓 프로그래밍] - Winsock - 1. WSAGetLastError, FormetMessage

[프로그래밍/소켓 프로그래밍] - Winsock - 2. TCP 연결 bind,listen,accept 그리고 connect

[프로그래밍/소켓 프로그래밍] - Winsock - 3. 데이터 송수신 send, recv

 

운영체제는 다양한 소켓 옵션등을 제공한다.

winsock을 사용하는 App은  소켓 옵션을 사용해 소켓의 동작방식을 제어할 수 있다.

기본적으로는 TCP등 네트워크 프로토콜의 동작방식과 밀접한 관련이 있으나,

그렇지 않은 옵션도 있다.

OS 자체의 전역적 네트워크 옵션과 정책에 따라 다르게 동작할 수 있다.

 

옵션 설정에는 setsockopt 함수가 사용된다.

 

optval은 포인터를 받고있고, optlen은 len을 받는다.

보통 이런 함수들이 그러하듯이, 옵션을 버퍼의 형태로 구성하라는 뜻이다.

별 다른 것은 아니고, 알맞은 변수나 구조체를 구성해서 주소 넘기고 그 크기를 넘기라는 것이다.

 

성공하면 0을 리턴하고, 실패하면 역시나 SOCKET_ERROR를 리턴한다.

 

자세한 정보는 다음을 참고하면 된다.https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-setsockopt

 

setsockopt function (winsock.h) - Win32 apps

Sets a socket option.

docs.microsoft.com

 

두번째인자 level은 '어느 단에서의 옵션인지' 나타낸다.

https://docs.microsoft.com/ko-kr/windows/win32/winsock/socket-options

 

소켓 옵션 - Win32 apps

Winsock (Windows Sockets) 소켓 옵션에 대 한 탐색 페이지입니다.

docs.microsoft.com

 

 

많은 옵션이 있지만, 자주 사용하는 옵션 몇가지만 소개하기로 한다.

 

SOL_SOCKET - SO_BROADCAST 

UDP 소켓에서만 쓸 수 있는 브로드 캐스트 수행 옵션.

 

간단한 사용법은 다음의 코드를 참조하면 된다.

#pragma comment(lib,"ws2_32.lib")					//WSA 라이브러리	
#include <WinSock2.h>								//WSA 헤더.
#include <WS2tcpip.h>								//tcp/ip 주소 변환 관련 함수가 여기에 많다.
#include <iostream>

int main()
{

	WSADATA wsa;
	WSAStartup(0x0202, &wsa);

	int ErrorCode = 0;

	do
	{
		SOCKET UDPSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
		if (INVALID_SOCKET == UDPSocket) break;

		//---------------이더넷 으로 나갈때 내 이름으로 찍히는 주소 (나의 명함)
		sockaddr_in MyAddr;
		MyAddr.sin_family = AF_INET;
		MyAddr.sin_port = htons(1234);
		MyAddr.sin_addr.S_un.S_addr = INADDR_ANY;
		//----------------------------------------------------

		//-------------------------------타겟 주소  

		sockaddr_in TargetAddress;
		TargetAddress.sin_family = AF_INET;
		TargetAddress.sin_addr.S_un.S_addr = 0xffffffff; //BraodCast 시 IP 주소는 255.255.255.255
		TargetAddress.sin_port = htons(5678);

		//------------------------------------------------------



		if (SOCKET_ERROR == bind(UDPSocket, (sockaddr*)&MyAddr, sizeof(MyAddr))) break;
		
		int BroadCastOnOff = 1234567; // 0 만 아니면 켜는거다.
		if(SOCKET_ERROR == setsockopt(UDPSocket, SOL_SOCKET, SO_BROADCAST, (char*)&BroadCastOnOff, sizeof(int))) break;
		
		

		char MessageToSend[256] = { 0 , };
		strcpy_s(MessageToSend, "Hello Neighbors");
		if(SOCKET_ERROR == sendto(UDPSocket, MessageToSend, 16, 0, (sockaddr*)&TargetAddress,sizeof(TargetAddress))) break;
		
		std::cout << "Success ! " << std::endl;

		system("pause");
		WSACleanup();
		return 0;

	} while (false);


	ErrorCode = WSAGetLastError();

	char msgBuf[1024];
	FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), msgBuf, 1024, NULL);

	std::cout << msgBuf << std::endl;

	WSACleanup();
	return 0;
}

 

IP를 255.255.255.255 로 넣은 결과 이더넷 주소 또한 ff:ff:ff:ff:ff:ff 로 설정된 것을 볼 수 있다.

 

SIO_KEEPALIVE_VALS

 

tcp_keepalive tcpKeepAliveVals;

tcpKeepAliveVals.onoff = 1;
tcpKeepAliveVals.keepalivetime = 1000;			// ms단위로 표현된 Keep Alive 패킷 전송 주기. 1000 -> 1초마다 한번씩 (살아있냐?) 신호를 보낸다.
tcpKeepAliveVals.keepaliveinterval = 1000;		// ms단위로 표현된 Keep Alive 패킷 전송 이후 미응답시 재전송 주기.

DWORD ReturnedValue = 
WSAIoctl(Client, SIO_KEEPALIVE_VALS, &tcpKeepAliveVals, sizeof(tcpKeepAliveVals), nullptr, 0, &ReturnedValue, nullptr, nullptr);

WSAIoctl 을 이용해서 적용한다.

SO_KEEPALIVE를 적용하면 레지스트리 값이 바뀌어서 모든 소켓에 적용된다.

따라서 SIO_KEEPALIVE_VALS 를 사용하는 편이 낫다.

 

TCP Keep Alive 패킷의 특징은 Seq가 1 작다는 것이다.

 

SOL_SOCKET - SO_LINGER

보통은 Hard Close 구현을 위해서 사용한다.

연결종료 관련 옵션이며 보통 hard close 구현을 위한 목적으로 사용된다.

 

 

closesocket은 대략적으로 다음과 같이 동작한다.

 

1. 우선, 연결 종료 절차를 위해 FIN Packet을 보낸다.

2. 소켓 핸들을 OS에 반환한다.

App에서는 반환했으므로 App에서는 사용할수 없다.

OS는 설정된 연결 종료정책에 따른 작업을 수행하고 커널 오브젝트를 정리한다.

3. 함수는 리턴한다.

 

 

soft close, graceful close 는 4way handshake의 진행을 의미한다.

 

옵션을 통해 바꿀 수 있는 것은 함수의 리턴 시점과 OS가 수행할 연결종료 정책이다.

 

A. closesocket은 곧바로 리턴한다.

OS는 soft close 를 수행한다.

OS는 soft close를 마치고 소켓 커널 오브젝트를 정리한다.

 

B. closesocket은 곧바로 리턴한다. 

OS는 4way handshake 과정을 수행하지 않고 바로 소켓 커널 오브젝트를 정리한다.

 

C. closesocket은 곧바로 리턴하지 않는다.

OS의 softclose가 모두 끝났다는 통보를 기다린다.

일정 시간 기다려도 OS로부터의 TCP 연결 종료과정에 대한 완료 통보가 안오면, 리턴한다.

 

closesocket의 기본동작은 A안 이다.

보통 B안으로 바꾸기 위해서 설정한다.

 

타임아웃없이 직관적으로 즉시 리턴하며,

또한 OS는 민첩하게 리소스 확보를 할 수 있다.

 

LINGER linger;

//A. closesocket은 곧바로 리턴한다. OS는 soft close 를 수행한다.
//다음과 같이 세팅한다.
linger.l_onoff = 0; 
linger.l_linger = 123456; // 이 값은 사용 되지 않음. 

//B. closesocket은 곧바로 리턴한다. 
//OS는 4way handshake 과정을 수행하지 않고 바로 커널 송신 버퍼에 남아있는 데이터는 즉시 삭제한다. (hard close)
//다음과 같이 세팅한다.
linger.l_onoff = 1;
linger.l_linger = 0;  
//C. closesocket은 곧바로 리턴하지 않고 OS의 softclose가 모두 끝날때 까지 일정시간 기다린다.
//기다려도 OS로부터의 TCP 연결 종료과정에 대한 완료 통보가 안오면, 리턴한다.
//다음과 같이 세팅한다.

linger.l_onoff = 1;
linger.l_linger = 10; //함수 리턴 전 최대 10초 동안 기다린다.

setsockopt(Client, SOL_SOCKET, SO_LINGER, (char*)&linger, sizeof(linger));

 

 

SOL_SOCKET - SO_SNDBUF, SO_RECVBUF

소켓의 커널 송신 버퍼와 수신 버퍼의 사이즈를 변경한다.

 

TCP의 버퍼링, 송수신 윈도우 스케일링과 밀접한 연관이 있는 옵션이다.

 

TCP의 경우 연결 이전에 이 버퍼크기를 바꾸는 것이 좋다.

UDP는 언제던지 관계없다.

 

예전에는 송신버퍼나 수신버퍼 크기를 0으로 맞추는 경우가 많았는데,

(Application측에서 제공된 버퍼로 즉시 카피된다는 이야기 때문에)

windows의 OS 버전이 업데이트 되면서 이제는 사실상 의미가 없는듯 하다.

 

[프로그래밍/Winsock & Windows OS Summary] - [winsock] 소켓 송수신 버퍼 사이즈 어떻게 해야하나

 

[winsock] 소켓 송수신 버퍼 사이즈 어떻게 해야하나

winsock을 다루는 많은 서적에서 SO_SNDBUF를 0으로 맞추면 send 시에 커널 버퍼에 카피를 하지 않기 때문에 성능에 이득이 된다고 한다. 하지만 실제로는 0로 맞춘다고 해도 복사가 

so-what-93.tistory.com

 

이것 관련해서는 OS의 정책이 큰 영향을 미치는데,

정책에 관해서는 최신의 자료를 참고하는 것이 좋다.

 

최근에는 많은 이들이 기본값으로 그냥 두라고 권하는 옵션중 하나이다.

 

사용법은 매우 간단하다.

int NewSNDBufferSize = 500000;
int NewRCVBufferSize = 600000;
setsockopt(Client, SOL_SOCKET, SO_SNDBUF, (char*)&NewSNDBufferSize, sizeof(NewSNDBufferSize));
setsockopt(Client, SOL_SOCKET, SO_RCVBUF, (char*)&NewRCVBufferSize, sizeof(NewRCVBufferSize));

 

SOL_SOCKET - SO_SNDTIMEO, SO_RCVTIMEO

 

블로킹 소켓에 대한 블로킹 타임아웃 시간 지정옵션이다.

 

블로킹 소켓의 송 수신시 블로킹 조건은 msdn 및 다음을 참고한다.

[프로그래밍/소켓 프로그래밍] - Winsock - 3. 데이터 송수신 send, recv

 

Winsock - 3. 데이터 송수신 send, recv

Winsock - 1. WSAGetLastError, FormetMessage  https://so-what-93.tistory.com/42 Winsock - 2. TCP 연결 bind,listen,accept 그리고 connect https://so-what-93.tistory.com/43 연결이 되었으니 데이터 송수..

so-what-93.tistory.com

 

int SNDTimeOut = 1000; // ms단위이다. 즉 1초로 설정됨.
int RCVTimeOut = 1000; // ms단위이다. 즉 1초로 설정됨.
setsockopt(Client, SOL_SOCKET, SO_SNDTIMEO, (char*)&SNDTimeOut, sizeof(SNDTimeOut));
setsockopt(Client, SOL_SOCKET, SO_RCVTIMEO, (char*)&RCVTimeOut, sizeof(RCVTimeOut));

 

IPPROTO_TCP - TCP_NODELAY

 

Nagle 알고리즘 작동 유무 변경.
Nagle 알고리즘은 존 네이글이 1984년 1월 6일에 네트워크 혼잡제어를 위해 제안한 방식이다.
이 알고리즘은 rfc896이라고도 불린다.

 

원문은 아래를 참고한다.

https://www.ietf.org/rfc/rfc896.txt

잘 설명해 놓은 글

https://noisy.network/2017/02/06/delayed-ack-and-nagles-algorithm

 

 

winsock에서의 구현은 매우 간단하다.

int NoDelayOnOff = 1; //TCP_NODELAY On -> Nagle Off.
setsockopt(Client, IPPROTO_TCP, TCP_NODELAY, (char*)NoDelayOnOff, sizeof(NoDelayOnOff));

 

Nagle 알고리즘을 간단하게만 요약하면 다음과 같다.
1. 커널의 송신버퍼에 일정량 이상의 데이터가 모일때까지 일단은 모은다.
모인 데이터의 양이 충족되면 그때 송신한다.
2. 단, 상대로부터 PSH-ACK가 온다면, 많이 모였던 아니던 송신한다.  

 

우선 기능은 그렇게 되는데, Winsock에서는 현재 1바이트만 보내도

 송신측에서 PSH - ACK 를  같이 보낸다...

하지만 msdn 문서에서는 최근까지도 유효하다고 밝히고 있다.

https://docs.microsoft.com/en-us/troubleshoot/windows/win32/data-segment-tcp-winsock

 

Send data segment over TCP by using Winsock - Application Developer

This article provides a series of recommendations for sending small data packets efficiently from a Winsock application.

docs.microsoft.com

TCP_NODELAY 정말 자주 쓰이는 옵션이니 우선은 MSDN을 믿어본다.