멀티케스트 소켓 구현 문제 해결해주세요 ㅠ
글쓴이: lwsang21 / 작성시간: 수, 2020/08/12 - 8:04오후
리눅스 환경에서 멀티케스트 소켓통신을 C로 구현했는데 로컬네트워크에서는 잘 작동하는데 인터네트워크에서는 수신단 쪽에 패킷이 안들어옵니다.
정말 오랫동안 해결하려고 별 짓을 다했지만 모르겠네요,,,
해결해주시면 정말 감사하겠습니다.
급하게 작성한다고 주석은 엉망입니다,,,
<송신단>
//SERVER SOCKET #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdint.h> #define MULPORTNUM 9005 #define TPPORTNUM 9000 #define BROADNUM 10 #define NODENUM 2 int main(void) { char buf[256], ipad[NODENUM][INET_ADDRSTRLEN]; struct sockaddr_in mult, sin, cli; struct in_addr localaddr; float beacon_interval = 0; int min_offset = 0, offset = 0, optval = 1; int mt, sd, nsd[NODENUM], min, iter = 0, clen = sizeof(cli), multiTTL = 64; int32_t offset_time = 0, offset_utime = 0, loc_time[NODENUM][BROADNUM], loc_utime[NODENUM][BROADNUM], My_time[NODENUM][BROADNUM], My_utime[NODENUM][BROADNUM]; if ((mt = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1){ //UDP socket을 생성(연결 성공시 0을 리턴 실패시 -1을 리턴) perror("socket"); //에러메시지를 출력하는 함수 exit(1); //1을 반환하면서 프로그램 종료 } if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ //TCP socket을 생성(연결 성공시 0을 리턴 실패시 -1을 리턴) perror("socket"); //에러메시지를 출력하는 함수 exit(1); //1을 반환하면서 프로그램 종료 } if ((setsockopt(mt , IPPROTO_IP, IP_MULTICAST_TTL, (char*)&multiTTL, sizeof(multiTTL))) == -1){ perror("setsockopt"); //에러메시지를 출력하는 함수 exit(1); //1을 반환하면서 프로그램 종료 } if ((setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))) == -1){ perror("setsockopt"); //에러메시지를 출력하는 함수 exit(1); //1을 반환하면서 프로그램 종료 } memset((char *)&mult, '\0', sizeof(mult)); //socket 구조체에 값을 지정(&ser-메모리의 시작 주소, \0-메모리에 채우고자 하는 값, size-채우고자하는 메모리의 크기) mult.sin_family = AF_INET; //socket family를 AF_INET으로 지정 mult.sin_port = htons(MULPORTNUM); mult.sin_addr.s_addr = inet_addr("224.0.1.222"); //소켓 주소 구조체에 서버의 주소를 지정 localaddr.s_addr = htonl(INADDR_ANY);; setsockopt(mt , IPPROTO_IP, IP_MULTICAST_IF, (char*)&localaddr, sizeof(localaddr)); memset((char *)&sin, '\0', sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(TPPORTNUM); sin.sin_addr.s_addr = htonl(INADDR_ANY);; //소켓 주소 구조체에 서버의 주소를 지정 if (bind(sd, (struct sockaddr *)&sin, sizeof(sin))) { perror("bind"); exit(1); } // Broadcasting phase for (int n = 1; n <= BROADNUM; n++) { sprintf(buf, "%d", n); if ((sendto(mt, buf, strlen(buf)+1, 0, (struct sockaddr *)&mult, sizeof(mult))) == -1) { //클라이언트에게 sendto 함수로 msg를 전송(mt, buf-전송할 msg를 저장한 메모리 주소, msg의 크기, 0-데이터를 주고받는 방법을 지정한 플래그, &cli-msg를 전송할 호스트의 주소, &clientlen-&cli의 크기) perror("sendto"); exit(1); } sleep(beacon_interval); } strcpy(buf, "END"); //종료문전달 if ((sendto(mt, buf, strlen(buf)+1, 0, (struct sockaddr *)&mult, sizeof(mult))) == -1) { //클라이언트에게 sendto 함수로 msg를 전송(mt, buf-전송할 msg를 저장한 메모리 주소, msg의 크기, 0-데이터를 주고받는 방법을 지정한 플래그, &cli-msg를 전송할 호스트의 주소, &clientlen-&cli의 크기) perror("sendto"); exit(1); } // Time stamp phase if (listen(sd, NODENUM)) { //cli로 부터 time값을 수신받기위해 대기 perror("listen"); exit(1); } for (int n = 0; n < NODENUM; n++) { if ((nsd[n] = accept(sd, (struct sockaddr *)&cli, &clen)) == -1) { //accept 함수로 클라이언트의 접속 요청을 수락하고 새로운 소켓 기술자를 생성해 nsd에 저장(sd, &cli-client의 IP, &clen-cli의 크기) perror("accept"); exit(1); } inet_ntop(AF_INET, &(cli.sin_addr), ipad[n], INET_ADDRSTRLEN); if (recv(nsd[n], &loc_time[n][0], sizeof(loc_time[n])+1, 0) == -1) { perror("recv"); exit(1); } if (recv(nsd[n], &loc_utime[n][0], sizeof(loc_utime[n])+1, 0) == -1) { perror("recv"); exit(1); } } /*for (int n = 0; n < NODENUM; n++) { for (int i = 0; i < BROADNUM; i++) { printf("%i-th client %i-th stamp: %d.%d\n", n+1, i+1, loc_time[n][i], loc_utime[n][i]); } }*/ // Offset phase for (int n = 0; n < NODENUM; n++) { for (int i = 0; i < BROADNUM; i++) { // 기준 node 설정을 위한 기지국과 단말간 offset 계산 if (loc_time[n][i] != 0){ // 손실된 UDP는 무시 offset_time = offset_time + My_time[n][i] - loc_time[n][i]; offset_utime = offset_utime + My_utime[n][i] - loc_utime[n][i]; } } offset = offset_time*1000000 + offset_utime; if (n == 0) { min = n; min_offset = offset; } else if (offset < min_offset) { min = n; min_offset = offset; } //printf("%i-th client offset: %d\n", n+1, offset); offset = 0; offset_time = 0; offset_utime = 0; } //printf("%i-th client has minimum offset: %d\n", min+1, min_offset); for (int n = 0; n < NODENUM; n++) { if (n != min) { for (int i = 0; i < BROADNUM; i++) { // 기준 node와 다른 node 간 offset 측정 if (loc_time[n][i] != 0 && loc_time[min][i] != 0){ // 손실된 UDP는 무시 offset_time = offset_time + loc_time[min][i] - loc_time[n][i]; offset_utime = offset_utime + loc_utime[min][i] - loc_utime[n][i]; iter++; } } offset_time = offset_time / iter; offset_utime = offset_utime / iter; strcpy(buf, "Adjust Offset"); send(nsd[n], buf, strlen(buf)+1, 0); send(nsd[n], &offset_time, sizeof(offset_time)+1, 0); send(nsd[n], &offset_utime, sizeof(offset_utime)+1, 0); printf("RBS synchronization %ius\n", abs(offset_time*1000000)+abs(offset_utime)); iter = 0; offset_time = 0; offset_utime = 0; } else if (n == min){ strcpy(buf, "END"); //종료문전달 send(nsd[n], buf, strlen(buf)+1, 0); close(nsd[n]); } } close(sd); return 0; } <\code> <수신단> <code lang="c"> //CLIENT SOCKET #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/time.h> #include <unistd.h> #include <stdint.h> #define MULPORTNUM 9005 #define TPPORTNUM 9000 #define BROADNUM 10 int main(void) { char buf[256], endst[50] = "END"; struct sockaddr_in mult, sin; struct ip_mreq joinAddr; struct timeval tv_cli, current; int32_t offset_time, offset_utime, loc_time[BROADNUM], loc_utime[BROADNUM], timelen = 0; int mt, sd, n, index, multlen = sizeof(mult), broadcast = 1; if ((mt = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1){ //socket을 생성(연결 성공시 0을 리턴 실패시 -1을 리턴) perror("socket"); //에러메시지를 출력하는 함수 exit(1); //1을 반환하면서 프로그램 종료 } memset((char *)&mult, '\0', sizeof(mult)); //socket 구조체에 값을 지정(&ser-메모리의 시작 주소, \0-메모리에 채우고자 하는 값, size-채우고자하는 메모리의 크기) mult.sin_family = AF_INET; //socket family를 AF_INET으로 지정 mult.sin_port = htons(MULPORTNUM); mult.sin_addr.s_addr = htonl(INADDR_ANY); //소켓 주소 구조체에 서버의 주소를 지정 if (bind(mt, (struct sockaddr *)&mult, sizeof(mult))) { //17행에서 생성한 소켓을 bind 함수로 22~25행에서 설정한 IP, port 번호와 연결(실패시 -1을 리턴 성공시 0을 리턴)(mt-socket에서 선언한 구조체, &ser-AF_INET의 경우 sockaddr_in AF_UNIX의 경우 sockaddr, len-선언한 구조체의 크기) perror("bind"); exit(1); } //멀티캐스트 그룹 가입을 위한 구조체 joinAddr.imr_multiaddr.s_addr = inet_addr("224.0.1.222"); //Multicast group (=IP addr) joinAddr.imr_interface.s_addr = htonl(INADDR_ANY); //joinAddr.imr_interface.s_addr = inet_addr("112.212.149.232:2000"); if ((setsockopt(mt, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&joinAddr, sizeof(joinAddr))) == -1){ perror("setsockopt"); exit(1); } while (1) { // Broadcasting phase timelen = 0; memset((int32_t *)&loc_time, '\0', sizeof(loc_time)); memset((int32_t *)&loc_utime, '\0', sizeof(loc_utime)); while (1) { n = recvfrom(mt, buf, 255, 0, (struct sockaddr *)&mult, &multlen); //서버가 보낸 msg를 recvfrom 함수로 수신(mt, buf-전송받은 msg를 저장할 메모리 주소, msg의 크기, 0-데이터를 주고받는 방법을 지정한 플래그, msg를 보내는 서버의 주소, 주소의 크기) index = atoi(buf); if (strcmp(endst, buf)==0 || timelen == BROADNUM) break; if ((ioctl(mt, SIOCGSTAMP, &tv_cli)) == -1){ perror("ioctl"); exit(1); } if (index == timelen+1){ // UDP 정상 수신시 loc_time[index-1] = tv_cli.tv_sec; loc_utime[index-1] = tv_cli.tv_usec; } else{ // UDP 손실시 loc_time[timelen] = 0; loc_utime[timelen] = 0; loc_time[index-1] = tv_cli.tv_sec; loc_utime[index-1] = tv_cli.tv_usec; timelen = index-1; } printf("** From Server : %i packet\n", index); printf("** Local clock : %i.%i\n", loc_time[timelen], loc_utime[timelen]); timelen++; } printf("END BRD\n"); // Time stamp phase if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ perror("socket"); exit(1); } memset((char *)&sin, '\0', sizeof(sin)); //socket 구조체에 값을 지정(&ser-메모리의 시작 주소, \0-메모리에 채우고자 하는 값, size-채우고자하는 메모리의 크기) sin.sin_family = AF_INET; //socket family를 AF_INET으로 지정 sin.sin_port = htons(TPPORTNUM); sin.sin_addr.s_addr = inet_addr("210.107.214.194"); //소켓 주소 구조체에 서버의 주소를 지정 if (connect(sd, (struct sockaddr *)&sin, sizeof(sin))) { perror("connect"); exit(1); } if ((send(sd, &loc_time[0], sizeof(loc_time)+1, 0)) == -1) { perror("send"); exit(1); } printf("** send time\n"); if ((send(sd, &loc_utime[0], sizeof(loc_utime)+1, 0)) == -1) { perror("send"); exit(1); } printf("** send utime\n"); // Offset phase if (recv(sd, buf, sizeof(buf) + 1, 0) == -1) { perror("recv"); exit(1); } if (strcmp(endst, buf) != 0){ printf("** %s\n", buf); recv(sd, &offset_time, sizeof(offset_time)+1, 0); recv(sd, &offset_utime, sizeof(offset_utime)+1, 0); printf("** offset : %i.%i\n", offset_time, offset_utime); gettimeofday(¤t, 0); printf("** current time : %i.%i\n", current.tv_sec, current.tv_usec); current.tv_sec = current.tv_sec + offset_time; current.tv_usec = current.tv_usec + offset_utime; printf("** change time : %i.%i\n", current.tv_sec, current.tv_usec); settimeofday(¤t, 0); // 관리자 권한 필요 gettimeofday(¤t, 0); printf("** current time : %i.%i\n", current.tv_sec, current.tv_usec); } close(sd); } return 0; } <\code>
Forums:
잘은 모릅니다만..
잘은 모릅니다만..
1. 송신단과 수신단 사이에서 패킷을 중계하는 라우터나 브리지같은 장비가 있다면 해당 장비가 멀티캐스트 패킷을 전달하도록 설정돼 있어야 하고
2. 송신단/수신단 컴퓨터 각각에서, 사용하는 NIC가 여러개일 경우 어느 NIC를 통해 멀티캐스트 패킷이 드나드는지 확실히 하기 위한 라우트 엔트리가 추가돼야겠습니다. 예를 들면 이런 식으로요.
$ sudo ip route add 224.0.1.222/32 dev eth0
답글 정말 감사합니다!
1. 일단 두개의 컴터 모두 각각 집과 직장의 공유기에 물려있어서 사이에 라우터나 브리지가 어떠한 방식으로 설정돼있는지 알 수가 없습니다ㅠ
2. 송수신단 모두 라즈베리파이 컴퓨터여서 NIC는 하나입니다 ㅠ
인터넷으로 임의의 멀티캐스트 패킷을 직접 전송하는
인터넷으로 임의의 멀티캐스트 패킷을 직접 전송하는 것은 불가능하므로 어떤 형태로든 터널을 만들어야 할 겁니다. 일례로, VPN으로 집과 직장의 라즈베리파이 사이를 사설망으로 묶고 VPN을 통해 멀티캐스트 패킷을 전송한다든지 하는 식으로요. 저는 관련 경험이나 아는 바가 없네요..
멀티캐스팅이 인터넷 구간에서 실현되려면, 서버가
멀티캐스팅이 인터넷 구간에서 실현되려면, 서버가 멀티캐스팅 그룹에 가입할 때,
setsockopt(mt, IPPROTO_IP, IP_ADD_MEMBERSHIP,...
* Layer 2를 담당하는 드라이버에 multicast 주소에 대한 수신을 허용하라는 명령이 내려가고,
* IGMP를 이용하여 Router에 해당 주소에 대한 routing 요청을 전달하게 됩니다.
그런데, IGMP에 대한 요청이 ISP들에 의해 무시되므로 인터넷을 이용한 Multicast가 쉽지 않습니다.
---
http://coolengineer.com
답글 정말 감사합니다!
그렇다면 혹시 위에 분 말씀대로 VPN을 이용한다면 가능할까요?
아니면 다른 방법이 있을까요,,,
댓글 달기