본문 바로가기

개인 공부/네트워크

네트워크P-013 (UDP Server&Client)

TCP& UDP

포트 번호를 이용해 주소를 지정한다

  • 응용 프로그램이 TCP나 UDP를 이용해 통신하려면 반드시 포트 번호를 결정해야한다

데이터 오류를 체크한다

  • IP와 달리 TCP와 UDP 데이터에 대한 오류를 체크한다  
  TCP UDP
프로토콜 연결형 프로토콜
세션을 성립 후 통신
비연결형 프로토콜
세션을 성립하지 않고 통신
신뢰성 신뢰성이 있는 데이터 전송
드랍시 데이터 재전송
신뢰성이 없는 데이터 전송
드랍시 데이터 재전송하지 않음
기본 통신 1 : 1 통신 1: 1 통신
1: N 통신
데이터 경계 데이터 경계 구분
바이트 스트림 서비스
데이터 경계 구분을 하지 않음
데이터그램 서비스

UDP(User Datagram Protocol)

  • 연결(세션) 설정을 하지 않으므로 connect() 함수 불필요
  • 프로토콜에서 신뢰성 있는 데이터 전송을 보장하지 않는다
    • 필요시 응용 프로그램에서 신뢰성 있는 데이터 전송 기능을 구현!!
  • 간단한 소켓 함수 호출 절차만 따르면 1:N 통신을 쉽게 구현할 수 있다
  • TCP와 달리 데이터 경계 구분을 작업을 할 필요가 없다

UDP Server - Client

  • UDP는 TCP와 달리 데이터 재전송과 흐름 제어를 하지 않아 송신 버퍼가 존재하지 않는다
  • 소켓 통신을 위해 결정해야할 요소
    • 프로토콜 : 통신 규약
    • 지역 IP : 서버 또는 클라이언트 주소
    • 원격 IP 주소와 원격 포트 번호 : 서버 또는 클라이언트가 통신하는 상대의 주소 

UDP 통신


UDP Server

socket() : 소켓을 생성, 프로토콜 결정

bind() : 지역 IP 주소와 지역 포트번호를 결정

recvfrom() : 클라이언트가 보낸 데이터를 받는다 -> 클라이언트 주소를 알 수 있다

sendto() : 받은 데이터를 처리한 결과를 보낸다

closesocket() : 모든 작업을 마치면 소켓을 닫는다

 

UDP Client 

socket() : 소켓 생성, 프로토콜 결정

sendto() : 서버에 데이터를 보낸다 -> 서버의 IP주소 포트번호, 지역 IP 주소 지역 포트 번호도 결정

recvfrom() : 서버가 처리해 보낸 데이터를 받는다

closesocekt() : 모든 작업을 마치면 소켓을 닫는다

 

UDP Server - Client

UDP 서버 클라이언트 모델 사용할 때 주의사항

  • 블로킹 소켓을 사용할 경우
    • 송수신 함수의 호출 순서가 맞지 않으면 교착상태가 발생할 수 있다
  • 클라이언트 데이터를 받은 후 송신자의 주소 (IP 주소, 포트번호)를 확인해야한다
    • recvfrom()함수는 UDP 서버가 보낸 데이터는 물론, 전혀 다른 UDP 응용 프로그램이 보낸 데이터를 수신할 수 있다

 


데이터 전송 함수

sendto()

  • 응용 프로그램 데이터를 OS의 송신 버퍼에 복사함으로써 데이터를 전송
  • 소켓의 지역 IP 주소와 지역 포트 번호가 아직 결정되지 않은 상태라면 OS가 자동으로 결정한다
// ========== Windows ==========
#include <winsock2.h>

int sendto(
	SOCKET sock,
	const char* buf,
	int len,
	int flags,
	const struct sockaddr* addr
	int addrlen
);

// 성공 : 보낸 바이트 수
// 실패 : SOCKET_ERROR

// ========== Linux ==========
#include <sys/types.h>
#include <sys/socket.h>

ssize_t sendto(
	int sock,
	void* buf,
	size_t len,
	int flags,
	struct sockaddr* addr
	socklen_t addrlen
)

// 성공 : 보낸 바이트 수
// 실패 : -1

recvfrom()

  • OS의 수신 버퍼에 도착한 데이터를 응용 프로그램 버퍼에 복사
  • UDP 패킷 데이터를 한번에 하나만 읽을 수 있다
// ========== Windows ==========
#include <winsock2.h>

int recvfrom(
	SOCKET sock,
	char* buf,
	int len,
	int flags,
	const struct sockaddr* addr
	int* addrlen
);

// 성공 : 받은 바이트 수
// 실패 : SOCKET_ERROR

// ========== Linux ==========
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(
	int sock,
	void* buf,
	size_t len,
	int flags,
	struct sockaddr* addr
	socklen_t* addrlen
)

// 성공 : 받은 바이트 수
// 실패 : -1

UDP Server 구현

UDPServer.cpp

#include "Common.h"

#define SERVERPORT 9000
#define BUFSIZE 512

int main() {
	int retval;

	// ===== 윈속 초기화 =====
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)	return 1;

	// ===== 소켓 생성 =====
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);	// SOCK_DGRAM : 데이터 그램 방식
	if (sock == INVALID_SOCKET)	printf("SOCKET() ERROR!!!\n");

	struct 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(SERVERPORT);

	// ===== BIND() =====
	retval = bind(sock, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
	if (retval == SOCKET_ERROR)	printf("BIND() ERROR!!!\n");

	// 통신에 사용할 변수들
	struct sockaddr_in clientaddr;
	int addrlen;
	char buf[BUFSIZE + 1];

	// ===== 클라이언트 데이터 통신 =====
	while (1) {
		addrlen = sizeof(clientaddr);
		// ===== RECVFROM() =====
		retval = recvfrom(sock, buf, BUFSIZE, 0, (struct sockaddr*)&clientaddr, &addrlen);
		if (retval == SOCKET_ERROR) {
			printf("RECVFROM() ERROR!!!\n");
			break;
		}

		// 받은 데이터 출력
		buf[retval] = '\0';
		char addr[INET_ADDRSTRLEN];
		inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr));
		printf("[UDP %s:%d] %s\n", addr, ntohs(clientaddr.sin_port), buf);

		// ===== SENDTO() =====
		retval = sendto(sock, buf, retval, 0, (struct sockaddr*)&clientaddr, sizeof(clientaddr));
		if (retval == SOCKET_ERROR) {
			printf("SENDTO() ERROR!!!\n");
			break;
		}
	}

	// ===== 소켓 닫기 ==== 
	closesocket(sock);
	// ===== 윈속 닫기 ===== 
	WSACleanup();
}


UDP Client 구현

UDPClient.cpp

 

#include "Common.h"
#define SERVERPORT 9000
#define BUFSIZE 512

char* SERVERIP = (char*)"127.0.0.1";

int main() {
	int retval;

	// ===== 윈속 초기화 =====
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)	return 1;

	// ===== 소켓 생성 =====
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);	// 데이터 그램 방식
	if (sock == INVALID_SOCKET)	printf("SOCKET() ERROR!!!\n");

	// ===== 소켓 주소 구조체 초기화 =====
	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	inet_pton(AF_INET, SERVERIP, &serveraddr.sin_addr);
	serveraddr.sin_port = htons(SERVERPORT);

	struct sockaddr_in peeraddr;	// 수신주소
	int addrlen;
	char buf[BUFSIZE + 1];
	int len;

	// 서버와 데이터 통신
	while (1) {
		printf("\n[보낼데이터] : ");
		if (fgets(buf, BUFSIZE + 1, stdin) == NULL)
			break;

		// 문자열 정리
		len = (int)strlen(buf);
		if (buf[len - 1] = '\n')
			buf[len - 1] = '\0';
		if (strlen(buf) == 0)
			break;

		// ===== SENDTO() =====
		retval = sendto(sock, buf, (int)strlen(buf), 0, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
		if (retval == SOCKET_ERROR) {
			printf("SENDTO() ERROR!!!!\n");
			break;
		}
		printf("[UDP Client] %d Byte Send\n", retval);

		// ===== RECVFROM() =====
		addrlen = sizeof(peeraddr);
		retval = recvfrom(sock, buf, BUFSIZE, 0, (struct sockaddr*)&peeraddr, &addrlen);
		if (retval == SOCKET_ERROR) {
			printf("recv() ERROR!!!!\n");
			break;
		}

		// 송신자 IP와 수신자 IP주소 체크 (UDP 서버-클라이언트 모델일 때만)
		if (memcmp(&peeraddr, &serveraddr, sizeof(peeraddr))) {
			printf("주소가 다름\n");
			break;
		}

		// 데이터 출력
		buf[retval] = '\0';
		printf("[UDP Client] %d Byte Recive\n", retval);
		printf("[DATA] : %s \n", buf);
	}

	// 소켓 닫기
	closesocket(sock);
	// 윈속 초기화
	WSACleanup();
}

 

UDP 1:1 통신
UDP 1:3 통신 (1:n)


[아이콘 저작권]

디지털 아이콘 제작자: Muhammad_Usman - Flaticon

Ui 아이콘 제작자: Ilham Fitrotul Hayat - Flaticon

섬기는 사람 아이콘 제작자: Those Icons - Flaticon