Network/Windows 소켓 프로그래밍 입문에서 고성능 서버까지!

UDP와 브로드캐스트

Tony Lim 2023. 11. 30. 10:44

UDP 프로토콜 특징

  • 연결, 상태 개념이 없음
  • 흐름제어 , 송/수신 보장 혹은 확인에 관한 기능 없음
  • 개발자 스스로 tcp 구현가능하다

영상 및 게임에서 많이 쓰이게 된다.

	//소켓 생성. SOCK_DGRAM 타입을 사용한다!
	SOCKET hSocket = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (hSocket == INVALID_SOCKET)
		ErrorHandler("UDP 소켓을 생성할 수 없습니다.");
//원격지로 메시지를 전송하는 스레드 함수.
DWORD WINAPI ThreadSendto(LPVOID pParam)
{
	//송신을 위한 UDP 소켓을 하나 더 개방한다.
	SOCKET hSocket = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (hSocket == INVALID_SOCKET)
		ErrorHandler("UDP 소켓을 생성할 수 없습니다.");

	char szBuffer[128];
	SOCKADDR_IN	remoteaddr = { 0 };
	remoteaddr.sin_family = AF_INET;
	remoteaddr.sin_port = htons(g_nRemotePort);
	remoteaddr.sin_addr.S_un.S_addr = inet_addr(g_szRemoteAddress);
	while (1)
	{
		gets_s(szBuffer);
		if (strcmp(szBuffer, "EXIT") == 0)
			break;

		//사용자가 입력한 메시지를 원격지로 전송한다.
		::sendto(hSocket, szBuffer, strlen(szBuffer) + 1, 0,
			(sockaddr*)&remoteaddr, sizeof(remoteaddr));
	}

	//수신을 위한 UDP 소켓을 닫는다. _tmain() 함수의 while문이 끝난다.
	::closesocket((SOCKET)pParam);
	::closesocket(hSocket);
	return 0;
}

별도의 스레드에서 send만 하는 예제이다.

tcp 처럼 메인 스레드에서 accept 된 cilent socket을 들고 있는것이 아니라
매번 remoteaddr에 ip, port를 작성해서 써줘야한다.


UDP 브로드캐스트 송/수신

브로드캐스트가 발생되면 L2 switch에 부하가 많이 발생되게 된다.

tcp에서는 브로드캐스팅이 없다

	//소켓 생성
	SOCKET hSocket = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (hSocket == INVALID_SOCKET)
		ErrorHandler("UDP 소켓을 생성할 수 없습니다.");

	//포트 바인딩
	SOCKADDR_IN	addr = { 0 };
	addr.sin_family = AF_INET;
	addr.sin_port = htons(25000);
	addr.sin_addr.S_un.S_addr = htonl(INADDR_BROADCAST);

address 를 braodcast로 설정한다. 255 (0xFF) 

wireshark 로 패킷을 보면 destination은 255.255.255.255 , mac 은ff로 되어있다.


DNS 쿼리 예시

#include "stdafx.h"
#include <winsock2.h>
#pragma comment(lib, "ws2_32")


int _tmain(int argc, _TCHAR* argv[])
{
	//윈속 초기화
	WSADATA wsa = { 0 };
	if (::WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		puts("ERROR: 윈속을 초기화 할 수 없습니다.");
		return 0;
	}

	//현재 DNS 설정을 근거로 Naaver의 IP주소를 질의한다.
	hostent *pHost = ::gethostbyname("www.naver.com");
	if (pHost == NULL)
	{
		puts("ERROR: Naver의 IP주소를 알 수 없습니다.");
		::WSACleanup();
		return 0;
	}

	printf("Official name: %s\n", pHost->h_name);

	for (int i = 0; pHost->h_aliases[i] != NULL; ++i)
		printf("\t별칭: %s\n", pHost->h_aliases[i]);

	for (int i = 0; pHost->h_addr_list[i] != NULL; ++i)
		printf("\tIP주소: %s\n",
			inet_ntoa(*(in_addr*)pHost->h_addr_list[i]));

	//윈속 해제
	::WSACleanup();
	return 0;
}

NIC Adapter 정보 소스로 확인하기

#include "stdafx.h"


#include <winsock2.h>
//IP Helper API 사용을 위한 헤더 및 라이브러리 설정
#include <iphlpapi.h>
#pragma comment(lib, "Iphlpapi.lib")

#include <stdio.h>
#include <stdlib.h>

int _tmain(int argc, _TCHAR* argv[])
{
	//한 NIC에 대한 설정이 저장될 수 있는 메모리 확보.
	ULONG uBufferLength = sizeof (IP_ADAPTER_INFO);
	PIP_ADAPTER_INFO pNicInfo = (IP_ADAPTER_INFO*)malloc(uBufferLength);

	//설정 정보를 확인한다.
	DWORD dwResult = ::GetAdaptersInfo(pNicInfo, &uBufferLength);
	if (dwResult == ERROR_BUFFER_OVERFLOW)
	{
		//NIC이 2개 이상이라 모든 정보를 담을 수 없는 경우이므로
		//메모리를 다시 할당 한다.
		free(pNicInfo);
		pNicInfo = (IP_ADAPTER_INFO *)malloc(uBufferLength);

		//버퍼 크기 조정 후 다시 설정 정보를 확인 한다.
		if (::GetAdaptersInfo(pNicInfo, &uBufferLength) != NO_ERROR)
		{
			free(pNicInfo);
			puts("ERROR: 어댑터 정보를 알 수 없습니다.");
			return 0;
		}
	}

	//연결 리스트 형태로 설정 정보에 접근한다.
	PIP_ADAPTER_INFO pNicInfoTmp = pNicInfo;
	while (pNicInfoTmp != NULL)
	{
		printf("설명: %s\n", pNicInfoTmp->Description);
		printf("MAC 주소: %02X-%02X-%02X-%02X-%02X-%02X\n",
			pNicInfoTmp->Address[0], pNicInfoTmp->Address[1],
			pNicInfoTmp->Address[2], pNicInfoTmp->Address[3],
			pNicInfoTmp->Address[4], pNicInfoTmp->Address[5]);
		printf("IP 주소: %s\n\n",
			pNicInfo->IpAddressList.IpAddress.String);

		pNicInfoTmp = pNicInfoTmp->Next;
	}

	free(pNicInfo);
	return 0;
}

ipconfig를 소스로 보는 방법