본문 바로가기
Programming/열혈 TCP, IP 소켓 프로그래밍(저자 윤성우)

Ch 14. 멀티캐스트 & 브로드캐스트

by minjunkim.dev 2020. 8. 9.

    모든 내용은 [윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어] 를 기반으로 제 나름대로 이해하여 정리한 것입니다. 다소 부정확한 내용이 있을수도 있으니 이를 유념하고 봐주세요!


# 다수의 클라이언트에게 동일한 데이터를 전송하는 일조차 서버와 네트워크의 트래픽 측면에서는 매우 부정적

- TCP 기반의 경우 : 클라이언트 수만큼 소켓연결을 유지해야 함

- UDP 기반의 경우 : 클라이언트 수만큼 데이터 전송이 필요

=> 이러한 상황에서의 해결책으로 "멀티캐스트"라는 기술이 존재

 

# 멀티캐스트
- UDP를 기반으로 함
- UDP 서버/클라이언트의 구현방식과 차이점이 있다면,
UDP에서의 데이터 전송은 하나의 목적지를 두고 이루어지나
멀티캐스트에서의 데이터 전송은 특정 그룹에 가입(등록)되어 있는 다수의 호스트가 목적지가 됨.
즉, 단 한번에 데이터 전송으로 다수의 호스트에 데이터를 전송할 수 있음.

# 멀티캐스트의 데이터 전송방식과 멀티캐스트 트래픽 이점
- 멀티캐스트 서버는 특정 멀티캐스트 그룹을 대상으로 데이터를 "딱 한 번" 전송
- 딱 한번 전송하더라도 그룹에 속하는 클라이언트는 모두 데이터를 수신
- 멀티캐스트 그룹의 수는 IP주소 범위 내에서 얼마든지 추가 가능
- 특정 멀티캐스트 그룹으로 전송되는 데이터를 수신하려면 해당 그룹에 가입하면 됨

(멀티캐스트 그룹이란? : 클래스 D(224.0.0.0 - 239.255.255.255)를 의미)
- 멀티캐스트 패킷의 형태가 UDP 패킷과 동일.

다만 일반적인 UDP 패킷과 달리 하나의 패킷만 네트워크상에 띄워놓으면

라우터들은 이 패킷을 복사해서 다수의 호스트에 이를 전달.

즉, 멀티캐스트는 "라우터의 도움으로 완성"됨.
- 하나의 영역에 동일한 패킷이 둘 이상 전송되지 않음.
- 멀티미디어 데이터의 실시간 전송에 주로 사용됨.

 

# 루프백 주소(loopback address)

- IP주소 클래스에 해당하는 번호를 보면 127번이 제외되어 있는 것을 알 수 있는데,
이 127번은 "127.0.0.1"번으로 사용하는 특별한 주소이기에 어느 클래스에도 속하지 않는다.
자기가 사용하는 LAN카드(NIC) 자신을 의미한다고 생각하면 쉽다.


# 라우팅(ROUTING)과 TTL(time to live), 그리고 그룹으로의 가입방법
- 멀티캐스트 패킷의 전송을 위해서는 TTL 설정과정을 반드시 거쳐야 함
TTL : 패킷을 얼마나 멀리 전달할 것인가를 결정하는 주요소.
정수로 표현되며 이 값은 라우터를 하나 거칠 때마다 1씩 감소.
이 값이 0이 되면 패킷은 더이상 전달되지 못하고 소멸됨.
TTL을 너무 크게 설정하면 네트워크 트래픽에 부정적인 영향을 줄 수 있으며,
너무 작게 설정해도 목적지에 도달하지 못하는 문제가 발생할 수 있음.

- TTL 설정은 소켓의 옵션설정을 통해 이루어짐.

- TTL 설정과 관련된 프로토콜 레벨은 IPPROTO_IP / 옵션의 이름은 IP_MULTICAST_TTL

int send_sock;
int time_live=64;
....
send_sock=socket(PF_INET,SOCK_DGRAM,0);
setsockopt(send_sock,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; // 가입할 그룹의 IP주소
    struct in_addr imr_interface; // 그룹에 가입하는 소켓이 속한 호스트IP(INADDR_ANY 사용가능)
}

# 멀티캐스트 Sender와 Receiver의 구현
- 멀티캐스트는 서버/클라이언트 개념이 없고 sender/receiver의 개념이 존재
- 멀티캐스트 sender는 UDP 소켓 프로그램과 차이가 크지 않으나,
멀티캐스트 receiver의 경우는 가입의 과정을 거쳐야만 한다.
- 실행의 순서는 중요하지 않다. TCP처럼 연결된 상태에서 송수신 하는 것이 아니기 때문.

그러나, 멀티캐스트는 receiver를 늦게 실행하면, 그 전에 sender에서 전송된 데이터는 수신이 불가능.

 

# new_sender.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#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) // 실행파일 경로/멀티캐스트IP/PORT번호 를 입력으로 받아야 함
    {
        printf("Usage : %s <GroupIP> <PORT> \n",argv[0]);
        exit(EXIT_FAILURE);
    }

    send_sock=socket(PF_INET,SOCK_DGRAM,0); // UDP 소켓 생성

    /* 멀티캐스트 주소정보 초기화 */
    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));
    // TTL 설정

    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,stdout);
    fputc('\n',stdout);
    exit(EXIT_FAILURE);
}

# news_receiver.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <linux/in.h>

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

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

    if(argc!=3) // 실행파일 경로/멀티캐스트IP/PORT번호 를 입력으로 받아야 함
    {
        printf("Usage : %s <GroupIP> <PORT> \n",argv[0]);
        exit(EXIT_FAILURE);
    }

    recv_sock=socket(PF_INET,SOCK_DGRAM,0); // UDP 소켓 생성

    /* 멀티캐스트 주소정보 초기화 */
    memset(&adr,0,sizeof(adr));
    adr.sin_family=AF_INET;
    adr.sin_addr.s_addr=inet_addr(argv[1]); // Multicast IP
    adr.sin_port=htons(atoi(argv[2])); // Multicast Port

    /* 멀티캐스트 주소정보를 기반으로 주소할당 */
    bind(recv_sock,(struct sockaddr*)&adr,sizeof(adr));
    join_adr.imr_multiaddr.s_addr=inet_addr(argv[1]); // 멀티캐스트 IP정보
    join_adr.imr_interface.s_addr=htonl(INADDR_ANY); // 호스트 IP정보

    setsockopt(recv_sock,IPPROTO_IP,IP_ADD_MEMBERSHIP,(void*)&join_adr,sizeof(join_adr));
    // 멀티캐스트 그룹 가입 설정

    while(true)
    {
        str_len=recvfrom(recv_sock,buf,BUF_SIZE-1,0,NULL,0); // 브로드캐스트 된 데이터를 수신
        if(str_len<0)
            break;
        buf[str_len]='\0';
        fputs(buf,stdout);
    }

    close(recv_sock); // 소켓 연결종료
    return 0;
}

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

 

# MBone(Multicast Backbone)
- 멀티캐스트는 MBone이라는 가상 네트워크(인터넷상에서 별도의 프로토콜을 기반으로 동작하는 소프트웨어적인 개념의 네트워크)를 기반으로 동작


# 브로드캐스트
- 한 번에 여러 호스트에게 데이터를 전송한다는 점에서 멀티캐스트와 유사하나,
전송이 이뤄지는 범위에서 차이가 난다.
- 멀티캐스트는 "서로 다른" 네트워크상에 존재하는 호스트라 할지라도
멀티캐스트 그룹에 가입만 되어있으면 데이터의 수신이 가능
- 브로드캐스트는 "동일한 네트워크"로 연결되어 있는 호스트로 데이터의 전송 대상에 제한

# 브로드캐스트의 이해와 구현방법
- 동일한 네트워크에 연결되어 있는 모든 호스트에게 동시에 데이터를 전송하기 위한 방법
- 멀티캐스트와 마찬가지로 UDP를 기반으로 데이터를 송수신

- 데이터 전송시 사용되는 IP주소의 형태에 따라서 다음과 같은 두가지 형태로 구분됨
1) Directed 브로드캐스트 : 네트워크IP를 제외한 나머지, 호스트 주소를 전부 1로 설정하면 얻을 수 있음(즉, 255(8bit가 전부 1이므로)를 의미)
- 129.12.34 네트워크IP에 연결되어있는 모든 호스트에게 데이터를 전송하고 싶다면,

129.12.34.255 로 데이터를 전송하면됨!
2) Local 브로드캐스트(UDP 예제와 구분이 잘 안되니 주의, IP주소를 잘보아야 함, 소켓옵션 변경도 필요) :
255.255.255.255라는 특별히 예약된 IP주소에 데이터를 전송하면,
데이터를 전송한 호스트의 네트워크IP에 연결되어있는 모든 호스트에 데이터가 전달됨

- 기본적으로 생성되는 소켓은 브로드캐스트 기반의 데이터 전송이 불가능하도록 설정되어 있으므로

다음 유형의 코드 구성을 통해서 변경해야 함(이는 sender에서만 구현하면 됨)

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와 Receiver의 구현

 

news_sender_brd.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

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

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

    if(argc!=3) // 실행파일 경로/브로드캐스트IP/PORT번호 를 입력으로 받아야 함
    {
        printf("Usage : %s <Broadcast IP> <PORT> \n",argv[0]);
        exit(EXIT_FAILURE);
    }

    send_sock=socket(PF_INET,SOCK_DGRAM,0); // UDP 소켓 생성

    /* 브로드캐스트 주소정보 초기화 */
    memset(&broad_adr,0,sizeof(broad_adr));
    broad_adr.sin_family=AF_INET;
    broad_adr.sin_addr.s_addr=inet_addr(argv[1]); // Broadcast IP
    broad_adr.sin_port=htons(atoi(argv[2])); // Broadcast Port

    setsockopt(send_sock,SOL_SOCKET,SO_BROADCAST,(void*)&so_brd,sizeof(so_brd));
    // SO_BROADCAST 옵션값을 1로 변경(broadcast 가능하게 만듦)

    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*)&broad_adr,sizeof(broad_adr));
        // 브로드캐스트 주소정보를 토대로 데이터를 송신
        sleep(2);
    }

    fclose(fp); // 파일 닫기
    close(send_sock); // 소켓 연결종료
    return 0;
}

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

 

news_receiver_brd.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

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

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

    if(argc!=2) // 실행파일 경로/PORT번호 를 입력으로 받아야 함
    {
        printf("Usage : %s <PORT> \n",argv[0]);
        exit(EXIT_FAILURE);
    }

    recv_sock=socket(PF_INET,SOCK_DGRAM,0); // UDP 소켓 생성

    /* 자신의 주소정보 초기화 */
    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]));

    /* 주소정보를 토대로 주소할당 */
    bind(recv_sock,(struct sockaddr*)&adr,sizeof(adr));

    while(true)
    {
        str_len=recvfrom(recv_sock,buf,BUF_SIZE-1,0,NULL,0); // 브로드캐스트IP에서 데이터를 수신
        if(str_len<0)
            break;
        buf[str_len]='\0';
        fputs(buf,stdout);
    }
    close(recv_sock); // 소켓 연결 종료
    return 0;
}

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

[출처] : 윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어