본문 바로가기
개인공부/윤성우의 열혈 TCP&IP 소켓 프로그래밍

Chapter 14 - 멀티캐스트 & 브로드캐스트

by 하고싶은건많은놈 2023. 2. 24.

14-1 : 멀티캐스트(Multicast)

멀티캐스트 방식 데이터전송은 UDP 기반
단, 하나의 목적지를 두었던 UDP에서와 달리 멀티캐스트에서의 데이터 전송은 특정 그룹에 가입되어있는 다수의 호스트가 됨

멀티캐스트 그룹 : 클래스 D에 속하는 IP주소(224.0.0.0 - 239.255.255.255)

  • 멀티캐스트 서버는 특정 멀티캐스트 그룹을 대상으로 데이터를 딱 한번 전송
  • 한번의 전송으로 그룹에 속하는 모든 클라이언트가 데이터를 수신
  • 멀티캐스트 그룹의 수는 IP주소 내에서 추가 가능
  • 특정 멀티캐스트 그룹으로 전송되는 데이터를 수신하기 위해서는 해당 그룹에 가입

멀티캐스트 패킷은 형태가 UDP 패킷과 동일
라우터들이 네트워크상에 띄워놓아진 하나의 패킷을 복사하여 다수의 호스트에 전달
따라서 하나의 영역에 동일한 패킷이 둘 이상 전송되지 않고, 이로인해 멀티미디어 데이터의 실시간 전송에 주로 사용됨


멀티캐스트 패킷의 전송을 위해서는 TTL(Time to Live)의 설정과정이 필요

  • 패킷을 얼마나 멀리 전달할지를 결정하는 주 요소
  • 정수로 표현, 라우터를 하나 거칠때마다 1씩 감소
  • 0이되면 패킷이 더이상 전달되지 않고 소멸

TTL 설정은 소켓의 옵션설정을 통해 이루어짐
IPPROTO_IP 레벨의 IP_MULTICAST_TTL 옵션을 사용

int    send_sock;
int    time_live = 64;

send_sock = socket(PF_INET, SOCK_DGRAM, 0);
setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live));
...

멀티캐스트 그룹으로의 가입도 소켓의 옵션설정을 통해 이루어짐
IPPROTO_IP 레벨의 IP_ADD_MEMBERSHIP 옵션을 사용

int                recv_sock;
struct ip_mreq    join_adr;

recv_sock = socket(PF_INET, SOCK_DGRAM, 0);

join_adr.imr_multiaddr.s_addr = "멀티캐스트 그룹의 주소정보";
join_adr.imr_interface.s_addr = "그룹에 가입할 호스트의 주소정보";
setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr));

...

struct ip_mreq
{
    struct in_addr    imr_multiaddr;
    struct in_addr    imr_interface;
}
  • 구조체 ip_mreq의 첫번째 멤버 imr_multiaddr에는 가입할 그룹의 IP주소를 입력
  • 두번째 멤버 imr_interface에는 그룹에 가입하는 소켓이 속한 호스트의 IP주소를 명시, INADDR_ANY의 사용도 가능

멀티캐스트 기반에서는 서버 / 클라이언트 대신 전송자(Sender) / 수신자(Receiver)라는 표현을 사용

멀티캐스트 기반 Sender 예시

// news_sender.cpp

#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

using namespace std;

#define TTL 64
#define BUF_SIZE 30
void    error_handling(char *message);

int        main(int argc, char *argv[])
{
    int                    send_sock;
    struct sockaddr_in    mul_adr;
    int                    time_live = TTL;
    FILE                *fp;
    char                buf[BUF_SIZE];

    if(argc != 3)
    {
        cout << "Usage : " << argv[0] << " <GroupIP> <port>\n";
        exit(1);
    }

    send_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&mul_adr, 0, sizeof(mul_adr));
    mul_adr.sin_family = AF_INET;
    mul_adr.sin_addr.s_addr = inet_addr(argv[1]);    // Multicast IP
    mul_adr.sin_port = htons(atoi(argv[2]));        // Multicast Port

    setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live));
    if ((fp = fopen("news.txt", "r")) == NULL)
        error_handling("fopen() error");

    while (!feof(fp))    // Broadcasting
    {
        fgets(buf, BUF_SIZE, fp);
        sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&mul_adr, sizeof(mul_adr));
        sleep(2);
    }

    fclose(fp);
    close(send_sock);
    return 0;
}

void    error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

멀티캐스트 기반 Receiver 예시

// news_receiver.cpp

#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

using namespace std;

#define BUF_SIZE 30
void    error_handling(char *message);

int        main(int argc, char *argv[])
{
    int                    recv_sock;
    int                    str_len;
    struct sockaddr_in    adr;
    struct ip_mreq        join_adr;
    char                buf[BUF_SIZE];

    if(argc != 3)
    {
        cout << "Usage : " << argv[0] << " <GroupIP> <port>\n";
        exit(1);
    }

    recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&adr, 0, sizeof(adr));
    adr.sin_family = AF_INET;
    adr.sin_addr.s_addr = htonl(INADDR_ANY);
    adr.sin_port = htons(atoi(argv[2]));

    if (::bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1)
        error_handling("bind() error");

    join_adr.imr_multiaddr.s_addr = inet_addr(argv[1]);
    join_adr.imr_interface.s_addr = htonl(INADDR_ANY);

    setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr));

    while (1)
    {
        str_len = recvfrom(recv_sock, buf, BUF_SIZE - 1, 0, NULL, 0);
        if (str_len < 0)
            break;
        buf[str_len] = 0;
        cout << buf;
    }
    close(recv_sock);
    return 0;
}

void    error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

TCP 소켓처럼 연결된 상태에서 데이터를 송수싢나느 것이 아니기 때문에 실행의 순서가 중요하지 않음
단, Receiver를 실행하기 이전에 전송된 멀티캐스트 데이터는 수신이 불가능함


14-2 : 브로드캐스트(Broadcast)

브로드캐스트도 한번에 여러 호스트에게 데이터를 전송한다는 점에서는 멀티캐스트와 유사
그러나 브로드캐스트는 동일한 네트워크에 연결되어있는 호스트로 데이터의 전송 대상이 제한됨

  • UDP 기반으로 데이터 송수신
  • Directed 브로드캐스트 / Local 브로드캐스트로 구분
    • Directed : 네트워크 주소를 제외한 나머지 호스트 주소를 전부 1로 설정
    • Local : 255.255.255.255가 예약되어있음
  • 데이터 송수신에 사용되는 IP 주소만이 UDP 예제와의 차이점
  • 단, 기본 생성 소켓은 브로드캐스트 기반 데이터 전송이 불가능하도록 되어있기 때문에 별도의 설정이 필요
    int    send_sock;
    int    bcast = 1;    // SO_BROADCAST의 옵션정보를 1로 변경하기 위한 변수 초기화
    

send_sock = socket(PF_INET, SOCK_DGRAM, 0);
setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&bcast, sizeof(bcast));


브로드캐스트 기반 Sender 예시
```cpp
// news_sender_brd.cpp

#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

using namespace std;

#define BUF_SIZE 30
void    error_handling(char *message);

int        main(int argc, char *argv[])
{
    int                    send_sock;
    int                    so_brd = 1;
    struct sockaddr_in    broad_adr;
    FILE                *fp;
    char                buf[BUF_SIZE];

    if(argc != 3)
    {
        cout << "Usage : " << argv[0] << " <BroadcastIP> <port>\n";
        exit(1);
    }

    send_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&broad_adr, 0, sizeof(broad_adr));
    broad_adr.sin_family = AF_INET;
    broad_adr.sin_addr.s_addr = inet_addr(argv[1]);
    broad_adr.sin_port = htons(atoi(argv[2]));

    setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&so_brd, sizeof(so_brd));
    if ((fp = fopen("news.txt", "r")) == NULL)
        error_handling("fopen() error");

    while (!feof(fp))
    {
        fgets(buf, BUF_SIZE, fp);
        sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&broad_adr, sizeof(broad_adr));
        sleep(2);
    }

    fclose(fp);
    close(send_sock);
    return 0;
}

void    error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

브로드캐스트 기반 Receiver 예시

// news_receiver_brd.cpp

#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

using namespace std;

#define BUF_SIZE 30
void    error_handling(char *message);

int        main(int argc, char *argv[])
{
    int                    recv_sock;
    int                    str_len;
    struct sockaddr_in    adr;
    char                buf[BUF_SIZE];

    if(argc != 2)
    {
        cout << "Usage : " << argv[0] << " <port>\n";
        exit(1);
    }

    recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&adr, 0, sizeof(adr));
    adr.sin_family = AF_INET;
    adr.sin_addr.s_addr = htonl(INADDR_ANY);
    adr.sin_port = htons(atoi(argv[1]));

    if (::bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1)
        error_handling("bind() error");

    while (1)
    {
        str_len = recvfrom(recv_sock, buf, BUF_SIZE - 1, 0, NULL, 0);
        if (str_len < 0)
            break;
        buf[str_len] = 0;
        cout << buf;
    }
    close(recv_sock);
    return 0;
}

void    error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

14-3 : 윈도우 기반으로 구현하기

IP_MULTICAST_TTL 옵션을 위한 ws2tcpip.h 헤더의 추가 이외이는 리눅스와 다르지 않음

윈도우 기반 멀티캐스트 sender 예제

// news_sender_win.cpp

#include <iostream>
#include <unistd.h>
#include <winsock2.h>
#include <ws2tcpip.h>

using namespace std;

#define TTL 64
#define BUF_SIZE 30
void    ErrorHandling(char* message);

int        main(int argc, char *argv[])
{
    WSADATA                wsaData;
    SOCKET                hSendSock;
    SOCKADDR_IN            mulAdr;
    int                    time_live = TTL;
    FILE                *fp;
    char                buf[BUF_SIZE];

    if(argc != 3)
    {
        cout << "Usage : " << argv[0] << " <GroupIP> <port>\n";
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() error");

    hSendSock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&mulAdr, 0, sizeof(mulAdr));
    mulAdr.sin_family = AF_INET;
    mulAdr.sin_addr.s_addr = inet_addr(argv[1]);
    mulAdr.sin_port = htons(atoi(argv[2]));

    setsockopt(hSendSock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&timeLive, sizeof(timeLive));
    if ((fp = fopen("news.txt", "r")) == NULL)
        error_handling("fopen() error");

    while (!feof(fp))    // Broadcasting
    {
        fgets(buf, BUF_SIZE, fp);
        sendto(hSendSock, buf, strlen(buf), 0, (struct sockaddr*)&mulAdr, sizeof(mulAdr));
        sleep(2);
    }

    fclose(fp);
    closesocket(hSendSock);
    WSACleanup();
    return 0;
}

void    ErrorHandling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

윈도우 기반 멀티캐스트 receiver 예제

// news_receiver_win.cpp

#include <iostream>
#include <unistd.h>
#include <winsock2.h>
#include <ws2tcpip.h>

using namespace std;

#define BUF_SIZE 30
void    ErrorHandling(char* message);

int        main(int argc, char *argv[])
{
    WSADATA                wsaData;
    SOCKET                hRecvSock;
    SOCKADDR_IN            adr;
    struct ip_mreq        joinAdr;
    char                buf[BUF_SIZE];
    int                    strLen;

    if(argc != 3)
    {
        cout << "Usage : " << argv[0] << " <GroupIP> <port>\n";
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() error");

    hRecvSock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&adr, 0, sizeof(adr));
    adr.sin_family = AF_INET;
    adr.sin_addr.s_addr = htonl(INADDR_ANY);
    adr.sin_port = htons(atoi(argv[2]));

    if (::bind(hRecvSock, (SOCKADDR*)&adr, sizeof(adr)) == SOCKET_ERROR)
        error_handling("bind() error");

    joinAdr.imr_multiaddr.s_addr = inet_addr(argv[1]);
    joinAdr.imr_interface.s_addr = htonl(INADDR_ANY);

    if (setsockopt(hRecvSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&joinAdr, sizeof(joinAdr)) == SOCKET_ERROR)
        ErrorHandling("setsock() error");

    while (1)
    {
        strLen = recvfrom(hRecvSock, buf, BUF_SIZE - 1, 0, NULL, 0);
        if (strLen < 0)
            break;
        buf[strLen] = 0;
        cout << buf;
    }
    closesocket(hRecvSock);
    WSACleanup();
    return 0;
}

void    error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

내용 확인문제

  1. TTL이 의미하는 바는 무엇인가? 그리고 TTL의 값이 크게 설정되는 것과 작게 설정되는 것에 따른 차이와 문제점을 라우팅의 관점에서 설명해보자.

TTL이란 패킷을 라우터를 통해 얼마나 멀리 보낼지를 결정하는 주 요소
크게 설정하면 네트워크 트래픽에 부하가 걸릴 수 있고, 작게 설정하면 목적지에 도달하지 않을 수 있음


  1. 멀티캐스트와 브로드캐스트의 공통점은 무엇이고 또 차이점은 무엇인가? 데이터의 송수신 관점에서 설명해보자.

멀티캐스트와 브로드캐스트 모두 한번의 전송으로 다수의 호스트들에 데이터를 전송할 수 있음
그러나 멀티캐스트는 멀티캐스트 그룹에 속하는 호스트들에 데이터를 전송하고, 브로드캐스트는 동일한 네트워크에 연결되어있는 호스트로 데이터를 전송함


  1. 다음 중 멀티캐스트에 대한 설명으로 옳지 않은 것을 모두 고르면?
  • 멀티캐스트는 멀티캐스트 그룹에 가입한 모든 호스트에게 데이터를 전송하는데 사용되는 프로토콜이다. (O)
  • 멀티캐스트 그룹에 가입하기 위해서는 동일 네트워크에 연결되어 있어야한다. 즉, 둘 이상의 네트워크에 걸쳐서 하나의 멀티캐스트 그룹이 형성될 수 없다. (X)
  • 멀티캐스트 그룹에 가입할 수 있는 호스트의 수에는 제한이 없으나, 이 그룹으로 데이터를 전송하는 호스트(Sender)의 수는 하나로 제한이 된다. (X)
  • 멀티캐스트를 위한 소켓은 UDP 소켓이어야한다. 멀티캐스트는 UDP를 기반으로 데이터를 송수신하기 때문이다. (O)

  1. 멀티캐스트는 트래픽 측면에서도 긍정적이다! 그렇다면 어떠한 이유로, 어떻게 긍정적인지 TCP의 데이터 송수신 방식과 비교해서 설명해보자.

TCP는 다수의 호스트에게 데이터 전송시 모든 호스트마다 각 한번씩 데이터를 전송하는 과정이 필요하지만, 멀티캐스트는 라우터상에서 패킷의 복사가 이루어지므로 한 번만 전송하면 됨


  1. 멀티캐스트 방식의 데이터 송수신을 위해서는 MBone이라는 가상의 네트워크가 구성되어 있어야한다. 즉, MBone은 멀티캐스트를 위한 네트워크이다. 그런데 이러한 MBone을 가리켜 가상 네트워크라 한다. 그렇다면 여기서 말하는 가상 네트워크가 무엇을 뜻하는지 설명해보자.

인터넷상에서 별도의 프로토콜을 기반으로 동작하는 소프트웨어적인 개념의 네트워크

댓글