Coding Memo

Socket Programming - UDP 본문

Game Server (C++)

Socket Programming - UDP

minttea25 2022. 11. 3. 14:47

본 포스팅은 인프런에 등록되어 있는 Rockiss 님의 강의를 보고 간단하게 정리한 글입니다.


UDP(User Data Protocol)은 네트워크 5계층에서 Transmission Layer에 해당하는 프로토콜 중 하나의 방식이다.

 

UDP는 다음과 같은 특징이 있다.

 

1. 연결을 위한 별다른 경로가 없다.

2. 송수신 여부를 확인하지 않는다.

3. 데이터 손실에 대한 조치가 없다.

4. 데이터 그램 단위로 데이터를 전송한다.

5. 속도가 상대적으로 빠르다. (TCP에 비해)

 

좀 더 자세히 보자면

1. 연결을 위한 즉, listening 할 소켓이 필요가 없다. 대신 connecting UDP가 있다. (즐겨찾기와 비슷)

2. 직접적인 연결을 송수신 자간에 따로 하지 않고 송수신 여부도 따로 확인하지 않기 때문에 전송 순서가 뒤바뀔 수 있고  신뢰성이 낮다. (단순히 그냥 보내고 그냥 받는다.)

3. 2번과 같은 이유로 UDP는 헤더의 checksum을 이용한 최소한의 오류만 검출하고 흐름제어와 혼잡제어가 없기 때문에 순서가 뒤바뀔 수 있을 뿐더러, 데이터 손실이 일어날 수있다.

4. 즉, 간단히 말하면 데이터 단위로 데이터를 받을 수있다는 말이된다.

 

결론적으로 UDP는 TCP보다 빠르지만, 신뢰성이 낮다.

 

UDP를 이용해 데이터를 송수신하는 에코 서버를 작성해본다.

 

이전 포스트: https://minttea25.tistory.com/81

 

Socket Programming Basic

본 포스팅은 인프런에 등록되어 있는 Rockiss 님의 강의를 보고 간단하게 정리한 글입니다. 소켓을 이용해서 간단하게 서버와 클라이언트 접속을 해보도록 하자. 소켓 프로그래밍을 위해서는 다

minttea25.tistory.com

소켓 프로그래밍은 이 포스팅을 참고하자.


1. UDP 소켓 생성

 

UDP 연결은 SOCK_DGRAM을 이용한다. (참고: TCP는 SOCK_STREAM)

SOCKET clientSocket = ::socket(AF_INET, SOCK_DGRAM, 0);
if (clientSocket == INVALID_SOCKET)
{
	int errCode = ::WSAGetLastError();
    cout << cause << " ErrorCode: " << errCode << endl;
	return 0;
}

 

2. 서버 주소 설정

TCP에서 설정했던 것과 동일하다. (주소 및 포트 설정)

 

<Client>

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);

 

<Server>

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(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
	int errCode = ::WSAGetLastError();
	cout << cause << " ErrorCode : " << errCode << endl;
	return 0;
}

 

3. Unconnected UDP

 

sendto 함수와 recvfrom 함수를 사용하여 데이터를 송수신한다.

flag 인자 이후에 타켓 소켓에 대한 주소와 사이즈가 추가로 들어간다.

 

https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-sendto

 

sendto function (winsock.h) - Win32 apps

The sendto function (winsock.h) sends data to a specific destination.

learn.microsoft.com

char sendBuffer[100] = "Hello World!";

//Unconnected UDP -> sendto 이용
int resultCode = ::sendto(clientSocket, sendBuffer, sizeof(sendBuffer), 0, (SOCKADDR*)&serverAddr, sizeof(serverAddr));

if (resultCode == SOCKET_ERROR)
{
	int errCode = ::WSAGetLastError();
	cout << "sendto ErrorCode : " << errCode << endl;
	return 1;
}

 

데이터를 받을 때는 recvfrom 함수를 이용한다.

flag 인자 이후에 sendto와 마찬가지로 타겟 소켓의 주소와 사이즈가 필요하다.

 

https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom

 

recvfrom function (winsock.h) - Win32 apps

The recvfrom function receives a datagram and stores the source address.

learn.microsoft.com

 

SOCKADDR_IN recvAddr;
::memset(&recvAddr, 0, sizeof(recvAddr));
int addrLen = sizeof(recvAddr);

char recvBuffer[1000];

int recvLen = ::recvfrom(clientSocket, recvBuffer, sizeof(recvBuffer), 0, (SOCKADDR*)&recvAddr, &addrLen);

if (recvLen <= 0)
{
	int errCode = ::WSAGetLastError();
	cout << "recvfrom ErrorCode: " << errCode << endl;
    return 1;
}

 

4. connected UDP

 

connected UDP는 connect로 먼저 주소를 등록 후 소켓을 즐겨찾기처럼 이용한다. (실제 connection이 맺어지는 것은 아니다.)

 

::connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr));

 

TCP처럼 send를 이용한다.

char sendBuffer[100] = "Hello World!";

int resultCode = ::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);
if (resultCode == SOCKET_ERROR)
{
	int errCode = ::WSAGetLastError();
	cout << "send ErrorCode : " << errCode << endl;
	return 1;
}

 

send에 맞게 recv 함수를 이용한다. (TCP 참고)

SOCKADDR_IN recvAddr;
::memset(&recvAddr, 0, sizeof(recvAddr));

char recvBuffer[1000];

int recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);

if (recvLen <= 0)
{
	int errCode = ::WSAGetLastError();
    cout << "recvfrom ErrorCode: " << errCode << endl;
    return 1;
}

 

5. Full Codes

 

<Client>

더보기
void HandleError(const char* cause)
{
	int errCode = ::WSAGetLastError();
	cout << cause << " ErrorCode : " << errCode << endl;
}

int main()
{
	WSAData wsaData;
	if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		return 1;
	}

	SOCKET clientSocket = ::socket(AF_INET, SOCK_DGRAM, 0);
	if (clientSocket == INVALID_SOCKET)
	{
		HandleError("Socket");
		return 1;
	}

	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);

	// Connected UDP -> 주소 등록 후, 소켓을 즐겨찾기 처럼 이용
	// 실제로 connection이 맺어지는 것은 아님
	::connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr));

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

	while (true)
	{
		char sendBuffer[100] = "Hello World!";

		// 나의 IP 주소 + 포트 번호 설정 -> 가능한 포트로 자동 설정

		// Unconnected UDP -> sendto 이용
		/*int32 resultCode = ::sendto(clientSocket, sendBuffer, sizeof(sendBuffer), 0,
			(SOCKADDR*)&serverAddr, sizeof(serverAddr));*/

		// Connected UDP -> send
		int resultCode = ::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);

		if (resultCode == SOCKET_ERROR)
		{
			HandleError("SendTo");
			return 1;
		}

		cout << "Send Data! Len = " << sizeof(sendBuffer) << endl;

		SOCKADDR_IN recvAddr;
		::memset(&recvAddr, 0, sizeof(recvAddr));
		int addrLen = sizeof(recvAddr);

		char recvBuffer[1000];

		// Unconnected UDP -> recvfrom 사용
		/*int recvLen = ::recvfrom(clientSocket, recvBuffer, sizeof(recvBuffer), 0,
			(SOCKADDR*)&recvAddr, &addrLen);*/

		// 마찬가지로 recv 사용
		int recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);

		if (recvLen <= 0)
		{
			HandleError("RecvFrom");
			return 1;
		}

		cout << "Recv Data! Data = " << recvBuffer << endl;
		cout << "Recv Data! Len = " << recvLen << endl;

		this_thread::sleep_for(1s);
	}

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

	// 소켓 리소스 반환
	::closesocket(clientSocket);

	// 윈속 종료
	::WSACleanup();
}

 

<Server>

더보기
void HandleError(const char* cause)
{
	int errCode = ::WSAGetLastError();
	cout << cause << " ErrorCode : " << errCode << endl;
}

int main()
{
	WSAData wsaData;
	if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		return 1;

	SOCKET serverSocket = ::socket(AF_INET, SOCK_DGRAM, 0);
	if (serverSocket == INVALID_SOCKET)
	{
		HandleError("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(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
	{
		HandleError("Bind");
		return 1;
	}

	while (true)
	{
		SOCKADDR_IN clientAddr;
		::memset(&clientAddr, 0, sizeof(clientAddr));
		int addrLen = sizeof(clientAddr);

		this_thread::sleep_for(1s);

		char recvBuffer[1000];

		int recvLen = ::recvfrom(serverSocket, recvBuffer, sizeof(recvBuffer), 0,
			(SOCKADDR*)&clientAddr, &addrLen);

		if (recvLen <= 0)
		{
			HandleError("RecvFrom");
			return 1;
		}

		cout << "Recv Data! Data = " << recvBuffer << endl;
		cout << "Recv Data! Len = " << recvLen << endl;

		int errorCode = ::sendto(serverSocket, recvBuffer, recvLen, 0,
			(SOCKADDR*)&clientAddr, sizeof(clientAddr));

		if (errorCode == SOCKET_ERROR)
		{
			HandleError("SendTo");
			return 1;
		}

		cout << "Send Data! Len = " << recvLen << endl;
	}

	// 윈속 종료
	::WSACleanup();
}

 

'Game Server (C++)' 카테고리의 다른 글

Non-blocking Socket  (0) 2022.11.18
Socket Option - (get/set)sockopt()  (0) 2022.11.07
Socket Programming - TCP  (0) 2022.10.31
Socket Programming Basic  (0) 2022.10.18
Type Cast  (1) 2022.10.11