헛둘이 2023. 12. 11. 09:41

setsockopt 란?
- 소켓의 세부 옵션을 세팅하는 함수
- 소켓의 핸들, level, 옵션명, 옵션값을 인자로 받아서 해당 소켓의 상세 옵션을 세팅한다.
 
알아둬야 할 옵션명
- SO_KEEPALIVE : 주기적으로 해당 소켓이 살아있는지 확인하는 옵션
- SO_REUSEADDR : 서버를 내렸다가 다시 켰을 때 해당 포트가 사용중이라고 뜰 경우가 있는데, 기존에 사용하던 포트라도 사용하겠다는 옵션
- TCP_NODELAY : Nagle 알고리즘을 사용할 것인지를 설정하는 옵션
Nagle 알고리즘이란?
- 데이터가 충분히 크면 보내고, 그렇지 않으면 데이터가 쌓일 때까지 대기
- 장점은 소소한 작은 패킷들이 자주 전송되면 낭비이기 때문 (데이터의 크기가 작더라도 이 패킷이 전달되기 위해 필요한 자원이 이것저것 많다)
- 보통은 이 옵션을 끄는데 그 이유는 게임 특성 상 아무리 작은 패킷이라도 즉시 처리되어야 하기 때문
 


논 블로킹 소켓이란?

- 지금까지 사용했던 것은 블로킹방식인데 accept, recv와 같은 함수들은 상대방에 의한 상호작용이 있기까지 무한히 대기한다.
- 이렇게 대기하게 되면 해당 서버나 클라이언트에서 다른 일을 할 수 없는데 논 블로킹 방식은 그러지 않고 연결되지 않았으면 실패로 두고 다른 일을 처리한 후 다음 루프에서 다시 체크하는 식으로 이어갈 수 있도록 하는 방식을 이야기한다.
- 이런 식으로 실패로 처리하면 다른 중요한 실패와 혼동할 수 있기 때문에 논블로킹 함수 뒤쪽에 WSAGetLastError 함수를 통해 지금 실패가 왜 실패했는지 여부를 확인해야 하는 번거로움이 있다.
 

	// 논블로킹 소켓으로 변경
	u_long on = 1;
	if (::ioctlsocket(listenSocket, FIONBIO, &on) == INVALID_SOCKET)
		return 0;

- 논 블로킹 소켓으로 변경하는 코드
- ioctl은 I/O Control의 약자이며, FIONBIO는 File I/O Non-Blocking I/O를 의미한다.
 

/* Server.cpp */

#include "pch.h"
#include "ThreadManager.h"

int main()
{
	SocketUtils::Init();
	SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0);

	if (listenSocket == INVALID_SOCKET)
		return 0;

	// 논블로킹 소켓으로 변경
	u_long on = 1;
	if (::ioctlsocket(listenSocket, FIONBIO, &on) == INVALID_SOCKET)
		return 0;

	// 이전에 사용했던 주소를 사용하기 위함
	SocketUtils::SetReuseAddress(listenSocket, true);

	if (SocketUtils::BindAnyAddress(listenSocket, 7777) == false)
		return 0;

	if (SocketUtils::Listen(listenSocket) == false)
		return 0;

	while (true)
	{
		SOCKADDR_IN clientAddr;
		int32 addrLen = sizeof(clientAddr);

		SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
		if (clientSocket == INVALID_SOCKET)
		{
			// 논블로킹 소켓일 때 접속한 소켓이 없을 때
			if (WSAGetLastError() == WSAEWOULDBLOCK)
				continue;
		}


		cout << "Client Connected!" << endl;

		while (true)
		{
			char recvBuffer[100];
			int32 recvLen = recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);

			if (recvLen == SOCKET_ERROR)
			{
				// 논블로킹 소켓일 때 접속한 소켓이 없을 때
				if (WSAGetLastError() == WSAEWOULDBLOCK)
					continue;
			}

			cout << "Recv Data = " << recvBuffer << " " << recvLen << endl;
		}
	}

	SocketUtils::Clear();
}
/* DummyClient.cpp */

#include "pch.h"
// 클라
// 1. 소켓 생성
// 2. 서버에 연결 요청
// 3. 통신

int main()
{
	SocketUtils::Init();

	SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (clientSocket == INVALID_SOCKET)
		return 0;

	u_long on = 1;
	if (ioctlsocket(clientSocket, FIONBIO, &on) == INVALID_SOCKET)
		return 0;

	SOCKADDR_IN serverAddr;
	memset(&serverAddr, 0, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;

	inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
	serverAddr.sin_port = htons(7777);

	while (true)
	{
		if (connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
		{
			if (WSAGetLastError() == WSAEWOULDBLOCK)
				continue;

			// 이미 연결되었다
			if (WSAGetLastError() == WSAEISCONN)
				break;
		}
	}

	while (true)
	{
		char sendBuffer[100] = "Hello I am Client!";
		int32 sendLen = sizeof(sendBuffer);

		if (::send(clientSocket, sendBuffer, sendLen, 0) == SOCKET_ERROR)
		{
			if (WSAGetLastError() == WSAEWOULDBLOCK)
				continue;
		}

		cout << "Send Data! Len = " << sendLen << endl;

		this_thread::sleep_for(1s);
	}

	SocketUtils::Clear();
}

- SocketUtils는 소켓 프로그래밍에서 해야 하는 자잘한 작성 코드들을 편하게 사용하기 위한 헬퍼 클래스이다.