멀티캐스트 방식은 UDP를 기반으로 하는 전송 방식임
둘 이상의 호스트에게 데이터를 전송하고자 하는 경우에는 데이터를 두 번 전송해야 함
그러나 멀티캐스트 방식에서는 데이터 전송의 목적지가 하나의 호스트가 아니라 멀티캐스트 그룹에
속해 있는 모든 호스트들이 목적지가 됨
→ 단 한번의 데이터 전송으로 여러 클라이언트들에게 데이터를 전달할 수 있게 됨
멀티캐스트 패킷을 수신하기 위해서는 멀티캐스트 그룹에 가입을 해야 함
멀티캐스트 패킷이란 받는 사람의 주소 정보가 클래스 D의 IP 주소 중 하나로 설정되어 있는 UDP 패킷을 의미
멀티캐스트 패킷을 전송하기 위해서는 TTL 설정 과정을 반드시 거쳐야 함
TTL은 TimeToLive의 약자로 “얼마나 멀리 전달될 것인가?”를 결정하는 주요 요소임
TTL은 정수로 표현되며, 라우터를 거쳐갈 때마다 하나씩 감소하게 되며
0이 되면 패킷은 더 이상 전달되지 못하고 소멸됨
→ TTL 값을 너무 크게 주면 네트워크의 트래픽에 좋지 못한 영향을 주게 되며
너무 적게 설정해도 목적지에 도달하지 못하는 문제를 가져올 수 있음
멀티캐스트 그룹으로 패킷을 전송하는 호스트가 Sender가 되고,
멀티캐스트 그룹에 가입을 해서 패킷 수신을 기대하고 있는 호스트가 Receiver가 됨
Receiver는 데이터를 수신하기 위해 가입(join)의 절차를 거쳐야 하지만
Sender는 UDP 소켓을 생성하고 멀티캐스트 주소로 데이터를 전송해 주기만 하면 됨
TTL도 반드시 설정해 줘야 함
(TTL 설정 없이 멀티캐스트 주소로 데이터를 전송하면 기본적으로 TTL은 1이기 때문에 외부로 패킷 전송 불가)
[sender 구현]
// news_sender.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define TTL 64
#define BUFSIZE 30
void error_handling(char* message);
int main(int argc, char** argv)
{
int send_sock;
struct sockaddr_in multi_addr;
int multi_TTL = TTL;
int state;
FILE* fp;
char buf[BUFSIZE];
if(argc != 3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
send_sock = socket(PF_INET, SOCK_DGRAM, 0); // UDP 소켓 생성
if(send_sock == -1)
error_handling("socket() error");
// 데이터를 전송할 주소 정보 설정
memset(&multi_addr, 0, sizeof(multi_addr));
multi_addr.sin_family = AF_INET;
multi_addr.sin_addr.s_addr = inet_addr(argv[1]);
multi_addr.sin_port = htons(atoi(argv[2]));
// 소켓의 디폴트 TTL 변경
state = setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&multi_TTL, sizeof(multi_TTL));
if(state)
error_handling("setsockopt() error");
if((fp = fopen("News.txt", "r")) == NULL)
error_handling("fopen() error");
while(!feof(fp))
{
fgets(buf, BUFSIZE, fp);
// 데이터 전송
// 전송 방식이 UDP이므로 sendto 사용
sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&multi_addr, sizeof(multi_addr));
sleep(2);
}
close(send_sock);
return 0;
}
void error_handling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
[receiver 구현]
// news_receiver.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define BUFSIZE 30
void error_handling(char* message);
int main(int argc, char** argv)
{
int recv_sock;
struct sockaddr_in addr;
int state, str_len;
char buf[BUFSIZE];
struct ip_mreq join_addr; // ip_mreq 구조체 변수를 선언
if(argc != 3)
{
printf("Usage : %s <GroupIP> <port>\n", argv[0]);
exit(1);
}
recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
if(recv_sock == -1)
error_handling("socket() error");
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(argv[2]));
if(bind(recv_sock, (struct sockaddr*)&addr, sizeof(addr)) == -1)
error_handling("bind() error");
// join_addr 초기화
join_addr.imr_multiaddr.s_addr = inet_addr(argv[1]);
join_addr.imr_interface.s_addr = htonl(INADDR_ANY);
// IP_ADD_MEMBERSHIP 옵션 설정을 통해 멀티캐스트 그룹에 가입을 하고 있음
state = setsockopt(recv_sock, IPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&join_addr, sizeof(join_addr));
if(state)
error_handling("setsockopt() error");
while(1) // 데이터 수신
{
str_len = recvfrom(recv_sock, buf, BUFSIZE - 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, stderr);
fputc('\n', stderr);
exit(1);
}
[ip_mreq 구조체]
struct ip_mreq
{
struct in_addr imr_multiaddr; // IPv4 class D multicast addr : 가입할 멀티캐스트 그룹의 주소 명시
struct in_addr imr_interface; // Ipv4 addr of local interface : 가입하는 호스트의 주소 정보 명시
// INADDR_ANY도 가능
}
인자 전달 시 멀티캐스트 주소를 전달해야 함
동일한 네트워크로 연결되어 있는 모든 호스트들에게 데이터를 전송해 주기 위해서 사용됨
기본적으로 UDP 방식 사용
브로드캐스트 주소는 네트워크 IP를 제외한 나머지 호스트 IP를 전부 1로 설정하면 얻을 수 잇음
(예를 들어 192.12.34로 시작하는 클래스 C 네트워크 주소의 경우 192.12.34.255와 같이 설정하면 됨)
패킷을 전송하는 호스트가 속해 있는 네트워크에 연결되어 있는 모든 호스트들에게 패킷을 전달하는 경우
255.255.255.255라는 브로드캐스트 주소를 사용할 수 있음 (지역적 브로드캐스트)
패킷이 전송되는 주소를 브로드캐스트 주소로 지정해주고,
브로드캐스트를 위애 생성한 소켓이 브로드캐스트가 가능하도록 설정해 주기만 하면 됨
(디폴트로 생성되는 UDP 소켓은 브로드캐스트가 불가능하도록 초기화 되어있기 때문에 설정이 필요함)
[sender]
// news_sender_broad.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define BUFSIZE 30
#define TRUE 1
#define FALSE 0
void error_handling(char* message);
int main(int argc, char** argv)
{
int send_sock;
struct sockaddr_in broad_addr;
int state;
FILE* fp;
char buf[BUFSIZE];
int so_broadcast = TRUE;
if(argc != 3)
{
printf("Usage : %s <Broadcast IP> <port>\n", argv[0]);
exit(1);
}
// UDP 소켓 생성
send_sock = socket(PF_INET, SOCK_DGRAM, 0);
if(send_sock == -1)
error_handling("socket() error");
memset(&broad_addr, 0, sizeof(broad_addr));
broad_addr.sin_family = AF_INET;
broad_addr.sin_addr.s_addr = inet_addr(argv[1]);
broad_addr.sin_port = htons(atoi(argv[2]));
// UDP 소켓의 옵션 변경 (브로드캐스트가 가능하도록)
state = setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&so_broadcast, sizeof(so_broadcast));
if(state)
error_handling("setsockopt() error");
if((fp = fopen("News.txt", "r")) == NULL)
error_handling("fopen() error");
while(!feof(fp))
{
fgets(buf, BUFSIZE, fp);
// 데이터 전송
sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&broad_addr, sizeof(broad_addr));
sleep(2);
}
close(send_sock);
return 0;
}
void error_handling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
[receiver]
브로드캐스트 Receiver는 bind 함수의 호출을 필요로 함
브로드캐스트 주소로 패킷이 전송되었다 하더라도 Port는 일치시켜 줘야 패킷을 수신할 수 있기 때문
즉 Sender가 Port 9190으로 브로드캐스트 했을 경우, Receiver가 패킷을 수신하기 위해서는 소켓의 Port 정보를
9190으로 미리 맞춰 놓고 기다리고 있어야 함
→ 그러기 위해 bind 함수의 호출이 선행되어야 함
// news_receiver_broad.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define BUFSIZE 30
void error_handling(char* message);
int main(int argc, char** argv)
{
int recv_sock;
struct sockaddr_in addr;
int str_len;
char buf[BUFSIZE];
if(argc != 2)
{
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
if(recv_sock == -1)
error_handling("socket() error");
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(argv[1]));
// 소켓에 Port 할당
// 이제 여기서 할당된 Port로 전달되는 브로드캐스트 패킷은 모두 수신 가능
if(bind(recv_sock, (struct sockaddr*)&addr, sizeof(addr)) == -1)
error_handling("bind() error");
while(1)
{
str_len = recvfrom(recv_sock, buf, BUFSIZE - 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, stderr);
fputc('\n', stderr);
exit(1);
}