11. 논 블로킹 소켓
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는 소켓 프로그래밍에서 해야 하는 자잘한 작성 코드들을 편하게 사용하기 위한 헬퍼 클래스이다.