KoreanFoodie's Study
[C++ 게임 서버] 3-5. UDP 서버 실습 본문
[C++ 게임 서버] 3-5. UDP 서버 실습
핵심 :
1. UDP 는 TCP 와 다르게, 패킷 간의 경계가 있고 속도가 빠르다. 다만 패킷의 순서가 보장되지 않으며, 데이터가 유실될 위험이 존재한다.
2. UDP 실습의 경우, TCP 실습 코드에서 ::connect 와 ::listen 만 없으면 로직은 거의 동일하다.
3. UDP 는 Connected UDP(일종의 즐겨찾기 기능)을 활용하면, sendto 대신 send 를, recvfrom 대신 recv 를 사용하여 API 를 더 간단하게 사용 가능하다.
저번에는 TCP 방식으로 데이터를 전송하는 실습을 했으니, 이번에는 UDP 방식으로 데이터를 전송해 보자. 사실 그리 다르지는 않다. 조금 간소화되었다고 하면 될까?
3-2 에 보면 기본적으로 소켓을 만들고 클라이언트-서버 간 통신을 구축하는 기본 작업을 하였는데, UDP 는 기존 작업에서 ::connect 쪽을 굳이 안해줘도 된다(클라이언트 쪽에서)!
WSAData wsaData;
if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
return 0;
SOCKET clientSocket = ::socket(AF_INET, SOCK_DGRAM, 0);
if (clientSocket == INVALID_SOCKET)
{
HandleError("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 문에 내용을 채워주자.
while (true)
{
char sendBuffer[100] = "Hello World!";
// 나의 IP 주소 + 포트 번호 설정
// Unconnected UDP
int32 resultCode = ::sendto(clientSocket, sendBuffer, sizeof(sendBuffer), 0,
(SOCKADDR*)&serverAddr, sizeof(serverAddr));
if (resultCode == SOCKET_ERROR)
{
HandleError("SendTo");
return 0;
}
cout << "Send Data! Len = " << sizeof(sendBuffer) << endl;
SOCKADDR_IN recvAddr;
::memset(&recvAddr, 0, sizeof(recvAddr));
int32 addrLen = sizeof(recvAddr);
char recvBuffer[1000];
// Unconnected UDP
int32 recvLen = ::recvfrom(clientSocket, recvBuffer, sizeof(recvBuffer), 0,
(SOCKADDR*)&recvAddr, &addrLen);
if (recvLen <= 0)
{
HandleError("RecvFrom");
return 0;
}
cout << "Recv Data! Data = " << recvBuffer << endl;
cout << "Recv Data! Len = " << recvLen << endl;
this_thread::sleep_for(1s);
}
사실 뭐... 크게 달라진 것은 없다. 중요한 함수는 sendto 와 recvfrom 정도이다.
::sendto 를 보면, 클라이언트 소켓과 서버 주소만 가지고 정보를 보내고, ::recvfrom 을 보면 클라이언트 소켓과 수령 주소만 가지고 정보를 받는다.
즉, 그냥 막(?) 주고 받는다고 보면 된다 😅
그렇다면 서버는 어떨까?
// 영업 시작!
if (::listen(listenSocket, 10) == SOCKET_ERROR)
{
int32 errCode = ::WSAGetLastError();
cout << "Listen ErrorCode : " << errCode << endl;
return 0;
}
클라에서는 ::connect 가 빠졌다면, 서버는 ::listen 부분이 빠지게 된다. 그외의 것들은.. 사실 클라와 비슷하다.
while (true)
{
SOCKADDR_IN clientAddr;
::memset(&clientAddr, 0, sizeof(clientAddr));
int32 addrLen = sizeof(clientAddr);
this_thread::sleep_for(1s);
char recvBuffer[1000];
int32 recvLen = ::recvfrom(serverSocket, recvBuffer, sizeof(recvBuffer), 0,
(SOCKADDR*)&clientAddr, &addrLen);
if (recvLen <= 0)
{
HandleError("RecvFrom");
return 0;
}
cout << "Recv Data! Data = " << recvBuffer << endl;
cout << "Recv Data! Len = " << recvLen << endl;
int32 errorCode = ::sendto(serverSocket, recvBuffer, recvLen, 0,
(SOCKADDR*)&clientAddr, sizeof(clientAddr));
if (errorCode == SOCKET_ERROR)
{
HandleError("SendTo");
return 0;
}
cout << "Send Data! Len = " << recvLen << endl;
}
서버에서도 ::recvfrom 과 ::sendto 를 썼다는 것에 유의하자.
아, 참고로 UDP 의 경우, 보내고자하는 서버 주소를 미리 즐겨찾기처럼 미리 지정해 놓고, sendto/recvfrom 을 할 때 주소를 미리 적지 않아도 되는 방법이 있다.
이를 Connected UDP 라고 하는데... 다음 구문을 추가하면 된다 :
// Connected UDP
::connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
그리고 나서, ::sendto 와 ::recvfrom 을 각각 아래 ::send 와 ::recv 로 대체해주기만하면 끝이다! 😉
// Connected UDP
int32 resultCode = ::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);
int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
'Game Dev > Game Server' 카테고리의 다른 글
[C++ 게임 서버] 3-7. 논블로킹 소켓 (0) | 2023.09.25 |
---|---|
[C++ 게임 서버] 3-6. 소켓 옵션 (0) | 2023.09.22 |
[C++ 게임 서버] 3-4. TCP vs UDP (0) | 2023.09.21 |
[C++ 게임 서버] 3-3. TCP 서버 실습 (0) | 2023.09.21 |
[C++ 게임 서버] 3-2. 소켓 프로그래밍 기초 #2 (0) | 2023.09.20 |
Comments