Coding Memo
Non-blocking Socket 본문
본 포스팅은 인프런에 등록되어 있는 Rockiss 님의 강의를 보고 간단하게 정리한 글입니다.
지금까지 했던 socket은 blocking으로 작동하는 소켓이었다.
accept : 접속한 클라이언트가 있을 때
connect : 서버가 접속에 성공했을 때
send, sendto : 요청한 데이터를 sendBuffer에 복사 했을 때
recv, recvfrom : recvBuffer에 도착한 데이터가 존재하고, 이를 유저레벨 buffer에 복사했을때
다음 코드가 실행이 된다.
다시 말해, 위 조건이 실행되거나 에러가 발생할 때까지 다음 코드 실행은 block되는 것이다.
곰곰히 생각해보면 서버를 실행할 때 어떤 송수신이나 connect에 대한 응답을 무한히 대기하여 block된 상태로 있을 수는 없을 것이다. 따라서 non-blocking을 사용한다.
non-blocking 소켓을 이용할 시 주의할 점은 명령 실행이 끝났는지 끝나지 않았는지 알 수 없다는 점이다.
그러므로 accept, connect등의 함수를 실행시켜도 결과가 바로 나오지 않았을 수 있음을 인지해야한다. -> infinite loop 이용...
non-blocking 소켓으로 echo 서버를 만들어보자.
bind, listen까지는 blocking과 동일하다.
bind - listen - accept 확인 - recv 대기 - send 실행 확인 - 반복
(에코 서버기 때문에 recv를 한 후에 send를 해야한다.)
(딱 봐도 보기 좋지 않고 비효율 적으로 보인다...)
- non-blocking 설정
1번째 인자는 설정할 소켓을 넣어주면 된다.
2번째 인자는 FIONBIO를 넣어주면 되는데 socket I/O mode라는 뜻이다.
3번째 인자가 0이면 blocking 그외 값이면 non-blocking 모드 이다.
int ioctlsocket(
[in] SOCKET s,
[in] long cmd,
[in, out] u_long *argp
);
// non-blocking 사용
u_long on = 1;
if (::ioctlsocket(listenSocket, FIONBIO, &on) == INVALID_SOCKET)
{
return 1;
}
자세한 내용은... 여기
https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-ioctlsocket
- full code
server
int main()
{
WSAData wsaData;
if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
return 1;
SOCKET listenSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (listenSocket == INVALID_SOCKET)
{
return 1;
}
// non-blocking 사용
u_long on = 1;
if (::ioctlsocket(listenSocket, FIONBIO, &on) == INVALID_SOCKET)
{
return 1;
}
SOCKADDR_IN serverAddr;
::memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = ::htonl(INADDR_ANY);
serverAddr.sin_port = ::htons(7777);
if (::bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
return 0;
}
if (::listen(listenSocket, SOMAXCONN) == SOCKET_ERROR)
{
return 0;
}
cout << "Accept" << endl;
SOCKADDR_IN clientAddr;
int addrLen = sizeof(clientAddr);
while (true)
{
SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
// 즉 연결을 하려면 계속 확인해야됨
if (clientSocket == INVALID_SOCKET)
{
// 원래 blocking으로 실행되었어야 하는데 지금은 non-blocking 상태라
// INVALID_SOCKET이어도 잘못된 상황이 아닐 수 있음(아직 실행이 완료가 안되었을 때)
if (::WSAGetLastError() == WSAEWOULDBLOCK)
{
continue;
}
// Error
break;
}
cout << "Client Connected!" << endl;
//recv
while (true)
{
char recvBuffer[1000];
int recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
if (recvLen == SOCKET_ERROR)
{
// 위와 마찬가지로 non-blocking이라 recvLen 값이 0 이하일 수 있음
// 그러나 문제는 아닐 수 있음 (데이터를 아직 못받은 상태)
if (::WSAGetLastError() == WSAEWOULDBLOCK)
{
continue;
}
// Error
break;
}
else if (recvLen == 0)
{
// 연결 끊김
break;
}
cout << "Recv Data Len = " << recvLen << endl;
// send
while (true)
{
if (::send(clientSocket, recvBuffer, recvLen, 0) == SOCKET_ERROR)
{
// 마찬가지...
if (::WSAGetLastError() == WSAEWOULDBLOCK)
{
continue;
}
// Error
break;
}
cout << "Send Data ! Len = " << recvLen << endl;
break;
}
}
}
// 윈속 종료
::WSACleanup();
}
client
int main()
{
WSAData wsaData;
if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
return 0;
}
SOCKET clientSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (clientSocket == INVALID_SOCKET)
{
return 0;
}
// non-blocking 사용
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);
// Connect
while (true)
{
if (::connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
// non-blocking
if (WSAGetLastError() == SOCKET_ERROR)
{
return 0;
}
// 이미 연결된 상태라면 break
if (::WSAGetLastError() == WSAEISCONN)
{
break;
}
// Error
break;
}
}
cout << "Connected to Server!" << endl;
char sendBuffer[100] = "Hello World";
// send
while (true)
{
if (::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0) == SOCKET_ERROR)
{
// 마찬가지...
if (::WSAGetLastError() == WSAEWOULDBLOCK)
{
continue;
}
// Error
break;
}
cout << "Send Data ! Len = " << sizeof(sendBuffer) << endl;
while (true)
{
char recvBuffer[1000];
int recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
if (recvLen == SOCKET_ERROR)
{
// 위와 마찬가지로 non-blocking이라 recvLen 값이 0 이하일 수 있음
// 그러나 문제는 아닐 수 있음 (데이터를 아직 못받은 상태)
if (::WSAGetLastError() == WSAEWOULDBLOCK)
{
continue;
}
// Error
break;
}
else if (recvLen == 0)
{
// 연결 끊김
break;
}
cout << "Recv Data Len = " << recvLen << endl;
break;
}
this_thread::sleep_for(1s);
}
// 소켓 리소스 반환
::closesocket(clientSocket);
// 윈속 종료
::WSACleanup();
}
매번 상태를 확인하는 무한 while문이 있기 때문에 효율적이지 않고 자원이 낭비될 수가 있다.
이 문제를 해결하기 위한 여러가지 대안이 있다.
select
https://minttea25.tistory.com/86
'Game Server (C++)' 카테고리의 다른 글
WSAEventSelect (0) | 2022.11.23 |
---|---|
Socket IO - Select (0) | 2022.11.18 |
Socket Option - (get/set)sockopt() (0) | 2022.11.07 |
Socket Programming - UDP (0) | 2022.11.03 |
Socket Programming - TCP (0) | 2022.10.31 |