2014년 12월 21일 일요일

TCP/IP 소켓 프로그래밍 14강

14강은 멀티 캐스트와 브로드 캐스트를 알아 볼 것이다.

14-1 : 멀티캐스트


우리가 인터넷 방송을 한다고 가정했을 때 멀티미디어 정보를 전송해야한다.
그 때 필요한 멀티 캐스트와 브로드 캐스트를 알아 볼 것이다.
먼저 멀티 캐스트를 알아보자.
멀티 캐스트는 UDP를 기반으로 한다. 그래서 UDP와 구현방식이 매우 유사하다.
차이점은 멀티 캐스트에서의 데이터 전송은 특정 그룹에 가입(등록)되어 있는 다수의 호스트가 된다는 점이다.
멀티 캐스트 방식을 이용하면 단 한번에 데이터 전송으로 다수의 호스트에게 데이터를 전송 할 수 있다.

멀티캐스트의 데이터 전송방식과 멀티캐스트 트래픽 이점


멀티캐스트의 데이터 전송특성은 다음과 같이 정리할 수 있다.
-멀티캐스트 서버는 특정 멀티캐스트 그룹을 대상으로 데이터를 딱 한번 전송한다.
-한번 전송하더라도 그룹에 속하는 클라이언트는 모두 데이터를 수신한다.
-멀티캐스트 그룹의 수는 IP주소 범위 내에서 얼마든지 추가가 가능하다
-특정 멀티캐스트 그룹으로 전송되는 데이터를 수신하려면 해당 그룹에 가입하면 된다.

여기서 말하는 멀티캐스트 그룹은
클래서 D에 속하는 IP주소(224.0.0.0~239.255.255.255)를 말한다.

딱 한번 전송으로 다수가 수신이 가능한 이유는 라우터가 패킷을 복사해서 다수의 호스트에 전달하는 방식이기 때문이다. 물론 이 방식은 트래픽 측명에서 부정적이다.
그렇게 때문에  TTL이라는 것을 설정해야 한다.

라우팅(Routing)과 TTL(Time to Live), 그리고 그룹으로의 가입방법

멀티캐스트 패킷의 전송을 위해서는 TTL이라는 것을 반드시 설정해야한다.
패킷을 얼마나 멀리 전달할 것인가를 결정하는 주 요소가 된다.
TTL은 정수로 표현하고, 이 값은 라우터를 하나 거칠 때마다 1씩 감소한다.
이 값이 0이 되면 더이상 패킷은 전달되지 않고 소멸한다.

그래서 이 값을 너무 크게 설정하면 네트워크 트래픽에 나쁜 영향을 미치고,
너무 작게 설정하면 목적지에 도달하지 않을 수 있다.

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이다.

멀티캐스트 Sender와 Receiver의 구현


멀티캐스트 기반에서는 서버, 클라이언트라는 표현을 대신해서 전송자(Sender라 표시한다), 수신자(Receiver라 표현한다.)라는 표현을 사용한다.
Sender는 멀티캐스트 데이터의 전송주체다.
Receiver는 멀티캐스트 그룹의 가입과정이 필요한 데이터의 수신주체다.
먼저 Sender의 소스를 보자.

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){
printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
exit(1);
}
//UDP 소켓을 사용하고 있다.  
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]);  // 멀티캐스트의 IP
mul_adr.sin_port=htons(atoi(argv[2]));       // 멀티캐스트의  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;
}

이번에는 Receiver의 소스코드를 보자.

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) {
printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
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");
//멀티캐스트의 IP 주소
join_adr.imr_multiaddr.s_addr=inet_addr(argv[1]);
//그룹에 가입할 IP 주소
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;
fputs(buf, stdout);
}
close(recv_sock);
return 0;
}

두 소스에서 PORT번호 는 당연히 일치 시켜야 한다.
실행 순서는 중요하지 않지만 아무래도 방송이다 보니
Receiver을 늦게 실행하면, 그 이전에 전송된 멀티캐스트 데이터는 수신이 불가능하다.

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


브로드캐스트 역시 한번에 여러 호스트에게 데이터를 전송하는 것은 멀티캐스트와 유사하다. 하지만 전송이 이루어지는 범위가 동일한 네트워크로 연결되어 있는 호스트로, 데이터 전송의 대상이 제한된다.

브로드캐스트의 이해와 구현방법


브로드캐스트는 동일한 네트워크에 연결되어 있는 모든 호스트에게 동시에 데이터를 전송하기 위한 방법이다. 역시 UDP를 기반으로 사용한다.

그리고 IP주소 형태에 따라 다음과 같이 두 가지 형태로 구분이 된다.

-Directed 브로드캐스트
-Local 브로드캐스트

이 둘의 차이점은 IP주소에 있다.
Directed 브로드캐스트의 IP주소는 네트워크 주소를 제외한 나머지 호스트 주소를 전부 1로 설정해서 얻을 수 있다.

Local 브로드캐스트를 위해서는 255.255.255.255 라는 IP 주소가 특별히 예약되어 있다.
IP 주소가 UDP 예제와 유일한 차이점이다.
다만 기본적으로 생성되는 소켓은 브로드캐스트 기반이 아니니 변경해줘야 한다.

sender 소스를 보자.

int main(int argc, char *argv[])
{
int send_sock;
struct sockaddr_in broad_adr;
FILE *fp;
char buf[BUF_SIZE];
//SO_BROADCAST의 옵션 정보를 1로 변경하기 위한 변수 초기화
int so_brd=1;
if(argc!=3) {
printf("Usage : %s <Boradcast IP> <PORT>\n", argv[0]);
exit(1);
}
  //UDP 소켓을 사용하도록 설정
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]));
//SOL_SOCKET의 옵션정보를 변수 bcast에 저장된 값인 1로 변경하는데,
//이는 브로드캐스트 기반의 데이터 전송이 가능함을 의미한다.
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);
}

close(send_sock);
return 0;
}

이번에는 receiver 소스를 보자.

int main(int argc, char *argv[])
{
int recv_sock;
struct sockaddr_in adr;
int str_len;
char buf[BUF_SIZE];
if(argc!=2) {
printf("Usage : %s  <PORT>\n", argv[0]);
exit(1);
}
//UDP 설정
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;
fputs(buf, stdout);
}
close(recv_sock);
return 0;
}

위 소스는 전형적인 UDP 소켓 소스니 더 설명은 필요 없을 듯하다.

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


윈도우 기반에서도 그대로 적용되지만 약간 헤더파일의 선언만 바뀐다.
소스를 보자.( 왜 윈도우는 따로 놀려고 하는가 ㅡㅡ)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>     // for IP_MULTICAST_TTL option

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

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

if(argc!=3) {
printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
ErrorHandling("WSAStartup() error!"); 
 //UDP 사용
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)
ErrorHandling("fopen() error");

while(!feof(fp))
{
fgets(buf, BUF_SIZE, fp);
sendto(hSendSock, buf, strlen(buf), 
0, (SOCKADDR*)&mulAdr, sizeof(mulAdr));
Sleep(2000);
}
closesocket(hSendSock);
WSACleanup();
return 0;
}

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

receiver 소스를 보자.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>     // for struct ip_mreq

#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) {
printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
ErrorHandling("WSAStartup() error!"); 
  //UDP 소켓으로 설정, 주소 설정
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)
ErrorHandling("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;
fputs(buf, stdout);
}
closesocket(hRecvSock);
WSACleanup();
return 0;
}

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

역시 소스를 받아서 직접 해보자.

이로써 14강은 끝났다. 그와 동시에 파트1도 끝났다.
파트1은 기본적인 소켓 프로그래밍을 배웠고
파트2는 리눅스 기반 프로그래밍을 배운다.
파트3는 윈도우 기반 프로그래밍을 배운다.

파트1이 끝났으니 난 당분간 밀린 동영상 강의를 보면서 복습을 하고자 한다.
그다음 파트2로 넘어가고 이곳에 요약 정리 할 것이다.
책을 6권을 빌려서 얼렁 봐야하는 것도 있다 ㅡㅡ;;;

해피 메리크리스마스~ 해피 뉴이어~ ^0^

댓글 없음:

댓글 쓰기